@wrongstack/core 0.270.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 +616 -46
  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 +1938 -349
  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
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as crypto2 from 'crypto';
2
- import { randomBytes, randomUUID, createHash, createCipheriv, createDecipheriv } from 'crypto';
2
+ import { randomBytes, randomUUID, createHash, createCipheriv, createDecipheriv, scryptSync } from 'crypto';
3
3
  import * as fsp3 from 'fs/promises';
4
4
  import { readFile, readdir, stat, mkdir } from 'fs/promises';
5
5
  import * as path3 from 'path';
@@ -9,6 +9,8 @@ import * as net from 'net';
9
9
  import * as os6 from 'os';
10
10
  import { hostname } from 'os';
11
11
  import * as fs3 from 'fs';
12
+ import { createReadStream } from 'fs';
13
+ import { createInterface } from 'readline';
12
14
  import { execFile, spawn } from 'child_process';
13
15
  import { promisify } from 'util';
14
16
  import { EventEmitter } from 'events';
@@ -59,8 +61,8 @@ async function atomicWrite(targetPath, content, opts = {}) {
59
61
  }
60
62
  let mode;
61
63
  try {
62
- const stat15 = await fsp3.stat(targetPath);
63
- mode = stat15.mode & 511;
64
+ const stat16 = await fsp3.stat(targetPath);
65
+ mode = stat16.mode & 511;
64
66
  } catch {
65
67
  mode = opts.mode;
66
68
  }
@@ -100,8 +102,8 @@ async function withFileLock(targetPath, fn, opts = {}) {
100
102
  }
101
103
  if (code !== "EEXIST") throw err;
102
104
  try {
103
- const stat15 = await fsp3.stat(lockPath);
104
- if (Date.now() - stat15.mtimeMs > staleMs) {
105
+ const stat16 = await fsp3.stat(lockPath);
106
+ if (Date.now() - stat16.mtimeMs > staleMs) {
105
107
  await fsp3.unlink(lockPath);
106
108
  continue;
107
109
  }
@@ -430,11 +432,11 @@ var init_session_registry = __esm({
430
432
  */
431
433
  async breakStaleLock(lockPath) {
432
434
  try {
433
- const [stat15, content] = await Promise.all([
435
+ const [stat16, content] = await Promise.all([
434
436
  fsp3.stat(lockPath),
435
437
  fsp3.readFile(lockPath, "utf8").catch(() => "")
436
438
  ]);
437
- const ageMs = Date.now() - stat15.mtimeMs;
439
+ const ageMs = Date.now() - stat16.mtimeMs;
438
440
  const ownerPid = Number.parseInt(content.trim(), 10);
439
441
  const ownerDead = Number.isInteger(ownerPid) && ownerPid > 0 && ownerPid !== process.pid && !pidAlive(ownerPid);
440
442
  if (ownerDead || ageMs > STALE_LOCK_MS) {
@@ -477,9 +479,9 @@ var init_session_registry = __esm({
477
479
  for (const name of await fsp3.readdir(dir)) {
478
480
  const isTemp = (name.startsWith(`${base}.`) || name.startsWith(`.${base}.`)) && name.endsWith(".tmp");
479
481
  if (!isTemp) continue;
480
- const stat15 = await fsp3.stat(path3.join(dir, name)).catch(() => null);
481
- if (!stat15) continue;
482
- if (now - stat15.mtimeMs > STALE_TMP_MS) stale.push({ name, mtimeMs: stat15.mtimeMs });
482
+ const stat16 = await fsp3.stat(path3.join(dir, name)).catch(() => null);
483
+ if (!stat16) continue;
484
+ if (now - stat16.mtimeMs > STALE_TMP_MS) stale.push({ name, mtimeMs: stat16.mtimeMs });
483
485
  }
484
486
  stale.sort((a, b) => b.mtimeMs - a.mtimeMs);
485
487
  await Promise.all(
@@ -562,6 +564,25 @@ function looksSecret(name) {
562
564
  }
563
565
  return false;
564
566
  }
567
+ function valueHasEmbeddedCredential(value) {
568
+ return /\b[a-z][a-z0-9+.-]*:\/\/[^/\s:@]*:[^/\s@]+@/i.test(value);
569
+ }
570
+ var NODE_OPTIONS_INJECTION_FLAG = /^(?:--require|-r|--import|--loader|--experimental-loader)$/;
571
+ var NODE_OPTIONS_INJECTION_FLAG_EQ = /^(?:--require|-r|--import|--loader|--experimental-loader)=/;
572
+ function sanitizeNodeOptions(value) {
573
+ const tokens = value.split(/\s+/).filter(Boolean);
574
+ const kept = [];
575
+ for (let i = 0; i < tokens.length; i++) {
576
+ const tok = tokens[i];
577
+ if (NODE_OPTIONS_INJECTION_FLAG_EQ.test(tok)) continue;
578
+ if (NODE_OPTIONS_INJECTION_FLAG.test(tok)) {
579
+ i++;
580
+ continue;
581
+ }
582
+ kept.push(tok);
583
+ }
584
+ return kept.join(" ");
585
+ }
565
586
  function buildChildEnv(optsOrSessionId) {
566
587
  const opts = typeof optsOrSessionId === "string" ? { sessionId: optsOrSessionId } : optsOrSessionId ?? {};
567
588
  const hasOwn = Object.hasOwn(process.env, "WRONGSTACK_CHILD_ENV_PASSTHROUGH");
@@ -582,11 +603,17 @@ function buildChildEnv(optsOrSessionId) {
582
603
  continue;
583
604
  }
584
605
  const upper = k.toUpperCase();
606
+ if (valueHasEmbeddedCredential(v)) continue;
585
607
  if (ALLOWED_KEYS.has(upper)) {
586
608
  out[k] = v;
587
609
  continue;
588
610
  }
589
611
  if (looksSecret(upper)) continue;
612
+ if (upper === "NODE_OPTIONS") {
613
+ const sanitized = sanitizeNodeOptions(v);
614
+ if (sanitized) out[k] = sanitized;
615
+ continue;
616
+ }
590
617
  if (upper.startsWith("NODE_") || upper.startsWith("NPM_") || upper.startsWith("PNPM_") || upper.startsWith("YARN_") || upper.startsWith("GIT_") || upper.startsWith("CI") || upper.startsWith("XDG_") || // Our own non-secret knobs (WRONGSTACK_HOME, WRONGSTACK_SESSION_ID, …).
591
618
  // Secrets never live in WRONGSTACK_* env vars (they're in the encrypted
592
619
  // vault). Forwarding keeps child wstack processes — e.g. ones spawned
@@ -676,7 +703,7 @@ function envFlag(value) {
676
703
  return !/^(0|false|no|off)$/i.test(value.trim());
677
704
  }
678
705
  var COLOR = isColorTty();
679
- var wrap = (open7, close) => (s) => COLOR ? `\x1B[${open7}m${s}\x1B[${close}m` : s;
706
+ var wrap = (open10, close) => (s) => COLOR ? `\x1B[${open10}m${s}\x1B[${close}m` : s;
680
707
  var color = {
681
708
  reset: wrap("0", "0"),
682
709
  bold: wrap("1", "22"),
@@ -727,9 +754,9 @@ async function updateJsonObjectFile(filePath, mutator) {
727
754
  await writeJsonObjectFile(filePath, next);
728
755
  return next;
729
756
  }
730
- function getJsonPath(root, path48) {
757
+ function getJsonPath(root, path50) {
731
758
  let current = root;
732
- for (const segment of path48) {
759
+ for (const segment of path50) {
733
760
  if (typeof segment === "number") {
734
761
  if (!Array.isArray(current)) return void 0;
735
762
  current = current[segment];
@@ -740,13 +767,13 @@ function getJsonPath(root, path48) {
740
767
  }
741
768
  return current;
742
769
  }
743
- function setJsonPath(root, path48, value) {
744
- if (path48.length === 0) {
770
+ function setJsonPath(root, path50, value) {
771
+ if (path50.length === 0) {
745
772
  if (!isJsonObject(value)) throw new Error("Root config value must be an object");
746
773
  return value;
747
774
  }
748
- const parent = ensureJsonParent(root, path48);
749
- const leaf = lastPathSegment(path48);
775
+ const parent = ensureJsonParent(root, path50);
776
+ const leaf = lastPathSegment(path50);
750
777
  if (typeof leaf === "number") {
751
778
  if (!Array.isArray(parent)) throw new Error(`Cannot set numeric segment ${leaf} on non-array parent`);
752
779
  parent[leaf] = value;
@@ -756,10 +783,10 @@ function setJsonPath(root, path48, value) {
756
783
  }
757
784
  return root;
758
785
  }
759
- function removeJsonPath(root, path48) {
760
- if (path48.length === 0) return false;
761
- const parent = getJsonPath(root, path48.slice(0, -1));
762
- const leaf = lastPathSegment(path48);
786
+ function removeJsonPath(root, path50) {
787
+ if (path50.length === 0) return false;
788
+ const parent = getJsonPath(root, path50.slice(0, -1));
789
+ const leaf = lastPathSegment(path50);
763
790
  if (typeof leaf === "number") {
764
791
  if (!Array.isArray(parent) || leaf < 0 || leaf >= parent.length) return false;
765
792
  parent.splice(leaf, 1);
@@ -769,27 +796,27 @@ function removeJsonPath(root, path48) {
769
796
  delete parent[leaf];
770
797
  return true;
771
798
  }
772
- async function setJsonPathInFile(filePath, path48, value) {
773
- return updateJsonObjectFile(filePath, (config) => setJsonPath(config, path48, value));
799
+ async function setJsonPathInFile(filePath, path50, value) {
800
+ return updateJsonObjectFile(filePath, (config) => setJsonPath(config, path50, value));
774
801
  }
775
- async function removeJsonPathInFile(filePath, path48) {
802
+ async function removeJsonPathInFile(filePath, path50) {
776
803
  return updateJsonObjectFile(filePath, (config) => {
777
- removeJsonPath(config, path48);
804
+ removeJsonPath(config, path50);
778
805
  });
779
806
  }
780
807
  function isJsonObject(value) {
781
808
  return typeof value === "object" && value !== null && !Array.isArray(value);
782
809
  }
783
- function lastPathSegment(path48) {
784
- const segment = path48[path48.length - 1];
810
+ function lastPathSegment(path50) {
811
+ const segment = path50[path50.length - 1];
785
812
  if (segment === void 0) throw new Error("Invalid empty JSON path");
786
813
  return segment;
787
814
  }
788
- function ensureJsonParent(root, path48) {
815
+ function ensureJsonParent(root, path50) {
789
816
  let current = root;
790
- for (let i = 0; i < path48.length - 1; i += 1) {
791
- const segment = path48[i];
792
- const nextSegment = path48[i + 1];
817
+ for (let i = 0; i < path50.length - 1; i += 1) {
818
+ const segment = path50[i];
819
+ const nextSegment = path50[i + 1];
793
820
  if (segment === void 0) throw new Error("Invalid empty JSON path segment");
794
821
  const nextContainer = typeof nextSegment === "number" ? [] : {};
795
822
  if (typeof segment === "number") {
@@ -808,6 +835,9 @@ var MAX_TOOL_CALLS = 80;
808
835
  var MAX_FACTS = 40;
809
836
  var MAX_ERRORS = 20;
810
837
  var MAX_DIGEST_CHARS = 4e3;
838
+ var RECENT_TOOL_CALL_SCAN_LIMIT = 20;
839
+ var EXTRACT_CONTENT_CAP_CHARS = 1e4;
840
+ var EXTRACT_ERROR_TAIL_LINES = 200;
811
841
  var WRITE_TOOLS = /* @__PURE__ */ new Set(["edit", "write", "replace", "patch"]);
812
842
  var READ_TOOLS = /* @__PURE__ */ new Set(["read", "grep", "glob", "ls", "tree"]);
813
843
  function createContextEvidenceState() {
@@ -833,8 +863,9 @@ function recordUserIntentEvidence(ctx, text) {
833
863
  }
834
864
  function recordToolOutputEvidence(ctx, input) {
835
865
  const state = ensureEvidence(ctx);
836
- const files = extractFiles(ctx, input.toolName, input.input, input.content);
837
- const symbols = extractSymbols(input.content, input.input);
866
+ const scanContent = input.content.length > EXTRACT_CONTENT_CAP_CHARS ? input.content.slice(0, EXTRACT_CONTENT_CAP_CHARS) : input.content;
867
+ const files = extractFiles(ctx, input.toolName, input.input, scanContent);
868
+ const symbols = extractSymbols(scanContent, input.input);
838
869
  const commands = extractCommands(input.toolName, input.input);
839
870
  const errors = extractErrors(input.content);
840
871
  const summary = summarizeToolOutput(input.toolName, input.input, input.content, {
@@ -877,7 +908,8 @@ function markAssistantReferencedEvidence(ctx, text) {
877
908
  const state = ensureEvidence(ctx);
878
909
  const haystack = text.toLowerCase();
879
910
  if (!haystack.trim()) return;
880
- for (const tool of state.toolCalls) {
911
+ const recent = state.toolCalls.length > RECENT_TOOL_CALL_SCAN_LIMIT ? state.toolCalls.slice(-RECENT_TOOL_CALL_SCAN_LIMIT) : state.toolCalls;
912
+ for (const tool of recent) {
881
913
  if (!metadataReferencedByText(tool, haystack)) continue;
882
914
  tool.status = "referenced";
883
915
  tool.referenceCount++;
@@ -1032,7 +1064,8 @@ function extractCommands(toolName, input) {
1032
1064
  return [command.slice(0, 220)];
1033
1065
  }
1034
1066
  function extractErrors(content) {
1035
- const lines = content.split(/\r?\n/);
1067
+ const allLines = content.split(/\r?\n/);
1068
+ const lines = allLines.length > EXTRACT_ERROR_TAIL_LINES ? allLines.slice(-EXTRACT_ERROR_TAIL_LINES) : allLines;
1036
1069
  const errors = [];
1037
1070
  for (const line of lines) {
1038
1071
  if (!/\b(error|exception|failed|failure|fatal|panic|timeout|denied|enoent|eacces|eperm|typeerror|syntaxerror)\b/i.test(line)) continue;
@@ -1441,8 +1474,8 @@ async function expandGlob(pattern) {
1441
1474
  for (const e of entries) {
1442
1475
  const full = `${dir}${SEP}${e}`;
1443
1476
  try {
1444
- const stat15 = await fsp3.stat(full);
1445
- if (stat15.isDirectory()) await walk3(full, rest);
1477
+ const stat16 = await fsp3.stat(full);
1478
+ if (stat16.isDirectory()) await walk3(full, rest);
1446
1479
  } catch {
1447
1480
  }
1448
1481
  }
@@ -1459,8 +1492,8 @@ async function expandGlob(pattern) {
1459
1492
  if (entries.includes(seg)) {
1460
1493
  const full = `${dir}${SEP}${seg}`;
1461
1494
  try {
1462
- const stat15 = await fsp3.stat(full);
1463
- if (stat15.isDirectory()) await walk3(full, rest);
1495
+ const stat16 = await fsp3.stat(full);
1496
+ if (stat16.isDirectory()) await walk3(full, rest);
1464
1497
  } catch {
1465
1498
  }
1466
1499
  }
@@ -1476,6 +1509,7 @@ function escapeRegex(s) {
1476
1509
  }
1477
1510
  var COMPILED_GLOB_CACHE = /* @__PURE__ */ new Map();
1478
1511
  var CACHE_MAX_SIZE = 2e3;
1512
+ var NEVER_MATCH = /[^\s\S]/;
1479
1513
  function getCachedGlob(pattern) {
1480
1514
  const cached = COMPILED_GLOB_CACHE.get(pattern);
1481
1515
  if (cached) return cached;
@@ -1485,7 +1519,12 @@ function getCachedGlob(pattern) {
1485
1519
  COMPILED_GLOB_CACHE.delete(expectDefined(keys[i]));
1486
1520
  }
1487
1521
  }
1488
- const re = compileGlob(pattern);
1522
+ let re;
1523
+ try {
1524
+ re = compileGlob(pattern);
1525
+ } catch {
1526
+ re = NEVER_MATCH;
1527
+ }
1489
1528
  COMPILED_GLOB_CACHE.set(pattern, re);
1490
1529
  return re;
1491
1530
  }
@@ -1733,11 +1772,11 @@ function validateAgainstSchema(value, schema) {
1733
1772
  walk(value, schema, "", errors);
1734
1773
  return { ok: errors.length === 0, errors };
1735
1774
  }
1736
- function walk(value, schema, path48, errors) {
1775
+ function walk(value, schema, path50, errors) {
1737
1776
  if (schema.enum !== void 0) {
1738
1777
  if (!schema.enum.some((e) => deepEqual(e, value))) {
1739
1778
  errors.push({
1740
- path: path48 || "<root>",
1779
+ path: path50 || "<root>",
1741
1780
  message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
1742
1781
  });
1743
1782
  return;
@@ -1746,7 +1785,7 @@ function walk(value, schema, path48, errors) {
1746
1785
  if (typeof schema.type === "string") {
1747
1786
  if (!checkType(value, schema.type)) {
1748
1787
  errors.push({
1749
- path: path48 || "<root>",
1788
+ path: path50 || "<root>",
1750
1789
  message: `expected ${schema.type}, got ${describeType(value)}`
1751
1790
  });
1752
1791
  return;
@@ -1756,20 +1795,20 @@ function walk(value, schema, path48, errors) {
1756
1795
  const obj = value;
1757
1796
  for (const req of schema.required ?? []) {
1758
1797
  if (!(req in obj)) {
1759
- errors.push({ path: joinPath(path48, req), message: "required property missing" });
1798
+ errors.push({ path: joinPath(path50, req), message: "required property missing" });
1760
1799
  }
1761
1800
  }
1762
1801
  if (schema.properties) {
1763
1802
  for (const [key, subSchema] of Object.entries(schema.properties)) {
1764
1803
  if (key in obj) {
1765
- walk(obj[key], subSchema, joinPath(path48, key), errors);
1804
+ walk(obj[key], subSchema, joinPath(path50, key), errors);
1766
1805
  }
1767
1806
  }
1768
1807
  }
1769
1808
  }
1770
1809
  if (schema.type === "array" && Array.isArray(value) && schema.items) {
1771
1810
  for (let i = 0; i < value.length; i++) {
1772
- walk(value[i], schema.items, `${path48}[${i}]`, errors);
1811
+ walk(value[i], schema.items, `${path50}[${i}]`, errors);
1773
1812
  }
1774
1813
  }
1775
1814
  }
@@ -2431,18 +2470,20 @@ var MODEL_FAMILY_RATIO = {
2431
2470
  deepseek: 3.5
2432
2471
  };
2433
2472
  var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
2473
+ var _estimateCacheOrder = [];
2434
2474
  var ESTIMATE_CACHE_MAX_SIZE = 5e4;
2435
2475
  function getCachedEstimate(key, compute) {
2436
2476
  const existing = ESTIMATE_CACHE.get(key);
2437
2477
  if (existing !== void 0) return existing;
2438
2478
  if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
2439
- for (const k of ESTIMATE_CACHE.keys()) {
2440
- if (ESTIMATE_CACHE.size <= Math.floor(ESTIMATE_CACHE_MAX_SIZE / 2)) break;
2441
- ESTIMATE_CACHE.delete(k);
2479
+ while (ESTIMATE_CACHE.size > Math.floor(ESTIMATE_CACHE_MAX_SIZE / 2)) {
2480
+ const oldest = _estimateCacheOrder.shift();
2481
+ if (oldest !== void 0) ESTIMATE_CACHE.delete(oldest);
2442
2482
  }
2443
2483
  }
2444
2484
  const estimate = compute(key);
2445
2485
  ESTIMATE_CACHE.set(key, estimate);
2486
+ _estimateCacheOrder.push(key);
2446
2487
  return estimate;
2447
2488
  }
2448
2489
  function estimateToolInputTokens(input) {
@@ -4463,11 +4504,88 @@ var ALGO = "aes-256-gcm";
4463
4504
  var KEY_FILE_MODE = 384;
4464
4505
  var KEY_FILE_MAGIC = Buffer.from("WSKV", "ascii");
4465
4506
  var VERSIONED_KEY_FILE_SIZE = KEY_FILE_MAGIC.length + 1 + KEY_BYTES;
4507
+ var KEK_MAGIC = Buffer.from("WSKW", "ascii");
4508
+ var KEK_SALT_BYTES = 16;
4509
+ var WRAPPED_KEY_FILE_SIZE = KEK_MAGIC.length + 1 + KEK_SALT_BYTES + IV_BYTES + TAG_BYTES + KEY_BYTES;
4510
+ var SCRYPT_N = 1 << 15;
4511
+ var SCRYPT_R = 8;
4512
+ var SCRYPT_P = 1;
4513
+ var SCRYPT_MAXMEM = 64 * 1024 * 1024;
4514
+ function getVaultPassphrase() {
4515
+ const v = process.env["WRONGSTACK_VAULT_PASSPHRASE"];
4516
+ return v && v.length > 0 ? v : void 0;
4517
+ }
4518
+ function isWrappedKeyFile(buf) {
4519
+ return buf.length === WRAPPED_KEY_FILE_SIZE && buf.subarray(0, KEK_MAGIC.length).equals(KEK_MAGIC);
4520
+ }
4521
+ function deriveKEK(passphrase, salt) {
4522
+ return scryptSync(passphrase, salt, KEY_BYTES, {
4523
+ N: SCRYPT_N,
4524
+ r: SCRYPT_R,
4525
+ p: SCRYPT_P,
4526
+ maxmem: SCRYPT_MAXMEM
4527
+ });
4528
+ }
4529
+ function wrapDataKey(dataKey, keyVersion, passphrase) {
4530
+ const salt = randomBytes(KEK_SALT_BYTES);
4531
+ const iv = randomBytes(IV_BYTES);
4532
+ const kek = deriveKEK(passphrase, salt);
4533
+ const cipher = createCipheriv(ALGO, kek, iv);
4534
+ const ct = Buffer.concat([cipher.update(dataKey), cipher.final()]);
4535
+ const tag = cipher.getAuthTag();
4536
+ const out = Buffer.alloc(WRAPPED_KEY_FILE_SIZE);
4537
+ let off = 0;
4538
+ KEK_MAGIC.copy(out, off);
4539
+ off += KEK_MAGIC.length;
4540
+ out[off] = keyVersion & 255;
4541
+ off += 1;
4542
+ salt.copy(out, off);
4543
+ off += KEK_SALT_BYTES;
4544
+ iv.copy(out, off);
4545
+ off += IV_BYTES;
4546
+ tag.copy(out, off);
4547
+ off += TAG_BYTES;
4548
+ ct.copy(out, off);
4549
+ return out;
4550
+ }
4551
+ function unwrapDataKey(buf, keyFile) {
4552
+ const passphrase = getVaultPassphrase();
4553
+ if (!passphrase) {
4554
+ throw new ConfigError({
4555
+ message: `SecretVault: key file ${keyFile} is passphrase-protected \u2014 set the WRONGSTACK_VAULT_PASSPHRASE environment variable to unlock it.`,
4556
+ code: ERROR_CODES.CONFIG_INVALID,
4557
+ context: { keyFile }
4558
+ });
4559
+ }
4560
+ let off = KEK_MAGIC.length;
4561
+ const version = buf[off];
4562
+ off += 1;
4563
+ const salt = buf.subarray(off, off + KEK_SALT_BYTES);
4564
+ off += KEK_SALT_BYTES;
4565
+ const iv = buf.subarray(off, off + IV_BYTES);
4566
+ off += IV_BYTES;
4567
+ const tag = buf.subarray(off, off + TAG_BYTES);
4568
+ off += TAG_BYTES;
4569
+ const ct = buf.subarray(off, off + KEY_BYTES);
4570
+ const kek = deriveKEK(passphrase, salt);
4571
+ const decipher = createDecipheriv(ALGO, kek, iv);
4572
+ decipher.setAuthTag(tag);
4573
+ try {
4574
+ const key = Buffer.concat([decipher.update(ct), decipher.final()]);
4575
+ return { key: Buffer.from(key), version };
4576
+ } catch {
4577
+ throw new ConfigError({
4578
+ message: `SecretVault: failed to unlock key file ${keyFile} \u2014 wrong WRONGSTACK_VAULT_PASSPHRASE (key unwrap authentication failed).`,
4579
+ code: ERROR_CODES.CONFIG_INVALID,
4580
+ context: { keyFile }
4581
+ });
4582
+ }
4583
+ }
4466
4584
  function checkKeyFilePermissions(keyFile) {
4467
4585
  if (process.platform === "win32") return;
4468
4586
  try {
4469
- const stat15 = fs3.statSync(keyFile);
4470
- const actualMode = stat15.mode & 511;
4587
+ const stat16 = fs3.statSync(keyFile);
4588
+ const actualMode = stat16.mode & 511;
4471
4589
  if (actualMode !== KEY_FILE_MODE) {
4472
4590
  console.warn(JSON.stringify({
4473
4591
  level: "warn",
@@ -4555,25 +4673,56 @@ var DefaultSecretVault = class {
4555
4673
  const oldVersion = this._keyVersion;
4556
4674
  const newKey = randomBytes(KEY_BYTES);
4557
4675
  const newVersion = oldVersion + 1;
4558
- const keyFileBuf = Buffer.alloc(VERSIONED_KEY_FILE_SIZE);
4559
- KEY_FILE_MAGIC.copy(keyFileBuf, 0);
4560
- keyFileBuf[KEY_FILE_MAGIC.length] = newVersion;
4561
- newKey.copy(keyFileBuf, KEY_FILE_MAGIC.length + 1);
4562
4676
  fs3.mkdirSync(path3.dirname(this.keyFile), { recursive: true });
4563
- fs3.writeFileSync(this.keyFile, keyFileBuf, { mode: 384 });
4677
+ const passphrase = getVaultPassphrase();
4678
+ if (passphrase) {
4679
+ fs3.writeFileSync(this.keyFile, wrapDataKey(newKey, newVersion, passphrase), { mode: 384 });
4680
+ } else {
4681
+ const keyFileBuf = Buffer.alloc(VERSIONED_KEY_FILE_SIZE);
4682
+ KEY_FILE_MAGIC.copy(keyFileBuf, 0);
4683
+ keyFileBuf[KEY_FILE_MAGIC.length] = newVersion;
4684
+ newKey.copy(keyFileBuf, KEY_FILE_MAGIC.length + 1);
4685
+ fs3.writeFileSync(this.keyFile, keyFileBuf, { mode: 384 });
4686
+ }
4564
4687
  checkKeyFilePermissions(this.keyFile);
4565
4688
  this.key = newKey;
4566
4689
  this._keyVersion = newVersion;
4567
4690
  return { oldVersion, newVersion };
4568
4691
  }
4692
+ /**
4693
+ * If WRONGSTACK_VAULT_PASSPHRASE is set but the key on disk is still stored
4694
+ * unwrapped (legacy v1 / versioned v2), re-write it in passphrase-wrapped (v3)
4695
+ * form. The data key is preserved, so all existing ciphertext keeps
4696
+ * decrypting. Best-effort: a write failure leaves the working unwrapped file
4697
+ * in place and is not fatal to load.
4698
+ */
4699
+ migrateToWrappedIfPassphrase() {
4700
+ const passphrase = getVaultPassphrase();
4701
+ if (!passphrase || !this.key) return;
4702
+ try {
4703
+ fs3.writeFileSync(this.keyFile, wrapDataKey(this.key, this._keyVersion, passphrase), {
4704
+ mode: 384
4705
+ });
4706
+ checkKeyFilePermissions(this.keyFile);
4707
+ } catch {
4708
+ }
4709
+ }
4569
4710
  loadOrCreateKey() {
4570
4711
  if (this.key) return this.key;
4571
4712
  try {
4572
4713
  const buf = fs3.readFileSync(this.keyFile);
4714
+ if (isWrappedKeyFile(buf)) {
4715
+ const { key: key2, version } = unwrapDataKey(buf, this.keyFile);
4716
+ this.key = key2;
4717
+ this._keyVersion = version;
4718
+ checkKeyFilePermissions(this.keyFile);
4719
+ return this.key;
4720
+ }
4573
4721
  if (buf.length === KEY_BYTES) {
4574
4722
  this.key = buf;
4575
4723
  this._keyVersion = 1;
4576
4724
  checkKeyFilePermissions(this.keyFile);
4725
+ this.migrateToWrappedIfPassphrase();
4577
4726
  return this.key;
4578
4727
  }
4579
4728
  if (buf.length === VERSIONED_KEY_FILE_SIZE) {
@@ -4597,6 +4746,7 @@ var DefaultSecretVault = class {
4597
4746
  this.key = Buffer.from(key2);
4598
4747
  this._keyVersion = version;
4599
4748
  checkKeyFilePermissions(this.keyFile);
4749
+ this.migrateToWrappedIfPassphrase();
4600
4750
  return this.key;
4601
4751
  }
4602
4752
  throw new ConfigError({
@@ -4609,11 +4759,20 @@ var DefaultSecretVault = class {
4609
4759
  }
4610
4760
  fs3.mkdirSync(path3.dirname(this.keyFile), { recursive: true });
4611
4761
  const key = randomBytes(KEY_BYTES);
4762
+ const passphrase = getVaultPassphrase();
4763
+ const initialBytes = passphrase ? wrapDataKey(key, 1, passphrase) : key;
4612
4764
  try {
4613
- fs3.writeFileSync(this.keyFile, key, { mode: 384, flag: "wx" });
4765
+ fs3.writeFileSync(this.keyFile, initialBytes, { mode: 384, flag: "wx" });
4614
4766
  } catch (err) {
4615
4767
  if (err.code !== "EEXIST") throw err;
4616
4768
  const buf = fs3.readFileSync(this.keyFile);
4769
+ if (isWrappedKeyFile(buf)) {
4770
+ const { key: winnerKey, version } = unwrapDataKey(buf, this.keyFile);
4771
+ this.key = winnerKey;
4772
+ this._keyVersion = version;
4773
+ checkKeyFilePermissions(this.keyFile);
4774
+ return this.key;
4775
+ }
4617
4776
  if (buf.length === KEY_BYTES) {
4618
4777
  this.key = buf;
4619
4778
  this._keyVersion = 1;
@@ -5217,17 +5376,9 @@ function findPreserveStart(messages, preserveK) {
5217
5376
  const prev = messages[preserveStart - 1];
5218
5377
  if (!first || !prev || first.role !== "user" || prev.role !== "assistant") break;
5219
5378
  if (typeof first.content === "string" || typeof prev.content === "string") break;
5220
- const resultIds = /* @__PURE__ */ new Set();
5221
- for (const block of first.content) {
5222
- pairRepairInnerIterations++;
5223
- if (block.type === "tool_result") resultIds.add(block.tool_use_id);
5224
- }
5225
- if (resultIds.size === 0) break;
5226
- const hasMatchingUse = prev.content.some((block) => {
5227
- pairRepairInnerIterations++;
5228
- return block.type === "tool_use" && resultIds.has(block.id);
5229
- });
5230
- if (!hasMatchingUse) break;
5379
+ const pairCheck = hasMatchingToolPair(first.content, prev.content);
5380
+ pairRepairInnerIterations += pairCheck.iterations;
5381
+ if (!pairCheck.matched) break;
5231
5382
  preserveStart--;
5232
5383
  }
5233
5384
  if (compactionDebugEnabled()) {
@@ -5246,9 +5397,34 @@ function findPreserveStart(messages, preserveK) {
5246
5397
  }
5247
5398
  return preserveStart;
5248
5399
  }
5400
+ function hasMatchingToolPair(resultContent, useContent) {
5401
+ let iterations = 0;
5402
+ let firstResultId;
5403
+ let resultIds;
5404
+ for (const block of resultContent) {
5405
+ iterations++;
5406
+ if (block.type !== "tool_result") continue;
5407
+ if (firstResultId === void 0) {
5408
+ firstResultId = block.tool_use_id;
5409
+ } else {
5410
+ resultIds ??= /* @__PURE__ */ new Set([firstResultId]);
5411
+ resultIds.add(block.tool_use_id);
5412
+ }
5413
+ }
5414
+ if (firstResultId === void 0) return { matched: false, iterations };
5415
+ for (const block of useContent) {
5416
+ iterations++;
5417
+ if (block.type !== "tool_use") continue;
5418
+ if (resultIds ? resultIds.has(block.id) : block.id === firstResultId) {
5419
+ return { matched: true, iterations };
5420
+ }
5421
+ }
5422
+ return { matched: false, iterations };
5423
+ }
5249
5424
  function eliseOldToolResults(messages, opts) {
5250
5425
  const preserveStart = findPreserveStart(messages, opts.preserveK);
5251
5426
  let hasOversized = false;
5427
+ let firstOversizedIndex = -1;
5252
5428
  let fastPathIterations = 0;
5253
5429
  let fastPathInnerIterations = 0;
5254
5430
  for (let i = 0; i < preserveStart && !hasOversized; i++) {
@@ -5260,6 +5436,7 @@ function eliseOldToolResults(messages, opts) {
5260
5436
  const oversized = b.type === "tool_result" && estimateToolResultTokens(b.content) >= opts.eliseThreshold || b.type === "tool_use" && estimateToolInputTokens(b.input) >= opts.eliseThreshold;
5261
5437
  if (oversized) {
5262
5438
  hasOversized = true;
5439
+ firstOversizedIndex = i;
5263
5440
  break;
5264
5441
  }
5265
5442
  }
@@ -5282,26 +5459,29 @@ function eliseOldToolResults(messages, opts) {
5282
5459
  let changed = false;
5283
5460
  let fullPassIterations = 0;
5284
5461
  let fullPassInnerIterations = 0;
5285
- const next = new Array(messages.length);
5286
- for (let i = 0; i < messages.length; i++) {
5462
+ let next;
5463
+ for (let i = firstOversizedIndex; i < preserveStart; i++) {
5287
5464
  fullPassIterations++;
5288
5465
  const msg = messages[i];
5289
- if (i >= preserveStart || !msg || !Array.isArray(msg.content)) {
5290
- next[i] = msg;
5291
- continue;
5292
- }
5466
+ if (!msg || !Array.isArray(msg.content)) continue;
5293
5467
  const original = msg.content;
5294
- const newContent = original.map((b) => {
5468
+ let newContent;
5469
+ for (let idx = 0; idx < original.length; idx++) {
5470
+ fullPassInnerIterations++;
5471
+ const b = original[idx];
5472
+ if (!b) continue;
5295
5473
  if (b.type === "tool_use") {
5296
5474
  const tokens2 = estimateToolInputTokens(b.input);
5297
- if (tokens2 < opts.eliseThreshold) return b;
5475
+ if (tokens2 < opts.eliseThreshold) continue;
5298
5476
  const elidedInput = summarizeToolUseInputElision(b, tokens2);
5299
5477
  saved += Math.max(0, tokens2 - estimateToolInputTokens(elidedInput));
5300
- return { ...b, input: elidedInput };
5478
+ newContent ??= original.slice();
5479
+ newContent[idx] = { ...b, input: elidedInput };
5480
+ continue;
5301
5481
  }
5302
- if (b.type !== "tool_result") return b;
5482
+ if (b.type !== "tool_result") continue;
5303
5483
  const tokens = estimateToolResultTokens(b.content);
5304
- if (tokens < opts.eliseThreshold) return b;
5484
+ if (tokens < opts.eliseThreshold) continue;
5305
5485
  saved += tokens;
5306
5486
  const elided = {
5307
5487
  type: "tool_result",
@@ -5309,15 +5489,14 @@ function eliseOldToolResults(messages, opts) {
5309
5489
  content: summarizeToolResultElision(b, tokens),
5310
5490
  is_error: b.is_error
5311
5491
  };
5312
- return elided;
5313
- });
5314
- if (newContent.every((b, idx) => b === original[idx])) {
5315
- next[i] = msg;
5316
- } else {
5492
+ newContent ??= original.slice();
5493
+ newContent[idx] = elided;
5494
+ }
5495
+ if (newContent) {
5496
+ next ??= messages.slice();
5317
5497
  next[i] = { ...msg, content: newContent };
5318
5498
  changed = true;
5319
5499
  }
5320
- fullPassInnerIterations += original.length;
5321
5500
  if (compactionDebugEnabled()) {
5322
5501
  const ratio = fullPassInnerIterations / fullPassIterations;
5323
5502
  if (ratio > 10) {
@@ -5344,7 +5523,7 @@ function eliseOldToolResults(messages, opts) {
5344
5523
  tokensSaved: saved,
5345
5524
  changed
5346
5525
  });
5347
- return { messages: changed ? next : messages, saved, changed };
5526
+ return { messages: changed && next ? next : messages, saved, changed };
5348
5527
  }
5349
5528
  function summarizeToolUseInputElision(block, tokens) {
5350
5529
  const fields = {};
@@ -6096,6 +6275,7 @@ var DefaultSecretScrubber = class {
6096
6275
  // src/models/models-registry.ts
6097
6276
  init_atomic_write();
6098
6277
  var DEFAULT_URL = "https://models.dev/api.json";
6278
+ var ENV_URL_KEY = "WRONGSTACK_MODELS_DEV_URL";
6099
6279
  var DEFAULT_TTL_SECONDS = 24 * 3600;
6100
6280
  var DEFAULT_REFRESH_TIMEOUT_MS = 15e3;
6101
6281
  var FAMILY_BY_NPM = {
@@ -6141,7 +6321,7 @@ var DefaultModelsRegistry = class {
6141
6321
  overlayCacheFile;
6142
6322
  constructor(opts) {
6143
6323
  this.cacheFile = opts.cacheFile;
6144
- this.url = opts.url ?? DEFAULT_URL;
6324
+ this.url = opts.url ?? process.env[ENV_URL_KEY] ?? DEFAULT_URL;
6145
6325
  this.ttlMs = (opts.ttlSeconds ?? DEFAULT_TTL_SECONDS) * 1e3;
6146
6326
  this.fetchImpl = opts.fetchImpl ?? fetch;
6147
6327
  this.seed = opts.seed;
@@ -6186,6 +6366,10 @@ var DefaultModelsRegistry = class {
6186
6366
  const cached = await this.readCacheAt(this.cacheFile);
6187
6367
  if (cached && this.isWithinMaxStaleAge(cached.fetchedAt)) {
6188
6368
  this.fetchedAt = new Date(cached.fetchedAt);
6369
+ const ageSeconds = Math.floor((Date.now() - this.fetchedAt.getTime()) / 1e3);
6370
+ console.warn(
6371
+ `ModelsRegistry: models.dev unavailable (${toErrorMessage(err)}); using stale cache from ${formatAge(ageSeconds)} ago. Run \`wstack models refresh\` to retry.`
6372
+ );
6189
6373
  return cached.payload;
6190
6374
  }
6191
6375
  if (overlayAvailable) {
@@ -6271,7 +6455,13 @@ var DefaultModelsRegistry = class {
6271
6455
  return json;
6272
6456
  } catch {
6273
6457
  const cached = await this.readCacheAt(this.overlayCacheFile);
6274
- if (cached && this.isWithinMaxStaleAge(cached.fetchedAt)) return cached.payload;
6458
+ if (cached && this.isWithinMaxStaleAge(cached.fetchedAt)) {
6459
+ const ageSeconds = Math.floor((Date.now() - new Date(cached.fetchedAt).getTime()) / 1e3);
6460
+ console.warn(
6461
+ `ModelsRegistry: overlay unavailable; using stale overlay from ${formatAge(ageSeconds)} ago.`
6462
+ );
6463
+ return cached.payload;
6464
+ }
6275
6465
  return void 0;
6276
6466
  }
6277
6467
  }
@@ -6368,6 +6558,16 @@ var DefaultModelsRegistry = class {
6368
6558
  return path3.resolve(this.cacheFile);
6369
6559
  }
6370
6560
  };
6561
+ function formatAge(seconds) {
6562
+ if (seconds < 60) return "<1m";
6563
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
6564
+ if (seconds < 86400) {
6565
+ const h = Math.floor(seconds / 3600);
6566
+ const m = Math.floor(seconds % 3600 / 60);
6567
+ return m > 0 ? `${h}h ${m}m` : `${h}h`;
6568
+ }
6569
+ return `${Math.floor(seconds / 86400)}d`;
6570
+ }
6371
6571
  function hasEntries(payload) {
6372
6572
  return payload !== void 0 && Object.keys(payload).length > 0;
6373
6573
  }
@@ -8176,6 +8376,8 @@ function generateSessionId(startedAt, model) {
8176
8376
  const modelPart = model ? `_${sanitizeModel(model)}` : "";
8177
8377
  return `${date}/${time}Z${modelPart}_${suffix}`;
8178
8378
  }
8379
+
8380
+ // src/storage/session-store.ts
8179
8381
  var DefaultSessionStore = class _DefaultSessionStore {
8180
8382
  dir;
8181
8383
  events;
@@ -8193,6 +8395,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
8193
8395
  _loadCache = /* @__PURE__ */ new Map();
8194
8396
  _indexCache = null;
8195
8397
  static LOAD_CACHE_MAX_ENTRIES = 50;
8398
+ static LIST_SCAN_CONCURRENCY = 32;
8196
8399
  constructor(opts) {
8197
8400
  this.dir = opts.dir;
8198
8401
  this.events = opts.events;
@@ -8354,9 +8557,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
8354
8557
  let cacheHit = false;
8355
8558
  try {
8356
8559
  const s = await fsp3.stat(file);
8357
- const stat15 = { mtimeMs: s.mtimeMs, size: s.size };
8560
+ const stat16 = { mtimeMs: s.mtimeMs, size: s.size };
8358
8561
  const cached = this._loadCache.get(id);
8359
- if (cached && cached.mtimeMs === stat15.mtimeMs && cached.size === stat15.size) {
8562
+ if (cached && cached.mtimeMs === stat16.mtimeMs && cached.size === stat16.size) {
8360
8563
  cacheHit = true;
8361
8564
  this._loadCache.delete(id);
8362
8565
  this._loadCache.set(id, cached);
@@ -8384,7 +8587,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
8384
8587
  this._loadCache.delete(oldest);
8385
8588
  }
8386
8589
  }
8387
- this._loadCache.set(id, { mtimeMs: stat15.mtimeMs, size: stat15.size, data });
8590
+ this._loadCache.set(id, { mtimeMs: stat16.mtimeMs, size: stat16.size, data });
8388
8591
  return data;
8389
8592
  } catch (err) {
8390
8593
  outcome = "failure";
@@ -8415,15 +8618,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
8415
8618
  });
8416
8619
  return indexed.slice(0, limit);
8417
8620
  }
8418
- const ids = await this.collectSessionIds(this.dir);
8419
- const sessions = await Promise.all(ids.map((id) => this.summaryFor(id).catch(() => null)));
8420
- const out = sessions.filter((s) => s !== null);
8421
- out.sort((a, b) => {
8422
- if (a.startedAt < b.startedAt) return 1;
8423
- if (a.startedAt > b.startedAt) return -1;
8424
- return a.id.localeCompare(b.id);
8425
- });
8426
- return out.slice(0, limit);
8621
+ return await this.listFromDirectoryScan(limit);
8427
8622
  } catch {
8428
8623
  return [];
8429
8624
  }
@@ -8494,15 +8689,15 @@ var DefaultSessionStore = class _DefaultSessionStore {
8494
8689
  * Returns empty array when the index doesn't exist or is corrupt.
8495
8690
  */
8496
8691
  async readIndex() {
8497
- let stat15;
8692
+ let stat16;
8498
8693
  try {
8499
8694
  const s = await fsp3.stat(this.indexFile);
8500
- stat15 = { mtimeMs: s.mtimeMs, size: s.size };
8695
+ stat16 = { mtimeMs: s.mtimeMs, size: s.size };
8501
8696
  } catch {
8502
8697
  this._indexCache = null;
8503
8698
  return [];
8504
8699
  }
8505
- if (this._indexCache !== null && this._indexCache.mtimeMs === stat15.mtimeMs && this._indexCache.size === stat15.size) {
8700
+ if (this._indexCache !== null && this._indexCache.mtimeMs === stat16.mtimeMs && this._indexCache.size === stat16.size) {
8506
8701
  return [...this._indexCache.summaries];
8507
8702
  }
8508
8703
  let raw;
@@ -8530,7 +8725,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
8530
8725
  }
8531
8726
  }
8532
8727
  const summaries = Array.from(seen.values());
8533
- this._indexCache = { ...stat15, summaries };
8728
+ this._indexCache = { ...stat16, summaries };
8534
8729
  return [...summaries];
8535
8730
  }
8536
8731
  /**
@@ -8548,46 +8743,105 @@ var DefaultSessionStore = class _DefaultSessionStore {
8548
8743
  this._indexCache = null;
8549
8744
  return valid.length;
8550
8745
  }
8746
+ async listFromDirectoryScan(limit) {
8747
+ const refs = await this.collectSessionFiles(this.dir);
8748
+ const candidates = await mapWithConcurrency(
8749
+ refs,
8750
+ _DefaultSessionStore.LIST_SCAN_CONCURRENCY,
8751
+ async (ref) => {
8752
+ const manifest = await this.readSummaryManifest(ref.id);
8753
+ if (manifest) return { summary: manifest, needsBackfill: false };
8754
+ const summary = await this.summaryHeaderFor(ref);
8755
+ return summary ? { summary, needsBackfill: true } : null;
8756
+ }
8757
+ );
8758
+ const out = candidates.filter((s) => s !== null);
8759
+ out.sort((a, b) => compareSessionSummaries(a.summary, b.summary));
8760
+ const selected = out.slice(0, limit);
8761
+ const summaries = await mapWithConcurrency(
8762
+ selected,
8763
+ Math.min(_DefaultSessionStore.LIST_SCAN_CONCURRENCY, Math.max(1, limit)),
8764
+ async (candidate) => {
8765
+ if (!candidate.needsBackfill) return candidate.summary;
8766
+ return await this.summaryFor(candidate.summary.id).catch(() => candidate.summary);
8767
+ }
8768
+ );
8769
+ return summaries.filter((s) => s !== null);
8770
+ }
8771
+ async collectSessionFiles(dir, prefix = "", depth = 0) {
8772
+ let entries;
8773
+ try {
8774
+ entries = await fsp3.readdir(dir, { withFileTypes: true });
8775
+ } catch {
8776
+ return [];
8777
+ }
8778
+ const dirEntries = [];
8779
+ const files = [];
8780
+ for (const entry of entries) {
8781
+ if (entry.name.startsWith(".") && entry.name !== ".wrongstack") continue;
8782
+ if (entry.name === "shared" || entry.name === "subagents" || entry.name === "attachments")
8783
+ continue;
8784
+ if (entry.isDirectory()) {
8785
+ dirEntries.push(entry);
8786
+ } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
8787
+ if (entry.name === "_index.jsonl") continue;
8788
+ const base = entry.name.replace(/\.jsonl$/, "");
8789
+ const id = prefix ? `${prefix}/${base}` : base;
8790
+ files.push({ id, filePath: path3.join(dir, entry.name) });
8791
+ }
8792
+ }
8793
+ const childFileArrays = await Promise.all(
8794
+ dirEntries.map((entry) => {
8795
+ const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
8796
+ return this.collectSessionFiles(path3.join(dir, entry.name), childPrefix, depth + 1);
8797
+ })
8798
+ );
8799
+ return [...childFileArrays.flat(), ...files];
8800
+ }
8551
8801
  /** Recursively collect session IDs from date-shard subdirectories.
8552
8802
  * IDs include the date-prefix path (e.g. "2026-06-06/17-46-57Z_…").
8553
8803
  * Skips `.jsonl`/`.summary.json` root files, dot-files, and
8554
8804
  * sub-directories that belong to fleet/subagent sessions. */
8555
8805
  async collectSessionIds(dir, prefix = "", depth = 0) {
8556
- const ids = [];
8557
8806
  let entries;
8558
8807
  try {
8559
8808
  entries = await fsp3.readdir(dir, { withFileTypes: true });
8560
8809
  } catch {
8561
- return ids;
8810
+ return [];
8562
8811
  }
8812
+ const dirEntries = [];
8813
+ const fileIds = [];
8563
8814
  for (const entry of entries) {
8564
8815
  if (entry.name.startsWith(".") && entry.name !== ".wrongstack") continue;
8565
8816
  if (entry.name === "shared" || entry.name === "subagents" || entry.name === "attachments")
8566
8817
  continue;
8567
8818
  if (entry.isDirectory()) {
8568
- const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
8569
- ids.push(...await this.collectSessionIds(path3.join(dir, entry.name), childPrefix, depth + 1));
8819
+ dirEntries.push(entry);
8570
8820
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
8571
8821
  if (entry.name === "_index.jsonl") continue;
8572
8822
  const base = entry.name.replace(/\.jsonl$/, "");
8573
- ids.push(prefix ? `${prefix}/${base}` : base);
8823
+ fileIds.push(prefix ? `${prefix}/${base}` : base);
8574
8824
  }
8575
8825
  }
8576
- return ids;
8826
+ const childIdArrays = await Promise.all(
8827
+ dirEntries.map((entry) => {
8828
+ const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
8829
+ return this.collectSessionIds(path3.join(dir, entry.name), childPrefix, depth + 1);
8830
+ })
8831
+ );
8832
+ return [...childIdArrays.flat(), ...fileIds];
8577
8833
  }
8578
8834
  async summaryFor(id) {
8579
8835
  const manifest = this.sessionPath(id, ".summary.json");
8580
8836
  const t0 = Date.now();
8581
8837
  let outcome = "success";
8582
8838
  let errorMsg;
8839
+ const fromManifest = await this.readSummaryManifest(id, t0);
8840
+ if (fromManifest) return fromManifest;
8583
8841
  try {
8584
- const raw = await fsp3.readFile(manifest, "utf8");
8585
- this.emitRead(id, manifest, "summary", "success", Date.now() - t0);
8586
- return JSON.parse(raw);
8587
- } catch {
8588
8842
  const full = this.sessionPath(id, ".jsonl");
8589
- const stat15 = await fsp3.stat(full);
8590
- const summary = await this.summarize(id, stat15.mtime.toISOString());
8843
+ const stat16 = await fsp3.stat(full);
8844
+ const summary = await this.summarize(id, stat16.mtime.toISOString());
8591
8845
  await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
8592
8846
  const msg = toErrorMessage(err);
8593
8847
  this.emitError(id, manifest, "summary_fallback", msg, true);
@@ -8603,6 +8857,78 @@ var DefaultSessionStore = class _DefaultSessionStore {
8603
8857
  errorMsg = "summary fallback \u2014 manifest rebuilt";
8604
8858
  this.emitRead(id, manifest, "summary", outcome, Date.now() - t0, errorMsg);
8605
8859
  return summary;
8860
+ } catch (err) {
8861
+ outcome = "failure";
8862
+ errorMsg = toErrorMessage(err);
8863
+ this.emitRead(id, manifest, "summary", outcome, Date.now() - t0, errorMsg);
8864
+ return {
8865
+ id,
8866
+ title: "(damaged)",
8867
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
8868
+ model: "unknown",
8869
+ provider: "unknown",
8870
+ tokenTotal: 0
8871
+ };
8872
+ }
8873
+ }
8874
+ async readSummaryManifest(id, startTime = Date.now()) {
8875
+ const manifest = this.sessionPath(id, ".summary.json");
8876
+ try {
8877
+ const raw = await fsp3.readFile(manifest, "utf8");
8878
+ this.emitRead(id, manifest, "summary", "success", Date.now() - startTime);
8879
+ return JSON.parse(raw);
8880
+ } catch {
8881
+ return null;
8882
+ }
8883
+ }
8884
+ async summaryHeaderFor(ref) {
8885
+ let mtime = (/* @__PURE__ */ new Date(0)).toISOString();
8886
+ try {
8887
+ const stat16 = await fsp3.stat(ref.filePath);
8888
+ if (!stat16.isFile()) {
8889
+ return {
8890
+ id: ref.id,
8891
+ title: "(damaged)",
8892
+ startedAt: stat16.mtime.toISOString(),
8893
+ model: "unknown",
8894
+ provider: "unknown",
8895
+ tokenTotal: 0
8896
+ };
8897
+ }
8898
+ mtime = stat16.mtime.toISOString();
8899
+ } catch {
8900
+ return null;
8901
+ }
8902
+ try {
8903
+ for await (const event of this.iterSessionEvents(ref.filePath)) {
8904
+ if (event.type === "session_start") {
8905
+ return {
8906
+ id: ref.id,
8907
+ title: "(empty session)",
8908
+ startedAt: event.ts,
8909
+ model: event.model ?? "unknown",
8910
+ provider: event.provider ?? "unknown",
8911
+ tokenTotal: 0
8912
+ };
8913
+ }
8914
+ }
8915
+ return {
8916
+ id: ref.id,
8917
+ title: "(empty session)",
8918
+ startedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
8919
+ model: "unknown",
8920
+ provider: "unknown",
8921
+ tokenTotal: 0
8922
+ };
8923
+ } catch {
8924
+ return {
8925
+ id: ref.id,
8926
+ title: "(damaged)",
8927
+ startedAt: mtime,
8928
+ model: "unknown",
8929
+ provider: "unknown",
8930
+ tokenTotal: 0
8931
+ };
8606
8932
  }
8607
8933
  }
8608
8934
  /**
@@ -8669,8 +8995,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
8669
8995
  const pruneFile = async (dir, name, prefix) => {
8670
8996
  const jsonlPath = path3.join(dir, name);
8671
8997
  try {
8672
- const stat15 = await fsp3.stat(jsonlPath);
8673
- if (stat15.mtimeMs >= cutoff) return;
8998
+ const stat16 = await fsp3.stat(jsonlPath);
8999
+ if (stat16.mtimeMs >= cutoff) return;
8674
9000
  } catch {
8675
9001
  return;
8676
9002
  }
@@ -8727,39 +9053,62 @@ var DefaultSessionStore = class _DefaultSessionStore {
8727
9053
  }
8728
9054
  async summarize(id, mtime) {
8729
9055
  try {
8730
- const data = await this.load(id);
8731
- const firstUser = data.events.find((e) => e.type === "user_input");
8732
- const title = firstUser && firstUser.type === "user_input" ? userInputTitle(firstUser.content) : "(empty session)";
9056
+ const file = this.sessionPath(id, ".jsonl");
9057
+ let title = "(empty session)";
9058
+ let startedAt = (/* @__PURE__ */ new Date(0)).toISOString();
9059
+ let endedAt;
9060
+ let model = "unknown";
9061
+ let provider = "unknown";
9062
+ let tokenIn = 0;
9063
+ let tokenOut = 0;
8733
9064
  let iterationCount = 0;
8734
9065
  let toolCallCount = 0;
8735
9066
  let toolErrorCount = 0;
8736
9067
  let fileChangeCount = 0;
8737
9068
  const toolBreakdown = {};
8738
9069
  let outcome;
8739
- const lastEvent = data.events[data.events.length - 1];
8740
- for (const e of data.events) {
8741
- if (e.type === "in_flight_start") iterationCount++;
9070
+ let lastEventType;
9071
+ let hasError = false;
9072
+ let sawStart = false;
9073
+ for await (const e of this.iterSessionEvents(file)) {
9074
+ lastEventType = e.type;
9075
+ if (e.type === "session_start") {
9076
+ if (!sawStart) {
9077
+ sawStart = true;
9078
+ startedAt = e.ts;
9079
+ model = e.model ?? "unknown";
9080
+ provider = e.provider ?? "unknown";
9081
+ }
9082
+ } else if (e.type === "session_end") {
9083
+ endedAt = e.ts;
9084
+ } else if (e.type === "user_input") {
9085
+ if (title === "(empty session)") title = userInputTitle(e.content);
9086
+ } else if (e.type === "llm_response") {
9087
+ tokenIn += e.usage.input ?? 0;
9088
+ tokenOut += e.usage.output ?? 0;
9089
+ } else if (e.type === "in_flight_start") iterationCount++;
8742
9090
  else if (e.type === "tool_call_start") {
8743
9091
  toolCallCount++;
8744
9092
  toolBreakdown[e.name] = (toolBreakdown[e.name] ?? 0) + 1;
8745
9093
  } else if (e.type === "tool_result" && e.isError) toolErrorCount++;
8746
9094
  else if (e.type === "file_snapshot") fileChangeCount += e.files.length;
9095
+ else if (e.type === "error" || e.type === "provider_error") hasError = true;
8747
9096
  }
8748
- if (lastEvent?.type === "session_end") {
9097
+ if (lastEventType === "session_end") {
8749
9098
  outcome = "completed";
8750
- } else if (lastEvent?.type === "in_flight_start") {
9099
+ } else if (lastEventType === "in_flight_start") {
8751
9100
  outcome = "aborted";
8752
- } else if (data.events.some((e) => e.type === "error")) {
9101
+ } else if (hasError) {
8753
9102
  outcome = "error";
8754
9103
  }
8755
9104
  return {
8756
9105
  id,
8757
9106
  title,
8758
- startedAt: data.metadata.startedAt,
8759
- endedAt: data.metadata.endedAt,
8760
- model: data.metadata.model ?? "unknown",
8761
- provider: data.metadata.provider ?? "unknown",
8762
- tokenTotal: data.usage.input + data.usage.output,
9107
+ startedAt,
9108
+ endedAt,
9109
+ model,
9110
+ provider,
9111
+ tokenTotal: tokenIn + tokenOut,
8763
9112
  iterationCount: iterationCount > 0 ? iterationCount : void 0,
8764
9113
  toolCallCount: toolCallCount > 0 ? toolCallCount : void 0,
8765
9114
  toolErrorCount: toolErrorCount > 0 ? toolErrorCount : void 0,
@@ -8778,6 +9127,25 @@ var DefaultSessionStore = class _DefaultSessionStore {
8778
9127
  };
8779
9128
  }
8780
9129
  }
9130
+ async *iterSessionEvents(file) {
9131
+ const stream = createReadStream(file, { encoding: "utf8" });
9132
+ const lines = createInterface({ input: stream, crlfDelay: Infinity });
9133
+ try {
9134
+ for await (const line of lines) {
9135
+ if (!line.trim()) continue;
9136
+ try {
9137
+ const parsed = JSON.parse(line);
9138
+ if (parsed !== null && typeof parsed === "object" && typeof parsed.type === "string" && typeof parsed.ts === "string") {
9139
+ yield parsed;
9140
+ }
9141
+ } catch {
9142
+ }
9143
+ }
9144
+ } finally {
9145
+ lines.close();
9146
+ stream.destroy();
9147
+ }
9148
+ }
8781
9149
  metaFromEvents(id, events) {
8782
9150
  const start = events.find((e) => e.type === "session_start");
8783
9151
  const end = events.findLast((e) => e.type === "session_end");
@@ -9441,6 +9809,27 @@ function userInputTitle(content) {
9441
9809
  const text = typeof content === "string" ? content : content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
9442
9810
  return (text || "(non-text input)").slice(0, 60);
9443
9811
  }
9812
+ function compareSessionSummaries(a, b) {
9813
+ if (a.startedAt < b.startedAt) return 1;
9814
+ if (a.startedAt > b.startedAt) return -1;
9815
+ return a.id.localeCompare(b.id);
9816
+ }
9817
+ async function mapWithConcurrency(items, concurrency, fn) {
9818
+ if (items.length === 0) return [];
9819
+ const out = new Array(items.length);
9820
+ let next = 0;
9821
+ const workerCount = Math.min(Math.max(1, concurrency), items.length);
9822
+ const workers = Array.from({ length: workerCount }, async () => {
9823
+ for (; ; ) {
9824
+ const idx = next++;
9825
+ if (idx >= items.length) return;
9826
+ const item = items[idx];
9827
+ if (item !== void 0) out[idx] = await fn(item);
9828
+ }
9829
+ });
9830
+ await Promise.all(workers);
9831
+ return out;
9832
+ }
9444
9833
 
9445
9834
  // src/storage/queue-store.ts
9446
9835
  init_atomic_write();
@@ -10470,8 +10859,8 @@ ${body.trim()}`);
10470
10859
  if (!this.persistBackup || scope === "project-agents") return;
10471
10860
  try {
10472
10861
  const content = await this.backend.readAll(scope, this.files[scope]);
10473
- const { writeFile: writeFile14, mkdir: mkdir23 } = await import('fs/promises');
10474
- await mkdir23(this.backupDir, { recursive: true });
10862
+ const { writeFile: writeFile14, mkdir: mkdir24 } = await import('fs/promises');
10863
+ await mkdir24(this.backupDir, { recursive: true });
10475
10864
  await writeFile14(`${this.backupDir}/${scope}.md`, content, "utf8");
10476
10865
  } catch {
10477
10866
  }
@@ -10657,6 +11046,42 @@ var defaultIndexing = {
10657
11046
  watchExternal: true,
10658
11047
  debounceMs: 400
10659
11048
  };
11049
+ var IN_PROJECT_FORBIDDEN_KEYS = /* @__PURE__ */ new Set([
11050
+ "provider",
11051
+ "apiKey",
11052
+ "baseUrl",
11053
+ "providers",
11054
+ "mcpServers",
11055
+ "hooks",
11056
+ "plugins",
11057
+ "sync",
11058
+ "yolo",
11059
+ "extensions"
11060
+ ]);
11061
+ function stripUnsafeInProjectFields(inProject, sourcePath, warn = (msg) => console.warn(msg)) {
11062
+ const stripped = [];
11063
+ const out = {};
11064
+ for (const [k, v] of Object.entries(inProject)) {
11065
+ if (IN_PROJECT_FORBIDDEN_KEYS.has(k)) {
11066
+ stripped.push(k);
11067
+ continue;
11068
+ }
11069
+ out[k] = v;
11070
+ }
11071
+ if (stripped.length > 0) {
11072
+ warn(
11073
+ JSON.stringify({
11074
+ level: "warn",
11075
+ event: "config.in_project_unsafe_fields_ignored",
11076
+ path: sourcePath,
11077
+ ignoredKeys: stripped,
11078
+ 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.`,
11079
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
11080
+ })
11081
+ );
11082
+ }
11083
+ return out;
11084
+ }
10660
11085
  function deepMerge2(base, patch) {
10661
11086
  const opts = { arrayMode: "concat-primitives" };
10662
11087
  if (envBoolOptional(process.env.WRONGSTACK_DEBUG_CONFIG)) {
@@ -10692,7 +11117,7 @@ var DefaultConfigLoader = class {
10692
11117
  ]);
10693
11118
  cfg = deepMerge2(cfg, global);
10694
11119
  cfg = deepMerge2(cfg, local);
10695
- cfg = deepMerge2(cfg, inProject);
11120
+ cfg = deepMerge2(cfg, stripUnsafeInProjectFields(inProject, this.paths.inProjectConfig));
10696
11121
  for (const [key, fn] of Object.entries(ENV_MAP)) {
10697
11122
  const v = process.env[key];
10698
11123
  if (v) fn(cfg, v);
@@ -12085,6 +12510,9 @@ function isClearlyDestructiveBashCommand(command, projectRoot) {
12085
12510
  }
12086
12511
 
12087
12512
  // src/security/permission-policy.ts
12513
+ function matchesTrust(patterns, subject) {
12514
+ return patterns.includes(subject) || matchAny(patterns, subject);
12515
+ }
12088
12516
  var DefaultPermissionPolicy = class {
12089
12517
  policy = {};
12090
12518
  loaded = false;
@@ -12221,7 +12649,7 @@ var DefaultPermissionPolicy = class {
12221
12649
  this._evalCache.set(cacheKey, decision);
12222
12650
  return decision;
12223
12651
  }
12224
- if (entry?.deny && subject && matchAny(entry.deny, subject)) {
12652
+ if (entry?.deny && subject && matchesTrust(entry.deny, subject)) {
12225
12653
  const decision = { permission: "deny", source: "deny", reason: "matched deny pattern" };
12226
12654
  this._evalCache.set(cacheKey, decision);
12227
12655
  return decision;
@@ -12231,7 +12659,7 @@ var DefaultPermissionPolicy = class {
12231
12659
  this._evalCache.set(cacheKey, decision);
12232
12660
  return decision;
12233
12661
  }
12234
- if (entry?.allow && subject && matchAny(entry.allow, subject)) {
12662
+ if (entry?.allow && subject && matchesTrust(entry.allow, subject)) {
12235
12663
  const decision = { permission: "auto", source: "trust", reason: "matched allow pattern" };
12236
12664
  this._evalCache.set(cacheKey, decision);
12237
12665
  return decision;
@@ -12767,6 +13195,16 @@ function handleMessageStop(state, ev) {
12767
13195
  async function streamProviderToResponse(provider, req, signal, ctx, events, logger) {
12768
13196
  const state = createStreamingState(req.model);
12769
13197
  logger.debug("Stream started", { providerId: provider.id, model: req.model });
13198
+ const TEXT_BATCH_SIZE = 4;
13199
+ let pendingText = "";
13200
+ let pendingCount = 0;
13201
+ const flushText = () => {
13202
+ if (pendingCount > 0) {
13203
+ events.emit("provider.text_delta", { ctx, text: pendingText });
13204
+ pendingText = "";
13205
+ pendingCount = 0;
13206
+ }
13207
+ };
12770
13208
  const iter = provider.stream(req, { signal })[Symbol.asyncIterator]();
12771
13209
  try {
12772
13210
  for (; ; ) {
@@ -12786,9 +13224,12 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
12786
13224
  break;
12787
13225
  case "text_delta":
12788
13226
  handleTextDelta(state, ev.text);
12789
- events.emit("provider.text_delta", { ctx, text: ev.text });
13227
+ pendingText += ev.text;
13228
+ pendingCount++;
13229
+ if (pendingCount >= TEXT_BATCH_SIZE) flushText();
12790
13230
  break;
12791
13231
  case "tool_use_start": {
13232
+ flushText();
12792
13233
  const idVal = ev.id;
12793
13234
  const nameVal = ev.name;
12794
13235
  handleToolUseStart(state, { id: idVal, name: nameVal });
@@ -12800,6 +13241,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
12800
13241
  handleToolUseInputDelta(state, ev);
12801
13242
  break;
12802
13243
  case "tool_use_stop": {
13244
+ flushText();
12803
13245
  const stoppedName = state.tools.get(ev.id)?.name ?? "unknown";
12804
13246
  handleToolUseStop(state, ev);
12805
13247
  events.emit("provider.tool_use_stop", { ctx, id: ev.id, name: stoppedName });
@@ -12809,6 +13251,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
12809
13251
  handleThinkingStart(state, ev);
12810
13252
  break;
12811
13253
  case "thinking_delta":
13254
+ flushText();
12812
13255
  handleThinkingDelta(state, ev.text);
12813
13256
  events.emit("provider.thinking_delta", { ctx, text: ev.text });
12814
13257
  break;
@@ -12840,6 +13283,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
12840
13283
  eventType: String(evAny.type),
12841
13284
  errorMessage: errMsg
12842
13285
  });
13286
+ flushText();
12843
13287
  events.emit("provider.stream_error", {
12844
13288
  ctx,
12845
13289
  eventType: String(evAny.type),
@@ -12850,6 +13294,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
12850
13294
  } catch (err) {
12851
13295
  if (signal.aborted) {
12852
13296
  state.stopReason = "end_turn";
13297
+ flushText();
12853
13298
  logger.debug("Stream aborted \u2014 returning partial state", {
12854
13299
  providerId: provider.id,
12855
13300
  model: req.model,
@@ -12878,6 +13323,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
12878
13323
  } catch {
12879
13324
  }
12880
13325
  }
13326
+ flushText();
12881
13327
  logger.debug("Stream completed", {
12882
13328
  providerId: provider.id,
12883
13329
  model: req.model,
@@ -14734,7 +15180,14 @@ var EternalAutonomyEngine = class {
14734
15180
  try {
14735
15181
  const reloaded = await loadGoal(this.goalPath, this.opts.events);
14736
15182
  iterationIndex = reloaded?.iterations ?? 0;
14737
- } catch {
15183
+ } catch (err) {
15184
+ console.error(JSON.stringify({
15185
+ level: "warn",
15186
+ event: "autonomy.goal_reload_failed",
15187
+ message: toErrorMessage(err),
15188
+ context: { goalPath: this.goalPath },
15189
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
15190
+ }));
14738
15191
  }
14739
15192
  this.opts.onIteration?.({
14740
15193
  at: (this.opts.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
@@ -14969,7 +15422,14 @@ ${lastFew}` : "No prior iterations yet.",
14969
15422
  } finally {
14970
15423
  clearTimeout(timer);
14971
15424
  }
14972
- } catch {
15425
+ } catch (err) {
15426
+ console.error(JSON.stringify({
15427
+ level: "warn",
15428
+ event: "autonomy.brainstorm_failed",
15429
+ message: toErrorMessage(err),
15430
+ context: { goal: goal.goal.slice(0, 100) },
15431
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
15432
+ }));
14973
15433
  return null;
14974
15434
  }
14975
15435
  }
@@ -18849,6 +19309,68 @@ Working rules:
18849
19309
  - When in doubt, flag as medium rather than ignoring potential issues`
18850
19310
  // Budgets are set by the orchestrator per task — see fleet.ts header.
18851
19311
  };
19312
+ var SHADOW_AGENT = {
19313
+ id: "shadow-agent",
19314
+ name: "Shadow",
19315
+ role: "shadow-agent",
19316
+ prompt: `You are the Shadow Agent \u2014 a silent background monitor for the WrongStack fleet.
19317
+
19318
+ Your job is to observe, detect anomalies, and be ready to intervene \u2014 but only when commanded.
19319
+
19320
+ ## Core Responsibilities
19321
+
19322
+ 1. **Fleet Monitoring** (every 30s)
19323
+ - Call \`fleet_status\` + \`fleet_health\` on each heartbeat
19324
+ - Track what each agent is doing (task descriptions)
19325
+ - Detect stuck agents (>5min no events), idle agents, crashed agents
19326
+
19327
+ 2. **FleetBus Subscription**
19328
+ - Subscribe to \`subagent.*\` events to track lifecycle
19329
+ - Subscribe to \`tool.executed\` to monitor activity
19330
+ - Track agent joins (subagent.started) and leaves (subagent.stopped)
19331
+
19332
+ 3. **Mailbox Surveillance**
19333
+ - Monitor for \`control\` type messages starting with "hoop"
19334
+ - Detect orphan assigns (assign without result within 5min)
19335
+ - Cross-session awareness via shared project mailbox
19336
+
19337
+ 4. **Spike Detection**
19338
+ - Track task duration per agent
19339
+ - Flag agents that spawn and die within <5 seconds
19340
+ - Log spike events with reason (completed/error/killed/timeout)
19341
+
19342
+ 5. **Intervention Commands**
19343
+ Parse these from mailbox control messages:
19344
+ - \`hoop <agentId>\` \u2014 terminate specific agent
19345
+ - \`hoop all\` \u2014 terminate all running agents
19346
+ - \`shadow status\` \u2014 report current fleet snapshot
19347
+ - \`shadow mute\` \u2014 pause heartbeat monitoring
19348
+ - \`shadow resume\` \u2014 resume heartbeat monitoring
19349
+ - \`shadow interval <ms>\` \u2014 change heartbeat interval
19350
+ - \`shadow model <model-id>\` \u2014 change analysis model
19351
+
19352
+ ## Operating Rules
19353
+
19354
+ - **Silent by default**: Use DEBUG level logging unless anomaly detected
19355
+ - **Deterministic**: Same state always produces same actions \u2014 no randomness
19356
+ - **Report on anomaly**: When anomaly detected, use \`mail_send\` to broadcast warning
19357
+ - **Never auto-intervene**: Always report unless explicitly commanded
19358
+ - **Minimal footprint**: Small state, efficient snapshots
19359
+
19360
+ ## Startup Sequence
19361
+
19362
+ 1. Send broadcast: \`shadow:started { intervalMs, model, startTime }\`
19363
+ 2. Subscribe to FleetBus for all relevant events
19364
+ 3. Schedule heartbeat cron job at configured interval
19365
+ 4. Wait for commands or anomalies
19366
+
19367
+ ## Shutdown Sequence
19368
+
19369
+ 1. Cancel all cron jobs (\`cron_cancel\`)
19370
+ 2. Send broadcast: \`shadow:stopped { reason, finalState }\`
19371
+ 3. Clean up FleetBus subscriptions`
19372
+ // Budgets are set by the orchestrator per task — see fleet.ts header.
19373
+ };
18852
19374
  var CRITIC_AGENT = {
18853
19375
  id: "critic",
18854
19376
  name: "Critic",
@@ -18889,6 +19411,7 @@ var FLEET_ROSTER = {
18889
19411
  "refactor-planner": REFACTOR_PLANNER_AGENT,
18890
19412
  "security-scanner": SECURITY_SCANNER_AGENT,
18891
19413
  "critic": CRITIC_AGENT,
19414
+ "shadow-agent": SHADOW_AGENT,
18892
19415
  ...Object.fromEntries(
18893
19416
  ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
18894
19417
  )
@@ -18900,6 +19423,8 @@ var FLEET_ROSTER_BUDGETS = {
18900
19423
  "refactor-planner": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 6e3, maxToolCalls: 18e3 },
18901
19424
  "security-scanner": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
18902
19425
  "critic": { timeoutMs: 5 * 60 * 60 * 1e3, maxIterations: 4e3, maxToolCalls: 12e3 },
19426
+ "shadow-agent": { timeoutMs: 24 * 60 * 60 * 1e3, maxIterations: 1e4, maxToolCalls: 5e3 },
19427
+ // Long-running background monitor
18903
19428
  ...Object.fromEntries(
18904
19429
  ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.budget])
18905
19430
  )
@@ -20107,7 +20632,14 @@ ${personaLine}Task: ${task}
20107
20632
  subagentIds.push(subagentId);
20108
20633
  taskIds.push(taskId);
20109
20634
  await coordinator.assign(spec);
20110
- } catch {
20635
+ } catch (err) {
20636
+ console.error(JSON.stringify({
20637
+ level: "warn",
20638
+ event: "parallel_engine.spawn_failed",
20639
+ message: toErrorMessage(err),
20640
+ context: { slot: i, task, subagentId },
20641
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
20642
+ }));
20111
20643
  }
20112
20644
  })()
20113
20645
  );
@@ -20132,7 +20664,14 @@ ${personaLine}Task: ${task}
20132
20664
  } finally {
20133
20665
  clearTimeout(timer);
20134
20666
  }
20135
- } catch {
20667
+ } catch (err) {
20668
+ console.error(JSON.stringify({
20669
+ level: "warn",
20670
+ event: "parallel_engine.brainstorm_results_failed",
20671
+ message: toErrorMessage(err),
20672
+ context: { slotCount, taskIds },
20673
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
20674
+ }));
20136
20675
  results = coordinator.results().slice(-taskIds.length);
20137
20676
  }
20138
20677
  await Promise.allSettled(subagentIds.map((id) => coordinator.remove(id)));
@@ -20166,7 +20705,14 @@ ${personaLine}Task: ${task}
20166
20705
  if (file) tasks.push(`[git] inspect and fix: ${file}`);
20167
20706
  }
20168
20707
  }
20169
- } catch {
20708
+ } catch (err) {
20709
+ console.error(JSON.stringify({
20710
+ level: "warn",
20711
+ event: "parallel_engine.git_status_failed",
20712
+ message: toErrorMessage(err),
20713
+ context: { projectRoot: this.opts.projectRoot },
20714
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
20715
+ }));
20170
20716
  }
20171
20717
  }
20172
20718
  if (tasks.length < this.slots) {
@@ -20520,7 +21066,7 @@ var CollabSession = class extends EventEmitter {
20520
21066
  }
20521
21067
  for (const filePath of allFiles) {
20522
21068
  try {
20523
- const [content, stat15] = await Promise.all([
21069
+ const [content, stat16] = await Promise.all([
20524
21070
  fsp3.readFile(filePath, "utf8"),
20525
21071
  fsp3.stat(filePath)
20526
21072
  ]);
@@ -20530,8 +21076,8 @@ var CollabSession = class extends EventEmitter {
20530
21076
  path: filePath,
20531
21077
  content,
20532
21078
  language,
20533
- snapshotMtimeMs: stat15.mtimeMs,
20534
- snapshotSizeBytes: stat15.size
21079
+ snapshotMtimeMs: stat16.mtimeMs,
21080
+ snapshotSizeBytes: stat16.size
20535
21081
  });
20536
21082
  } catch {
20537
21083
  this.snapshot.files.push({ path: filePath, content: "", language: void 0 });
@@ -20944,9 +21490,9 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
20944
21490
  for (const file of this.snapshot.files) {
20945
21491
  if (file.snapshotMtimeMs === void 0 && file.snapshotSizeBytes === void 0) continue;
20946
21492
  try {
20947
- const stat15 = await fsp3.stat(file.path);
20948
- const mtimeChanged = file.snapshotMtimeMs !== void 0 && stat15.mtimeMs > file.snapshotMtimeMs + 1;
20949
- const sizeChanged = file.snapshotSizeBytes !== void 0 && stat15.size !== file.snapshotSizeBytes;
21493
+ const stat16 = await fsp3.stat(file.path);
21494
+ const mtimeChanged = file.snapshotMtimeMs !== void 0 && stat16.mtimeMs > file.snapshotMtimeMs + 1;
21495
+ const sizeChanged = file.snapshotSizeBytes !== void 0 && stat16.size !== file.snapshotSizeBytes;
20950
21496
  if (mtimeChanged || sizeChanged) {
20951
21497
  warnings.push(`${file.path} changed after the collab snapshot was captured.`);
20952
21498
  }
@@ -23707,8 +24253,8 @@ async function loadProjectModes(modesDir) {
23707
24253
  for (const entry of entries) {
23708
24254
  if (!entry.endsWith(".md") && !entry.endsWith(".txt")) continue;
23709
24255
  const filePath = path3.join(modesDir, entry);
23710
- const stat15 = await fsp3.stat(filePath);
23711
- if (!stat15.isFile()) continue;
24256
+ const stat16 = await fsp3.stat(filePath);
24257
+ if (!stat16.isFile()) continue;
23712
24258
  const content = await fsp3.readFile(filePath, "utf8");
23713
24259
  const id = path3.basename(entry, path3.extname(entry));
23714
24260
  modes.push({
@@ -25045,10 +25591,10 @@ var AISpecBuilder = class {
25045
25591
  async saveSession() {
25046
25592
  if (!this.sessionPath) return;
25047
25593
  try {
25048
- const fsp25 = await import('fs/promises');
25049
- const path48 = await import('path');
25594
+ const fsp26 = await import('fs/promises');
25595
+ const path50 = await import('path');
25050
25596
  const { atomicWrite: atomicWrite2 } = await Promise.resolve().then(() => (init_atomic_write(), atomic_write_exports));
25051
- await fsp25.mkdir(path48.dirname(this.sessionPath), { recursive: true });
25597
+ await fsp26.mkdir(path50.dirname(this.sessionPath), { recursive: true });
25052
25598
  await atomicWrite2(this.sessionPath, JSON.stringify(this.session, null, 2));
25053
25599
  } catch {
25054
25600
  }
@@ -25057,8 +25603,8 @@ var AISpecBuilder = class {
25057
25603
  async loadSession() {
25058
25604
  if (!this.sessionPath) return false;
25059
25605
  try {
25060
- const fsp25 = await import('fs/promises');
25061
- const raw = await fsp25.readFile(this.sessionPath, "utf8");
25606
+ const fsp26 = await import('fs/promises');
25607
+ const raw = await fsp26.readFile(this.sessionPath, "utf8");
25062
25608
  const loaded = JSON.parse(raw);
25063
25609
  if (loaded?.id && loaded?.phase && loaded?.title) {
25064
25610
  this.session = loaded;
@@ -25072,8 +25618,8 @@ var AISpecBuilder = class {
25072
25618
  async deleteSession() {
25073
25619
  if (!this.sessionPath) return;
25074
25620
  try {
25075
- const fsp25 = await import('fs/promises');
25076
- await fsp25.unlink(this.sessionPath);
25621
+ const fsp26 = await import('fs/promises');
25622
+ await fsp26.unlink(this.sessionPath);
25077
25623
  } catch {
25078
25624
  }
25079
25625
  }
@@ -25775,15 +26321,15 @@ function computeCriticalPath(graph, _topoOrder, blockedByMap) {
25775
26321
  maxId = id;
25776
26322
  }
25777
26323
  }
25778
- const path48 = [];
26324
+ const path50 = [];
25779
26325
  let current = maxId;
25780
26326
  const visited = /* @__PURE__ */ new Set();
25781
26327
  while (current && !visited.has(current)) {
25782
26328
  visited.add(current);
25783
- path48.unshift(current);
26329
+ path50.unshift(current);
25784
26330
  current = prev.get(current) ?? null;
25785
26331
  }
25786
- return path48;
26332
+ return path50;
25787
26333
  }
25788
26334
  function computeParallelGroups(graph, blockedByMap) {
25789
26335
  const groups = [];
@@ -26791,7 +27337,7 @@ async function startMetricsServer(opts) {
26791
27337
  const tls = opts.tls;
26792
27338
  const useHttps = !!(tls?.cert && tls?.key);
26793
27339
  const host = opts.host ?? "127.0.0.1";
26794
- const path48 = opts.path ?? "/metrics";
27340
+ const path50 = opts.path ?? "/metrics";
26795
27341
  const healthPath = opts.healthPath ?? "/healthz";
26796
27342
  const healthRegistry = opts.healthRegistry;
26797
27343
  const listener = (req, res) => {
@@ -26801,7 +27347,7 @@ async function startMetricsServer(opts) {
26801
27347
  return;
26802
27348
  }
26803
27349
  const url = req.url.split("?")[0];
26804
- if (url === path48) {
27350
+ if (url === path50) {
26805
27351
  let body;
26806
27352
  try {
26807
27353
  body = renderPrometheus(opts.sink.snapshot());
@@ -26865,7 +27411,7 @@ async function startMetricsServer(opts) {
26865
27411
  const protocol = useHttps ? "https" : "http";
26866
27412
  return {
26867
27413
  port: boundPort,
26868
- url: `${protocol}://${host}:${boundPort}${path48}`,
27414
+ url: `${protocol}://${host}:${boundPort}${path50}`,
26869
27415
  close: () => new Promise((resolve18, reject) => {
26870
27416
  server.close((err) => err ? reject(err) : resolve18());
26871
27417
  })
@@ -27804,13 +28350,13 @@ var SkillInstaller = class {
27804
28350
  context: { reason: "path_traversal", skillName: skill.name }
27805
28351
  });
27806
28352
  }
27807
- const stat15 = await fsp3.stat(srcPath);
27808
- if (stat15.size > MAX_SKILL_FILE_SIZE) {
28353
+ const stat16 = await fsp3.stat(srcPath);
28354
+ if (stat16.size > MAX_SKILL_FILE_SIZE) {
27809
28355
  throw new FsError({
27810
- message: `Skill file "${file}" is too large (${(stat15.size / 1024).toFixed(1)}KB). Max: ${MAX_SKILL_FILE_SIZE / 1024}KB`,
28356
+ message: `Skill file "${file}" is too large (${(stat16.size / 1024).toFixed(1)}KB). Max: ${MAX_SKILL_FILE_SIZE / 1024}KB`,
27811
28357
  code: ERROR_CODES.FS_WRITE_FAILED,
27812
28358
  path: srcPath,
27813
- context: { skillName: skill.name, fileSize: stat15.size, maxSize: MAX_SKILL_FILE_SIZE }
28359
+ context: { skillName: skill.name, fileSize: stat16.size, maxSize: MAX_SKILL_FILE_SIZE }
27814
28360
  });
27815
28361
  }
27816
28362
  await fsp3.mkdir(path3.dirname(destPath), { recursive: true });
@@ -28068,6 +28614,12 @@ var GraphMemoryBackend = class {
28068
28614
  edges = [];
28069
28615
  loadedScope = null;
28070
28616
  loaded = false;
28617
+ /**
28618
+ * Promise that resolves when the current in-flight _saveGraph completes.
28619
+ * Tests call flush() to await this before deleting the backend or its temp dir.
28620
+ * Each save operation chains onto the previous one so concurrent saves are serialised.
28621
+ */
28622
+ _saveDone = Promise.resolve();
28071
28623
  constructor(opts) {
28072
28624
  this.file = new FileMemoryBackend({ paths: opts.paths });
28073
28625
  this.graphFile = opts.graphPath ?? `${opts.paths.projectDir}/memory-graph.json`;
@@ -28094,7 +28646,8 @@ var GraphMemoryBackend = class {
28094
28646
  tags: entry.tags,
28095
28647
  priority: entry.priority
28096
28648
  });
28097
- for (const [, other] of this.nodes) {
28649
+ const recentNodes = [...this.nodes.values()].slice(-100);
28650
+ for (const other of recentNodes) {
28098
28651
  if (other.id === nodeId) continue;
28099
28652
  const sim = wordOverlap(entry.text, other.entry.text);
28100
28653
  const tagSim = sharedTags(entry.tags ?? [], other.tags ?? []);
@@ -28110,7 +28663,8 @@ var GraphMemoryBackend = class {
28110
28663
  }
28111
28664
  }
28112
28665
  }
28113
- await this.saveGraph(scope);
28666
+ this._saveDone = this._saveGraph(scope);
28667
+ await this._saveDone;
28114
28668
  }
28115
28669
  async forget(scope, query, filePath) {
28116
28670
  const removed = await this.file.forget(scope, query, filePath);
@@ -28125,7 +28679,8 @@ var GraphMemoryBackend = class {
28125
28679
  }
28126
28680
  for (const id of toRemove) this.nodes.delete(id);
28127
28681
  this.edges = this.edges.filter((e) => !toRemove.includes(e.from) && !toRemove.includes(e.to));
28128
- await this.saveGraph(scope);
28682
+ this._saveDone = this._saveGraph(scope);
28683
+ await this._saveDone;
28129
28684
  }
28130
28685
  return removed;
28131
28686
  }
@@ -28237,7 +28792,8 @@ var GraphMemoryBackend = class {
28237
28792
  this.loadedScope = scope;
28238
28793
  this.loaded = true;
28239
28794
  }
28240
- async saveGraph(scope) {
28795
+ /** Fire-and-forget graph persistence. Named _saveGraph to signal it must not be awaited. */
28796
+ async _saveGraph(scope) {
28241
28797
  this.loadedScope = scope;
28242
28798
  this.loaded = true;
28243
28799
  try {
@@ -28245,16 +28801,21 @@ var GraphMemoryBackend = class {
28245
28801
  nodes: [...this.nodes.entries()],
28246
28802
  edges: this.edges
28247
28803
  };
28248
- await fsp3.mkdir(
28249
- this.graphFile.substring(0, this.graphFile.lastIndexOf("/")),
28250
- { recursive: true }
28251
- );
28804
+ const dir = this.graphFile.substring(0, this.graphFile.lastIndexOf("/"));
28805
+ await fsp3.mkdir(dir, { recursive: true });
28252
28806
  const tmp = `${this.graphFile}.tmp`;
28253
28807
  await fsp3.writeFile(tmp, JSON.stringify(data));
28254
28808
  await fsp3.rename(tmp, this.graphFile);
28255
28809
  } catch {
28256
28810
  }
28257
28811
  }
28812
+ /**
28813
+ * Wait for all in-flight _saveGraph operations to complete.
28814
+ * Call this before deleting the backend or its temp directory.
28815
+ */
28816
+ async flush() {
28817
+ await this._saveDone;
28818
+ }
28258
28819
  };
28259
28820
  function wordOverlap(a, b) {
28260
28821
  const wordsA = new Set(a.toLowerCase().split(/\s+/).filter((w) => w.length > 2));
@@ -28355,84 +28916,89 @@ var SessionMemoryConsolidator = class {
28355
28916
  this.minIterations = opts.minIterations ?? 2;
28356
28917
  this.maxExistingEntries = opts.maxExistingEntries ?? 15;
28357
28918
  }
28358
- afterRun = async (ctx, result) => {
28919
+ afterRun = (ctx, result) => {
28359
28920
  if (result.status !== "done") return;
28360
28921
  if (!result.finalText || result.finalText.trim().length < 20) return;
28361
28922
  if (result.iterations < this.minIterations) return;
28362
28923
  const provider = this.provider ?? ctx.provider;
28363
28924
  if (!provider?.complete) return;
28364
- try {
28365
- const existingEntries = await this.memoryStore.list("project-memory", this.maxExistingEntries);
28366
- const prompt = buildConsolidationPrompt(
28367
- result.finalText,
28368
- result.iterations,
28369
- existingEntries
28370
- );
28371
- const signal = AbortSignal.timeout(15e3);
28372
- const response = await provider.complete(
28373
- {
28374
- model: this.model ?? ctx.model,
28375
- system: [{ type: "text", text: prompt }],
28376
- messages: [
28377
- { role: "user", content: "Review the session and return memory operations as JSON." }
28378
- ],
28379
- maxTokens: 500
28380
- },
28381
- { signal }
28382
- );
28383
- const text = response.content.filter((b) => b.type === "text").map((b) => b.text).join("").trim();
28384
- if (!text) return;
28385
- const jsonMatch = text.match(/\{[\s\S]*\}/);
28386
- if (!jsonMatch) return;
28387
- const parsed = JSON.parse(jsonMatch[0]);
28388
- if (!Array.isArray(parsed.operations) || parsed.operations.length === 0) return;
28389
- let added = 0;
28390
- let edited = 0;
28391
- let deleted = 0;
28392
- for (const op of parsed.operations) {
28393
- switch (op.action) {
28394
- case "add": {
28395
- if (op.text?.trim()) {
28396
- await this.memoryStore.remember(op.text.trim(), void 0, {
28397
- type: op.type,
28398
- tags: op.tags,
28399
- priority: op.priority
28400
- });
28401
- added++;
28925
+ const _finalText = result.finalText;
28926
+ const _iterations = result.iterations;
28927
+ const _model = this.model ?? ctx.model;
28928
+ void (async () => {
28929
+ try {
28930
+ const existingEntries = await this.memoryStore.list("project-memory", this.maxExistingEntries);
28931
+ const prompt = buildConsolidationPrompt(
28932
+ _finalText,
28933
+ _iterations,
28934
+ existingEntries
28935
+ );
28936
+ const signal = AbortSignal.timeout(15e3);
28937
+ const response = await provider.complete(
28938
+ {
28939
+ model: _model,
28940
+ system: [{ type: "text", text: prompt }],
28941
+ messages: [
28942
+ { role: "user", content: "Review the session and return memory operations as JSON." }
28943
+ ],
28944
+ maxTokens: 500
28945
+ },
28946
+ { signal }
28947
+ );
28948
+ const text = response.content.filter((b) => b.type === "text").map((b) => b.text).join("").trim();
28949
+ if (!text) return;
28950
+ const jsonMatch = text.match(/\{[\s\S]*\}/);
28951
+ if (!jsonMatch) return;
28952
+ const parsed = JSON.parse(jsonMatch[0]);
28953
+ if (!Array.isArray(parsed.operations) || parsed.operations.length === 0) return;
28954
+ let added = 0;
28955
+ let edited = 0;
28956
+ let deleted = 0;
28957
+ for (const op of parsed.operations) {
28958
+ switch (op.action) {
28959
+ case "add": {
28960
+ if (op.text?.trim()) {
28961
+ await this.memoryStore.remember(op.text.trim(), void 0, {
28962
+ type: op.type,
28963
+ tags: op.tags,
28964
+ priority: op.priority
28965
+ });
28966
+ added++;
28967
+ }
28968
+ break;
28402
28969
  }
28403
- break;
28404
- }
28405
- case "edit": {
28406
- if (op.query && op.text?.trim()) {
28407
- await this.memoryStore.forget(op.query);
28408
- await this.memoryStore.remember(op.text.trim(), void 0, {
28409
- type: op.type,
28410
- tags: op.tags,
28411
- priority: op.priority
28412
- });
28413
- edited++;
28970
+ case "edit": {
28971
+ if (op.query && op.text?.trim()) {
28972
+ await this.memoryStore.forget(op.query);
28973
+ await this.memoryStore.remember(op.text.trim(), void 0, {
28974
+ type: op.type,
28975
+ tags: op.tags,
28976
+ priority: op.priority
28977
+ });
28978
+ edited++;
28979
+ }
28980
+ break;
28414
28981
  }
28415
- break;
28416
- }
28417
- case "delete": {
28418
- if (op.query) {
28419
- const n = await this.memoryStore.forget(op.query);
28420
- deleted += n;
28982
+ case "delete": {
28983
+ if (op.query) {
28984
+ const n = await this.memoryStore.forget(op.query);
28985
+ deleted += n;
28986
+ }
28987
+ break;
28421
28988
  }
28422
- break;
28423
28989
  }
28424
28990
  }
28425
- }
28426
- if (added > 0 || edited > 0 || deleted > 0) {
28427
- const parts = [];
28428
- if (added) parts.push(`${added} added`);
28429
- if (edited) parts.push(`${edited} edited`);
28430
- if (deleted) parts.push(`${deleted} deleted`);
28431
- process.stderr.write(`[memory] Session consolidation: ${parts.join(", ")}
28991
+ if (added > 0 || edited > 0 || deleted > 0) {
28992
+ const parts = [];
28993
+ if (added) parts.push(`${added} added`);
28994
+ if (edited) parts.push(`${edited} edited`);
28995
+ if (deleted) parts.push(`${deleted} deleted`);
28996
+ process.stderr.write(`[memory] Session consolidation: ${parts.join(", ")}
28432
28997
  `);
28998
+ }
28999
+ } catch {
28433
29000
  }
28434
- } catch {
28435
- }
29001
+ })();
28436
29002
  };
28437
29003
  };
28438
29004
  function sessionScopedPath(dir, sessionId, suffix) {
@@ -29059,15 +29625,15 @@ var SessionRecovery = class {
29059
29625
  async detectStale(sessionId) {
29060
29626
  const fp = this.filePath(sessionId);
29061
29627
  const TAIL_SIZE = 8192;
29062
- let stat15;
29628
+ let stat16;
29063
29629
  try {
29064
- stat15 = await fsp3.stat(fp);
29630
+ stat16 = await fsp3.stat(fp);
29065
29631
  } catch (err) {
29066
29632
  if (err.code === "ENOENT") return null;
29067
29633
  return null;
29068
29634
  }
29069
- if (stat15.size === 0) return null;
29070
- const position = Math.max(0, stat15.size - TAIL_SIZE);
29635
+ if (stat16.size === 0) return null;
29636
+ const position = Math.max(0, stat16.size - TAIL_SIZE);
29071
29637
  const buf = Buffer.alloc(TAIL_SIZE);
29072
29638
  let fh;
29073
29639
  try {
@@ -29545,6 +30111,9 @@ var AgentStatusTracker = class {
29545
30111
  leaderName;
29546
30112
  // Live agent map: agentId → AgentEntry
29547
30113
  agents = /* @__PURE__ */ new Map();
30114
+ // Last full agent list flushed (leader + subagents). Lets external consumers
30115
+ // read the current state synchronously without re-deriving it.
30116
+ lastAgents = [];
29548
30117
  // Leader tracking
29549
30118
  leaderStatus = "idle";
29550
30119
  leaderCurrentTool;
@@ -29566,6 +30135,10 @@ var AgentStatusTracker = class {
29566
30135
  this.leaderName = opts.leaderName ?? "leader";
29567
30136
  this.onUpdate = opts.onUpdate;
29568
30137
  }
30138
+ /** Current full agent list (leader + subagents) as of the last flush. */
30139
+ getAgents() {
30140
+ return this.lastAgents.length > 0 ? [...this.lastAgents] : [];
30141
+ }
29569
30142
  start() {
29570
30143
  this.unsubscribers.push(
29571
30144
  this.events.onPattern("agent.run.started", () => {
@@ -29807,6 +30380,11 @@ var AgentStatusTracker = class {
29807
30380
  lastActivityAt: (/* @__PURE__ */ new Date()).toISOString()
29808
30381
  };
29809
30382
  const allAgents = [leaderEntry, ...this.agents.values()];
30383
+ this.lastAgents = allAgents;
30384
+ try {
30385
+ this.events.emit("session.agents_updated", { agents: allAgents });
30386
+ } catch {
30387
+ }
29810
30388
  this.registry.updateAgents(allAgents).then(() => {
29811
30389
  try {
29812
30390
  this.onUpdate?.();
@@ -30466,8 +31044,8 @@ var CloudSync = class {
30466
31044
  const localPath = this.categoryToPath(cat);
30467
31045
  if (!localPath) continue;
30468
31046
  try {
30469
- const stat15 = await fsp3.stat(localPath);
30470
- if (stat15.isDirectory()) {
31047
+ const stat16 = await fsp3.stat(localPath);
31048
+ if (stat16.isDirectory()) {
30471
31049
  const files = await this.walkDir(localPath, localPath);
30472
31050
  for (const file of files) {
30473
31051
  const content = await fsp3.readFile(file, "utf8");
@@ -30492,8 +31070,8 @@ var CloudSync = class {
30492
31070
  const localPath = this.categoryToPath(cat);
30493
31071
  if (!localPath) continue;
30494
31072
  try {
30495
- const stat15 = await fsp3.stat(localPath);
30496
- if (stat15.isDirectory()) {
31073
+ const stat16 = await fsp3.stat(localPath);
31074
+ if (stat16.isDirectory()) {
30497
31075
  const files = await this.walkDir(localPath, localPath);
30498
31076
  for (const file of files) {
30499
31077
  const content = await fsp3.readFile(file);
@@ -30684,7 +31262,13 @@ function parseHqFrame(raw) {
30684
31262
  }
30685
31263
  }
30686
31264
  }
30687
- var KNOWN_HQ_EVENT_PAYLOAD_TYPES = /* @__PURE__ */ new Set(["mailbox.snapshot", "mailbox.event"]);
31265
+ var KNOWN_HQ_EVENT_PAYLOAD_TYPES = /* @__PURE__ */ new Set([
31266
+ "mailbox.snapshot",
31267
+ "mailbox.event",
31268
+ "session.snapshot",
31269
+ "session.transcript",
31270
+ "session.ended"
31271
+ ]);
30688
31272
  function isHqMailboxMessageSummary(x) {
30689
31273
  if (typeof x !== "object" || x === null) return false;
30690
31274
  const v = x;
@@ -30731,6 +31315,52 @@ function isHqMailboxEventPayload(x) {
30731
31315
  if (v.agent !== void 0 && !isHqMailboxAgentSummary(v.agent)) return false;
30732
31316
  return true;
30733
31317
  }
31318
+ var HQ_SESSION_AGENT_STATUSES = /* @__PURE__ */ new Set([
31319
+ "idle",
31320
+ "running",
31321
+ "streaming",
31322
+ "waiting_user",
31323
+ "error"
31324
+ ]);
31325
+ function isHqSessionAgentSummary(x) {
31326
+ if (typeof x !== "object" || x === null) return false;
31327
+ const v = x;
31328
+ return typeof v.id === "string" && typeof v.name === "string" && typeof v.status === "string" && HQ_SESSION_AGENT_STATUSES.has(v.status) && typeof v.iterations === "number" && typeof v.toolCalls === "number" && typeof v.lastActivityAt === "string";
31329
+ }
31330
+ var HQ_SESSION_STATUSES = /* @__PURE__ */ new Set(["active", "idle", "closing", "stale"]);
31331
+ function isHqSessionSnapshotPayload(x) {
31332
+ if (typeof x !== "object" || x === null) return false;
31333
+ const v = x;
31334
+ if (typeof v.sessionId !== "string" || typeof v.clientKind !== "string" || typeof v.machineId !== "string" || typeof v.projectId !== "string" || typeof v.projectName !== "string" || typeof v.projectRoot !== "string" || typeof v.status !== "string" || !HQ_SESSION_STATUSES.has(v.status) || typeof v.startedAt !== "string" || typeof v.lastActivityAt !== "string" || typeof v.agentCount !== "number" || !Array.isArray(v.agents)) {
31335
+ return false;
31336
+ }
31337
+ for (const agent of v.agents) {
31338
+ if (!isHqSessionAgentSummary(agent)) return false;
31339
+ }
31340
+ return true;
31341
+ }
31342
+ var HQ_TRANSCRIPT_ROLES = /* @__PURE__ */ new Set(["user", "assistant", "tool", "system", "error"]);
31343
+ function isHqTranscriptEntry(x) {
31344
+ if (typeof x !== "object" || x === null) return false;
31345
+ const v = x;
31346
+ return typeof v.ts === "string" && typeof v.role === "string" && HQ_TRANSCRIPT_ROLES.has(v.role) && typeof v.text === "string";
31347
+ }
31348
+ function isHqTranscriptAppendPayload(x) {
31349
+ if (typeof x !== "object" || x === null) return false;
31350
+ const v = x;
31351
+ if (typeof v.sessionId !== "string" || typeof v.fromSeq !== "number" || !Array.isArray(v.entries)) {
31352
+ return false;
31353
+ }
31354
+ for (const entry of v.entries) {
31355
+ if (!isHqTranscriptEntry(entry)) return false;
31356
+ }
31357
+ return true;
31358
+ }
31359
+ function isHqSessionEndedPayload(x) {
31360
+ if (typeof x !== "object" || x === null) return false;
31361
+ const v = x;
31362
+ return typeof v.sessionId === "string" && typeof v.endedAt === "string";
31363
+ }
30734
31364
  function parseHqEventPayload(eventType, payload) {
30735
31365
  if (!KNOWN_HQ_EVENT_PAYLOAD_TYPES.has(eventType)) {
30736
31366
  return { ok: true, payload };
@@ -30740,6 +31370,12 @@ function parseHqEventPayload(eventType, payload) {
30740
31370
  return isHqMailboxSnapshotPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
30741
31371
  case "mailbox.event":
30742
31372
  return isHqMailboxEventPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
31373
+ case "session.snapshot":
31374
+ return isHqSessionSnapshotPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
31375
+ case "session.transcript":
31376
+ return isHqTranscriptAppendPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
31377
+ case "session.ended":
31378
+ return isHqSessionEndedPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
30743
31379
  default: {
30744
31380
  const _exhaustive = eventType;
30745
31381
  return _exhaustive;
@@ -31210,6 +31846,41 @@ var HqPublisher = class {
31210
31846
  ...input.timestamp !== void 0 ? { timestamp: input.timestamp } : {}
31211
31847
  });
31212
31848
  }
31849
+ /** The client identity this publisher announced (clientId, kind, machineId, …). */
31850
+ get identity() {
31851
+ return this.options.client;
31852
+ }
31853
+ /** The project identity this publisher is bound to. */
31854
+ get project() {
31855
+ return this.options.project;
31856
+ }
31857
+ /** Publish a live session/terminal snapshot (state + agents). */
31858
+ publishSessionSnapshot(payload, opts) {
31859
+ return this.publishEvent({
31860
+ type: "session.snapshot",
31861
+ payload,
31862
+ sessionId: payload.sessionId,
31863
+ ...opts?.timestamp !== void 0 ? { timestamp: opts.timestamp } : {}
31864
+ });
31865
+ }
31866
+ /** Publish an incremental batch of transcript turns for a session. */
31867
+ publishTranscriptAppend(payload, opts) {
31868
+ return this.publishEvent({
31869
+ type: "session.transcript",
31870
+ payload,
31871
+ sessionId: payload.sessionId,
31872
+ ...opts?.timestamp !== void 0 ? { timestamp: opts.timestamp } : {}
31873
+ });
31874
+ }
31875
+ /** Mark a session/terminal as ended. */
31876
+ publishSessionEnded(payload, opts) {
31877
+ return this.publishEvent({
31878
+ type: "session.ended",
31879
+ payload,
31880
+ sessionId: payload.sessionId,
31881
+ ...opts?.timestamp !== void 0 ? { timestamp: opts.timestamp } : {}
31882
+ });
31883
+ }
31213
31884
  pollCommands() {
31214
31885
  this.sendFrame({
31215
31886
  type: "client.command_poll",
@@ -31777,30 +32448,69 @@ var GlobalMailbox = class {
31777
32448
  async _readMessages() {
31778
32449
  try {
31779
32450
  const raw = await fsp3.readFile(this.messagePath, "utf8");
31780
- const lines = raw.split(LINE_SEPARATOR).filter((l) => l.trim().length > 0);
31781
- const messages = [];
31782
- for (const line of lines) {
31783
- try {
31784
- const parsed = JSON.parse(line);
31785
- if (!parsed["readBy"]) {
31786
- const readBy = {};
31787
- if (parsed["read"] && parsed["readAt"]) {
31788
- readBy[parsed["to"]] = parsed["readAt"];
31789
- }
31790
- parsed["readBy"] = readBy;
31791
- delete parsed["read"];
31792
- delete parsed["readAt"];
31793
- }
31794
- messages.push(parsed);
31795
- } catch {
31796
- }
31797
- }
31798
- return messages;
32451
+ return this._parseLines(raw);
31799
32452
  } catch (err) {
31800
32453
  if (err.code === "ENOENT") return [];
31801
32454
  throw err;
31802
32455
  }
31803
32456
  }
32457
+ /**
32458
+ * Read only newly-appended bytes from the file and append them to the
32459
+ * in-memory cache. This avoids re-reading and re-parsing the entire file
32460
+ * when another process appended messages since our last read.
32461
+ *
32462
+ * Only safe when the file grew in size (messages are append-only). When
32463
+ * the file was rewritten (ack/purge changed existing content), callers
32464
+ * must fall back to {@link _readMessages}.
32465
+ *
32466
+ * @returns The (now up-to-date) message cache.
32467
+ */
32468
+ async _readNewMessagesOnly(fd, oldSize, newSize) {
32469
+ const tailLen = newSize - oldSize;
32470
+ const buf = Buffer.alloc(tailLen);
32471
+ await fd.read(buf, 0, tailLen, oldSize);
32472
+ const tail = buf.toString("utf8");
32473
+ for (const line of tail.split(LINE_SEPARATOR)) {
32474
+ if (!line.trim()) continue;
32475
+ try {
32476
+ const parsed = JSON.parse(line);
32477
+ if (!parsed["readBy"]) {
32478
+ const readBy = {};
32479
+ if (parsed["read"] && parsed["readAt"]) {
32480
+ readBy[parsed["to"]] = parsed["readAt"];
32481
+ }
32482
+ parsed["readBy"] = readBy;
32483
+ delete parsed["read"];
32484
+ delete parsed["readAt"];
32485
+ }
32486
+ this._messageCache.push(parsed);
32487
+ } catch {
32488
+ }
32489
+ }
32490
+ return this._messageCache;
32491
+ }
32492
+ /** Parse a JSONL string into MailboxMessage[], including migration. */
32493
+ _parseLines(raw) {
32494
+ const lines = raw.split(LINE_SEPARATOR).filter((l) => l.trim().length > 0);
32495
+ const messages = [];
32496
+ for (const line of lines) {
32497
+ try {
32498
+ const parsed = JSON.parse(line);
32499
+ if (!parsed["readBy"]) {
32500
+ const readBy = {};
32501
+ if (parsed["read"] && parsed["readAt"]) {
32502
+ readBy[parsed["to"]] = parsed["readAt"];
32503
+ }
32504
+ parsed["readBy"] = readBy;
32505
+ delete parsed["read"];
32506
+ delete parsed["readAt"];
32507
+ }
32508
+ messages.push(parsed);
32509
+ } catch {
32510
+ }
32511
+ }
32512
+ return messages;
32513
+ }
31804
32514
  /**
31805
32515
  * Read messages, then adopt the result as the in-memory cache. Use this
31806
32516
  * from writers that just took the file lock — the read reflects the
@@ -31820,6 +32530,10 @@ var GlobalMailbox = class {
31820
32530
  * stat matches the cached mtime+size we return the cached array — no
31821
32531
  * file read and no JSON.parse — collapsing the per-iteration query
31822
32532
  * cost on the mailbox-loop hot path.
32533
+ *
32534
+ * When the file only grew (new messages appended by another process),
32535
+ * we read and parse just the tail bytes instead of the entire file.
32536
+ * This avoids re-parsing the full 10K-message history on every check.
31823
32537
  */
31824
32538
  async _readMessagesCached() {
31825
32539
  try {
@@ -31827,6 +32541,17 @@ var GlobalMailbox = class {
31827
32541
  if (this._messageCache !== null && this._messageCacheMtime === st.mtimeMs && this._messageCacheSize === st.size) {
31828
32542
  return this._messageCache;
31829
32543
  }
32544
+ if (this._messageCache !== null && this._messageCacheSize >= 0 && st.size > this._messageCacheSize) {
32545
+ const fd = await fsp3.open(this.messagePath, "r");
32546
+ try {
32547
+ const updated = await this._readNewMessagesOnly(fd, this._messageCacheSize, st.size);
32548
+ this._messageCacheMtime = st.mtimeMs;
32549
+ this._messageCacheSize = st.size;
32550
+ return updated;
32551
+ } finally {
32552
+ await fd.close();
32553
+ }
32554
+ }
31830
32555
  const all = await this._readMessages();
31831
32556
  this._setMessageCache(all, st.mtimeMs, st.size);
31832
32557
  return all;
@@ -32191,7 +32916,7 @@ function resolveHqConfig(options = {}) {
32191
32916
  };
32192
32917
  }
32193
32918
  function stableMachineId() {
32194
- return createHash("sha256").update(`${hostname()}:${process.pid}`).digest("hex").slice(0, 12);
32919
+ return createHash("sha256").update(hostname()).digest("hex").slice(0, 12);
32195
32920
  }
32196
32921
  function deriveProjectId(projectRoot) {
32197
32922
  return createHash("sha256").update(projectRoot).digest("hex").slice(0, 12);
@@ -32233,6 +32958,377 @@ function createHqPublisherFromEnv(options) {
32233
32958
  function createGlobalMailbox(options) {
32234
32959
  return new GlobalMailbox(options.projectDir, options.events, options.hqPublisher);
32235
32960
  }
32961
+
32962
+ // src/hq/agent-bridge.ts
32963
+ function startAgentMonitorEventBridge(opts) {
32964
+ const { events, clientId, projectId, publish } = opts;
32965
+ const seq = { current: 0 };
32966
+ function nextSeq() {
32967
+ seq.current += 1;
32968
+ return seq.current;
32969
+ }
32970
+ function buildEnvelope(type, payload) {
32971
+ return createHqEventEnvelope({
32972
+ id: `${Date.now().toString(36)}-${nextSeq()}`,
32973
+ type,
32974
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
32975
+ clientId,
32976
+ projectId,
32977
+ seq: nextSeq(),
32978
+ payload
32979
+ });
32980
+ }
32981
+ const offMessage = events.on("agent.timeline.message", (payload) => {
32982
+ if (!publish) return;
32983
+ const msgPayload = {
32984
+ subagentId: payload.subagentId,
32985
+ agentName: payload.agentName,
32986
+ content: payload.content,
32987
+ kind: payload.kind,
32988
+ iteration: payload.iteration,
32989
+ ts: payload.ts
32990
+ };
32991
+ if (payload.toolName !== void 0) msgPayload.toolName = payload.toolName;
32992
+ if (payload.costUsd !== void 0) msgPayload.costUsd = payload.costUsd;
32993
+ publish(buildEnvelope("agent.message", msgPayload));
32994
+ });
32995
+ const offStatus = events.on("agent.status_changed", (payload) => {
32996
+ if (!publish) return;
32997
+ const statusPayload = {
32998
+ subagentId: payload.subagentId,
32999
+ agentName: payload.agentName,
33000
+ status: payload.status,
33001
+ ts: payload.ts
33002
+ };
33003
+ if (payload.summary !== void 0) statusPayload.summary = payload.summary;
33004
+ if (payload.task !== void 0) statusPayload.task = payload.task;
33005
+ publish(buildEnvelope("agent.status", statusPayload));
33006
+ });
33007
+ return () => {
33008
+ offMessage();
33009
+ offStatus();
33010
+ };
33011
+ }
33012
+
33013
+ // src/hq/transcript-mapper.ts
33014
+ function blocksToText(content) {
33015
+ if (typeof content === "string") return content;
33016
+ if (Array.isArray(content)) {
33017
+ return content.filter(
33018
+ (b) => !!b && typeof b === "object" && b.type === "text" && typeof b.text === "string"
33019
+ ).map((b) => b.text).join("\n");
33020
+ }
33021
+ return "";
33022
+ }
33023
+ function asString(v) {
33024
+ if (typeof v === "string") return v;
33025
+ try {
33026
+ return JSON.stringify(v, null, 2);
33027
+ } catch {
33028
+ return String(v);
33029
+ }
33030
+ }
33031
+ function argsEntry(ts, name, input, id) {
33032
+ return {
33033
+ ts,
33034
+ role: "tool",
33035
+ tool: String(name ?? "tool"),
33036
+ toolInput: input !== void 0 && input !== null ? asString(input) : "{}",
33037
+ text: "",
33038
+ ...typeof id === "string" ? { toolUseId: id } : {}
33039
+ };
33040
+ }
33041
+ function mapSessionEventToEntries(ev) {
33042
+ const ts = typeof ev["ts"] === "string" ? ev["ts"] : "";
33043
+ const make = (role, text, extra) => ({
33044
+ ts,
33045
+ role,
33046
+ text,
33047
+ ...extra
33048
+ });
33049
+ switch (ev["type"]) {
33050
+ case "user_input": {
33051
+ const t2 = blocksToText(ev["content"]);
33052
+ return t2.trim() ? [make("user", t2)] : [];
33053
+ }
33054
+ case "llm_response": {
33055
+ const out = [];
33056
+ const t2 = blocksToText(ev["content"]);
33057
+ if (t2.trim()) out.push(make("assistant", t2));
33058
+ const content = ev["content"];
33059
+ if (Array.isArray(content)) {
33060
+ for (const b of content) {
33061
+ if (b && typeof b === "object" && b.type === "tool_use") {
33062
+ const block = b;
33063
+ out.push(argsEntry(ts, block.name, block.input, block.id));
33064
+ }
33065
+ }
33066
+ }
33067
+ return out;
33068
+ }
33069
+ case "tool_use":
33070
+ return [argsEntry(ts, ev["name"], ev["input"], ev["id"])];
33071
+ case "tool_call_start":
33072
+ return [argsEntry(ts, ev["name"], ev["input"] ?? ev["args"], ev["id"])];
33073
+ case "tool_result": {
33074
+ const isError = ev["isError"] === true;
33075
+ const content = ev["content"] ?? ev["output"];
33076
+ const outStr = content !== void 0 && content !== null ? asString(content) : "";
33077
+ return [
33078
+ make(isError ? "error" : "tool", outStr, {
33079
+ tool: "\u21B3 result",
33080
+ ...isError ? { isError: true } : {},
33081
+ ...typeof ev["id"] === "string" ? { toolUseId: ev["id"] } : {}
33082
+ })
33083
+ ];
33084
+ }
33085
+ case "tool_call_end": {
33086
+ const isError = ev["isError"] === true;
33087
+ const content = ev["output"] ?? ev["content"];
33088
+ const outStr = content !== void 0 && content !== null ? asString(content) : "";
33089
+ if (!outStr.trim() && !isError && typeof ev["durationMs"] !== "number") return [];
33090
+ return [
33091
+ make(isError ? "error" : "tool", outStr, {
33092
+ tool: typeof ev["name"] === "string" ? String(ev["name"]) : "\u21B3 result",
33093
+ ...typeof ev["durationMs"] === "number" ? { durationMs: ev["durationMs"] } : {},
33094
+ ...isError ? { isError: true } : {},
33095
+ ...typeof ev["id"] === "string" ? { toolUseId: ev["id"] } : {}
33096
+ })
33097
+ ];
33098
+ }
33099
+ case "error":
33100
+ case "provider_error":
33101
+ return [make("error", String(ev["message"] ?? "error"))];
33102
+ case "agent_spawned":
33103
+ return [make("system", `spawned ${String(ev["role"] ?? "agent")}`)];
33104
+ case "task_completed":
33105
+ return [make("system", `task done: ${String(ev["title"] ?? "")}`)];
33106
+ case "task_failed":
33107
+ return [make("system", `task failed: ${String(ev["title"] ?? "")}`)];
33108
+ default:
33109
+ return [];
33110
+ }
33111
+ }
33112
+ function isResultEntry(e) {
33113
+ return (e.role === "tool" || e.role === "error") && e.toolUseId !== void 0 && e.toolInput === void 0;
33114
+ }
33115
+ function mergeToolResults(flat) {
33116
+ const out = [];
33117
+ const argsById = /* @__PURE__ */ new Map();
33118
+ for (const src of flat) {
33119
+ const e = { ...src };
33120
+ if (isResultEntry(e) && e.toolUseId !== void 0) {
33121
+ const tgt = argsById.get(e.toolUseId);
33122
+ if (tgt) {
33123
+ tgt.text = e.text || "";
33124
+ if (e.durationMs !== void 0) tgt.durationMs = e.durationMs;
33125
+ if (e.isError) {
33126
+ tgt.role = "error";
33127
+ tgt.isError = true;
33128
+ }
33129
+ continue;
33130
+ }
33131
+ }
33132
+ out.push(e);
33133
+ if (e.role === "tool" && e.toolUseId !== void 0 && e.toolInput !== void 0) {
33134
+ argsById.set(e.toolUseId, e);
33135
+ }
33136
+ }
33137
+ return out;
33138
+ }
33139
+ function buildTranscriptFromEvents(events) {
33140
+ const flat = [];
33141
+ for (const ev of events) {
33142
+ for (const e of mapSessionEventToEntries(ev)) flat.push(e);
33143
+ }
33144
+ return mergeToolResults(flat);
33145
+ }
33146
+
33147
+ // src/hq/session-bridge.ts
33148
+ var VALID_AGENT_STATUS = /* @__PURE__ */ new Set([
33149
+ "idle",
33150
+ "running",
33151
+ "streaming",
33152
+ "waiting_user",
33153
+ "error"
33154
+ ]);
33155
+ function toAgentSummary(a) {
33156
+ const status = VALID_AGENT_STATUS.has(a.status) ? a.status : "idle";
33157
+ return {
33158
+ id: a.id,
33159
+ name: a.name,
33160
+ status,
33161
+ iterations: a.iterations,
33162
+ toolCalls: a.toolCalls,
33163
+ lastActivityAt: a.lastActivityAt,
33164
+ ...a.currentTool !== void 0 ? { currentTool: a.currentTool } : {},
33165
+ ...a.costUsd !== void 0 ? { costUsd: a.costUsd } : {},
33166
+ ...a.tokensIn !== void 0 ? { tokensIn: a.tokensIn } : {},
33167
+ ...a.tokensOut !== void 0 ? { tokensOut: a.tokensOut } : {},
33168
+ ...a.ctxPct !== void 0 ? { ctxPct: a.ctxPct } : {},
33169
+ ...a.model !== void 0 ? { model: a.model } : {},
33170
+ ...a.partialText !== void 0 ? { partialText: a.partialText } : {}
33171
+ };
33172
+ }
33173
+ function deriveSessionStatus(agents) {
33174
+ return agents.some(
33175
+ (a) => a.status === "running" || a.status === "streaming" || a.status === "waiting_user"
33176
+ ) ? "active" : "idle";
33177
+ }
33178
+ function startSessionTelemetryBridge(opts) {
33179
+ const now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
33180
+ const publisher = opts.publisher;
33181
+ const identity = publisher.identity;
33182
+ const project = publisher.project;
33183
+ const startedAt = opts.startedAt ?? now();
33184
+ const wpaths = resolveWstackPaths({
33185
+ projectRoot: opts.projectRoot,
33186
+ ...opts.globalRoot !== void 0 ? { globalRoot: opts.globalRoot } : {}
33187
+ });
33188
+ const sessionFile = path3.join(wpaths.projectSessions, `${opts.sessionId}.jsonl`);
33189
+ let agents = [];
33190
+ let lastActivityAt = startedAt;
33191
+ let lastSnapshotHash = "";
33192
+ let disposed = false;
33193
+ function buildSnapshot() {
33194
+ return {
33195
+ sessionId: opts.sessionId,
33196
+ clientKind: identity.kind,
33197
+ machineId: identity.machineId,
33198
+ projectId: project.projectId,
33199
+ projectName: opts.projectName ?? project.projectName,
33200
+ projectRoot: opts.projectRoot,
33201
+ status: deriveSessionStatus(agents),
33202
+ startedAt,
33203
+ lastActivityAt,
33204
+ agentCount: agents.length,
33205
+ agents,
33206
+ ...identity.hostname !== void 0 ? { hostname: identity.hostname } : {},
33207
+ ...identity.pid !== void 0 ? { pid: identity.pid } : {},
33208
+ ...opts.gitBranch !== void 0 ? { gitBranch: opts.gitBranch } : {}
33209
+ };
33210
+ }
33211
+ function publishSnapshot(force = false) {
33212
+ if (disposed) return;
33213
+ const snap = buildSnapshot();
33214
+ const hash = JSON.stringify({ ...snap, lastActivityAt: "" });
33215
+ if (!force && hash === lastSnapshotHash) return;
33216
+ lastSnapshotHash = hash;
33217
+ try {
33218
+ publisher.publishSessionSnapshot(snap);
33219
+ } catch {
33220
+ }
33221
+ }
33222
+ const offAgents = opts.events?.on("session.agents_updated", (payload) => {
33223
+ agents = payload.agents.map(toAgentSummary);
33224
+ lastActivityAt = now();
33225
+ publishSnapshot();
33226
+ });
33227
+ publishSnapshot(true);
33228
+ let offset = 0;
33229
+ let partial = "";
33230
+ let seqEmitted = 0;
33231
+ let tailing = false;
33232
+ let watcher = null;
33233
+ let watchPending = false;
33234
+ function setupWatcher() {
33235
+ if (disposed || watcher) return;
33236
+ try {
33237
+ const nextWatcher = fs3.watch(sessionFile, () => {
33238
+ if (watchPending || disposed) return;
33239
+ watchPending = true;
33240
+ setTimeout(() => {
33241
+ watchPending = false;
33242
+ void tail();
33243
+ }, 25);
33244
+ });
33245
+ nextWatcher.on("error", () => {
33246
+ try {
33247
+ nextWatcher.close();
33248
+ } catch {
33249
+ }
33250
+ if (watcher === nextWatcher) watcher = null;
33251
+ });
33252
+ watcher = nextWatcher;
33253
+ } catch {
33254
+ watcher = null;
33255
+ }
33256
+ }
33257
+ async function tail() {
33258
+ if (disposed || tailing) return;
33259
+ tailing = true;
33260
+ try {
33261
+ const stat16 = await fsp3.stat(sessionFile).catch(() => null);
33262
+ if (disposed) return;
33263
+ if (!stat16) return;
33264
+ setupWatcher();
33265
+ if (stat16.size <= offset) return;
33266
+ const fd = await fsp3.open(sessionFile, "r");
33267
+ try {
33268
+ if (disposed) return;
33269
+ const len = stat16.size - offset;
33270
+ const buf = Buffer.allocUnsafe(len);
33271
+ await fd.read(buf, 0, len, offset);
33272
+ offset = stat16.size;
33273
+ partial += buf.toString("utf8");
33274
+ const lines = partial.split("\n");
33275
+ partial = lines.pop() ?? "";
33276
+ const entries = [];
33277
+ for (const line of lines) {
33278
+ const trimmed = line.trim();
33279
+ if (!trimmed) continue;
33280
+ let obj;
33281
+ try {
33282
+ obj = JSON.parse(trimmed);
33283
+ } catch {
33284
+ continue;
33285
+ }
33286
+ for (const entry of mapSessionEventToEntries(obj)) entries.push(entry);
33287
+ }
33288
+ if (entries.length > 0) {
33289
+ try {
33290
+ publisher.publishTranscriptAppend({
33291
+ sessionId: opts.sessionId,
33292
+ fromSeq: seqEmitted,
33293
+ entries
33294
+ });
33295
+ } catch {
33296
+ }
33297
+ seqEmitted += entries.length;
33298
+ lastActivityAt = now();
33299
+ }
33300
+ } finally {
33301
+ await fd.close();
33302
+ }
33303
+ } catch {
33304
+ } finally {
33305
+ tailing = false;
33306
+ }
33307
+ }
33308
+ const snapshotTimer = setInterval(() => publishSnapshot(true), opts.snapshotIntervalMs ?? 2500);
33309
+ const tailTimer = setInterval(() => void tail(), opts.transcriptIntervalMs ?? 500);
33310
+ snapshotTimer.unref?.();
33311
+ tailTimer.unref?.();
33312
+ void tail();
33313
+ return () => {
33314
+ if (disposed) return;
33315
+ disposed = true;
33316
+ offAgents?.();
33317
+ if (watcher) {
33318
+ try {
33319
+ watcher.close();
33320
+ } catch {
33321
+ }
33322
+ watcher = null;
33323
+ }
33324
+ clearInterval(snapshotTimer);
33325
+ clearInterval(tailTimer);
33326
+ try {
33327
+ publisher.publishSessionEnded({ sessionId: opts.sessionId, endedAt: now() });
33328
+ } catch {
33329
+ }
33330
+ };
33331
+ }
32236
33332
  var MATCHERS = {
32237
33333
  pnpmWorkspace: (files) => files.includes("pnpm-workspace.yaml"),
32238
33334
  gradlew: (_files, dirs) => dirs.includes("gradlew"),
@@ -33110,8 +34206,8 @@ var ReportGenerator = class {
33110
34206
  try {
33111
34207
  await stat(this.options.outputDir);
33112
34208
  } catch {
33113
- const { mkdir: mkdir23 } = await import('fs/promises');
33114
- await mkdir23(this.options.outputDir, { recursive: true });
34209
+ const { mkdir: mkdir24 } = await import('fs/promises');
34210
+ await mkdir24(this.options.outputDir, { recursive: true });
33115
34211
  }
33116
34212
  }
33117
34213
  generateMarkdown(result) {
@@ -34639,6 +35735,10 @@ var DefaultMailbox = class {
34639
35735
  _messageCache = null;
34640
35736
  _messageCacheMtime = -1;
34641
35737
  _messageCacheSize = -1;
35738
+ /** Primary index: recipient → Set of messages (points into _messageCache). */
35739
+ _byTo = /* @__PURE__ */ new Map();
35740
+ /** Secondary index: sender → Set of messages (points into _messageCache). */
35741
+ _byFrom = /* @__PURE__ */ new Map();
34642
35742
  constructor(sessionDir) {
34643
35743
  this.filePath = path3.join(sessionDir, MAILBOX_FILE2);
34644
35744
  }
@@ -34674,12 +35774,30 @@ var DefaultMailbox = class {
34674
35774
  }
34675
35775
  // ── Query ─────────────────────────────────────────────────────────────
34676
35776
  async query(q) {
34677
- const all = await this._readAllCached();
35777
+ const needFullScan = q.unreadBy !== void 0 || q.since !== void 0;
35778
+ let candidates;
35779
+ if (needFullScan) {
35780
+ candidates = await this._readAllCached();
35781
+ } else {
35782
+ await this._readAllCached();
35783
+ if (q.to !== void 0) {
35784
+ const direct = this._byTo.get(q.to);
35785
+ const broadcast = this._byTo.get("*");
35786
+ const combined = /* @__PURE__ */ new Map();
35787
+ if (direct) for (const m of direct) combined.set(m.id, m);
35788
+ if (broadcast) for (const m of broadcast) combined.set(m.id, m);
35789
+ candidates = Array.from(combined.values());
35790
+ } else if (q.from !== void 0) {
35791
+ candidates = Array.from(this._byFrom.get(q.from) ?? []);
35792
+ } else {
35793
+ candidates = await this._readAllCached();
35794
+ }
35795
+ }
34678
35796
  const limit = q.limit ?? 50;
34679
35797
  const order = q.minPriority !== void 0 ? { low: 0, normal: 1, high: 2 } : null;
34680
35798
  const minPriorityRank = order && q.minPriority !== void 0 ? order[q.minPriority] : 0;
34681
35799
  const filtered = [];
34682
- for (const msg of all) {
35800
+ for (const msg of candidates) {
34683
35801
  if (q.to !== void 0 && msg.to !== q.to && msg.to !== "*") continue;
34684
35802
  if (q.from !== void 0 && msg.from !== q.from) continue;
34685
35803
  if (q.unreadBy !== void 0 && q.unreadBy in msg.readBy) continue;
@@ -34780,6 +35898,8 @@ var DefaultMailbox = class {
34780
35898
  this._messageCache = null;
34781
35899
  this._messageCacheMtime = -1;
34782
35900
  this._messageCacheSize = -1;
35901
+ this._byTo.clear();
35902
+ this._byFrom.clear();
34783
35903
  }
34784
35904
  async clearAll() {
34785
35905
  await withFileLock(this.filePath, async () => {
@@ -34838,36 +35958,81 @@ var DefaultMailbox = class {
34838
35958
  async _readAll() {
34839
35959
  try {
34840
35960
  const raw = await fsp3.readFile(this.filePath, "utf8");
34841
- const lines = raw.split(LINE_SEPARATOR2).filter((l) => l.trim().length > 0);
34842
- const messages = [];
34843
- for (const line of lines) {
34844
- try {
34845
- const parsed = JSON.parse(line);
34846
- if (!parsed["readBy"]) {
34847
- const readBy = {};
34848
- if (parsed["read"] && parsed["readAt"]) {
34849
- readBy[parsed["to"] ?? "unknown"] = parsed["readAt"];
34850
- }
34851
- parsed["readBy"] = readBy;
34852
- delete parsed["read"];
34853
- delete parsed["readAt"];
34854
- }
34855
- messages.push(parsed);
34856
- } catch {
34857
- }
34858
- }
34859
- return messages;
35961
+ return this._parseLines(raw);
34860
35962
  } catch (err) {
34861
35963
  if (err.code === "ENOENT") return [];
34862
35964
  throw err;
34863
35965
  }
34864
35966
  }
35967
+ /**
35968
+ * Read only newly-appended bytes from the file and append them to the
35969
+ * in-memory cache, avoiding a full re-read when the file only grew.
35970
+ */
35971
+ async _readNewMessagesOnly(fd, oldSize, newSize) {
35972
+ const tailLen = newSize - oldSize;
35973
+ const buf = Buffer.alloc(tailLen);
35974
+ await fd.read(buf, 0, tailLen, oldSize);
35975
+ const tail = buf.toString("utf8");
35976
+ for (const line of tail.split(LINE_SEPARATOR2)) {
35977
+ if (!line.trim()) continue;
35978
+ try {
35979
+ const parsed = JSON.parse(line);
35980
+ if (!parsed["readBy"]) {
35981
+ const readBy = {};
35982
+ if (parsed["read"] && parsed["readAt"]) {
35983
+ readBy[parsed["to"] ?? "unknown"] = parsed["readAt"];
35984
+ }
35985
+ parsed["readBy"] = readBy;
35986
+ delete parsed["read"];
35987
+ delete parsed["readAt"];
35988
+ }
35989
+ const msg = parsed;
35990
+ this._messageCache.push(msg);
35991
+ this._indexMsg(msg);
35992
+ } catch {
35993
+ }
35994
+ }
35995
+ return this._messageCache;
35996
+ }
35997
+ /** Parse a JSONL string into MailboxMessage[], including migration. */
35998
+ _parseLines(raw) {
35999
+ const lines = raw.split(LINE_SEPARATOR2).filter((l) => l.trim().length > 0);
36000
+ const messages = [];
36001
+ for (const line of lines) {
36002
+ try {
36003
+ const parsed = JSON.parse(line);
36004
+ if (!parsed["readBy"]) {
36005
+ const readBy = {};
36006
+ if (parsed["read"] && parsed["readAt"]) {
36007
+ readBy[parsed["to"] ?? "unknown"] = parsed["readAt"];
36008
+ }
36009
+ parsed["readBy"] = readBy;
36010
+ delete parsed["read"];
36011
+ delete parsed["readAt"];
36012
+ }
36013
+ messages.push(parsed);
36014
+ } catch {
36015
+ }
36016
+ }
36017
+ return messages;
36018
+ }
34865
36019
  async _readAllCached() {
34866
36020
  try {
34867
36021
  const st = await fsp3.stat(this.filePath);
34868
36022
  if (this._messageCache !== null && this._messageCacheMtime === st.mtimeMs && this._messageCacheSize === st.size) {
34869
36023
  return this._messageCache;
34870
36024
  }
36025
+ if (this._messageCache !== null && this._messageCacheSize >= 0 && st.size > this._messageCacheSize) {
36026
+ const fd = await fsp3.open(this.filePath, "r");
36027
+ try {
36028
+ const updated = await this._readNewMessagesOnly(fd, this._messageCacheSize, st.size);
36029
+ this._messageCacheMtime = st.mtimeMs;
36030
+ this._messageCacheSize = st.size;
36031
+ return updated;
36032
+ } finally {
36033
+ await fd.close();
36034
+ }
36035
+ }
34871
36036
  const all = await this._readAll();
34872
36037
  this._setMessageCache(all, st.mtimeMs, st.size);
34873
36038
  return all;
@@ -34884,9 +36049,12 @@ var DefaultMailbox = class {
34884
36049
  this._messageCache = null;
34885
36050
  this._messageCacheMtime = -1;
34886
36051
  this._messageCacheSize = -1;
36052
+ this._byTo.clear();
36053
+ this._byFrom.clear();
34887
36054
  return;
34888
36055
  }
34889
36056
  this._messageCache = messages;
36057
+ this._buildIndexes(messages);
34890
36058
  if (mtime !== void 0 && size !== void 0) {
34891
36059
  this._messageCacheMtime = mtime;
34892
36060
  this._messageCacheSize = size;
@@ -34904,9 +36072,35 @@ var DefaultMailbox = class {
34904
36072
  this._messageCache = null;
34905
36073
  this._messageCacheMtime = -1;
34906
36074
  this._messageCacheSize = -1;
36075
+ this._byTo.clear();
36076
+ this._byFrom.clear();
34907
36077
  return;
34908
36078
  }
34909
36079
  this._messageCache.push(msg);
36080
+ this._indexMsg(msg);
36081
+ }
36082
+ /** Rebuild both indexes from a full message list. */
36083
+ _buildIndexes(messages) {
36084
+ this._byTo.clear();
36085
+ this._byFrom.clear();
36086
+ for (const msg of messages) {
36087
+ this._indexMsg(msg);
36088
+ }
36089
+ }
36090
+ /** Add a single message to both indexes. */
36091
+ _indexMsg(msg) {
36092
+ const toSet = this._byTo.get(msg.to);
36093
+ if (toSet) {
36094
+ toSet.add(msg);
36095
+ } else {
36096
+ this._byTo.set(msg.to, /* @__PURE__ */ new Set([msg]));
36097
+ }
36098
+ const fromSet = this._byFrom.get(msg.from);
36099
+ if (fromSet) {
36100
+ fromSet.add(msg);
36101
+ } else {
36102
+ this._byFrom.set(msg.from, /* @__PURE__ */ new Set([msg]));
36103
+ }
34910
36104
  }
34911
36105
  };
34912
36106
  var BrainMonitor = class {
@@ -38337,6 +39531,262 @@ ${input.detail}`
38337
39531
  this.onCoordinatorEvent?.(event);
38338
39532
  }
38339
39533
  };
39534
+ var AgentMonitorService = class {
39535
+ _fleetBus;
39536
+ _events;
39537
+ _transcriptsDir;
39538
+ _maxEntries;
39539
+ _streamEnabled;
39540
+ _onEntry;
39541
+ /** Per-subagent virtual sessions. */
39542
+ _sessions = /* @__PURE__ */ new Map();
39543
+ /** Disposers for FleetBus subscriptions, keyed by subagentId. */
39544
+ _subscriptions = /* @__PURE__ */ new Map();
39545
+ /** Generic fleet-wide subscription disposer. */
39546
+ _fleetDisposer;
39547
+ /** Track whether service is running. */
39548
+ _started = false;
39549
+ constructor(opts) {
39550
+ this._fleetBus = opts.fleetBus;
39551
+ this._events = opts.events;
39552
+ this._transcriptsDir = opts.transcriptsDir;
39553
+ this._maxEntries = opts.maxEntriesPerAgent ?? 500;
39554
+ this._streamEnabled = opts.streamEnabled ?? false;
39555
+ this._onEntry = opts.onEntry;
39556
+ }
39557
+ // ── Public API ────────────────────────────────────────────────────
39558
+ /** Set the FleetBus to listen on. Must be called before `start()`. */
39559
+ setFleetBus(bus) {
39560
+ this._fleetBus = bus;
39561
+ }
39562
+ get streamEnabled() {
39563
+ return this._streamEnabled;
39564
+ }
39565
+ /** Enable/disable streaming agent conversations to the main chat timeline. */
39566
+ setStreamEnabled(enabled) {
39567
+ this._streamEnabled = enabled;
39568
+ }
39569
+ /** Get a snapshot of all known agent sessions. */
39570
+ getAllSessions() {
39571
+ return Array.from(this._sessions.values());
39572
+ }
39573
+ /** Get a specific agent's virtual session, or undefined. */
39574
+ getSession(subagentId) {
39575
+ return this._sessions.get(subagentId);
39576
+ }
39577
+ /** Get transcript entries for a specific agent, newest first. */
39578
+ getTranscript(subagentId, limit = 50) {
39579
+ const session = this._sessions.get(subagentId);
39580
+ if (!session) return [];
39581
+ return session.transcript.slice(-limit).reverse();
39582
+ }
39583
+ /** Set a callback for each new timeline entry (HQ bridge). */
39584
+ setOnEntry(handler) {
39585
+ this._onEntry = handler;
39586
+ }
39587
+ // ── Lifecycle ──────────────────────────────────────────────────────
39588
+ /** Start listening to FleetBus events. */
39589
+ start() {
39590
+ if (this._started) return;
39591
+ if (!this._fleetBus) {
39592
+ this._started = true;
39593
+ return;
39594
+ }
39595
+ this._started = true;
39596
+ this._fleetDisposer = this._fleetBus.onAny((event) => {
39597
+ this._routeEvent(event.subagentId, event.type, event.payload);
39598
+ });
39599
+ }
39600
+ /** Stop listening and clean up all subscriptions. */
39601
+ stop() {
39602
+ if (!this._started) return;
39603
+ this._started = false;
39604
+ if (this._fleetDisposer) {
39605
+ this._fleetDisposer();
39606
+ this._fleetDisposer = void 0;
39607
+ }
39608
+ for (const disposer of this._subscriptions.values()) {
39609
+ disposer();
39610
+ }
39611
+ this._subscriptions.clear();
39612
+ }
39613
+ /** Ensure a subagent is being tracked. Called when a subagent spawns. */
39614
+ trackSubagent(subagentId, agentName, task) {
39615
+ if (this._sessions.has(subagentId)) return;
39616
+ const now = (/* @__PURE__ */ new Date()).toISOString();
39617
+ const session = {
39618
+ subagentId,
39619
+ agentName,
39620
+ createdAt: now,
39621
+ status: "spawned",
39622
+ task,
39623
+ transcript: []
39624
+ };
39625
+ this._sessions.set(subagentId, session);
39626
+ this._addEntry(subagentId, {
39627
+ id: this._uid(),
39628
+ subagentId,
39629
+ agentName,
39630
+ ts: now,
39631
+ kind: "system",
39632
+ content: task ? `\u{1F3AF} Spawned: ${task}` : "\u{1F916} Agent spawned",
39633
+ iteration: 0
39634
+ });
39635
+ this._events.emit("agent.status_changed", {
39636
+ subagentId,
39637
+ agentName,
39638
+ status: "spawned",
39639
+ ts: now,
39640
+ summary: task,
39641
+ task
39642
+ });
39643
+ }
39644
+ /** Mark a subagent as completed/failed/etc. Called on subagent finish. */
39645
+ completeSubagent(subagentId, status, summary) {
39646
+ const session = this._sessions.get(subagentId);
39647
+ if (!session) return;
39648
+ const now = (/* @__PURE__ */ new Date()).toISOString();
39649
+ session.status = status;
39650
+ this._addEntry(subagentId, {
39651
+ id: this._uid(),
39652
+ subagentId,
39653
+ agentName: session.agentName,
39654
+ ts: now,
39655
+ kind: "status",
39656
+ content: summary ?? `Agent ${status}`,
39657
+ iteration: 999
39658
+ });
39659
+ this._events.emit("agent.status_changed", {
39660
+ subagentId,
39661
+ agentName: session.agentName,
39662
+ status,
39663
+ ts: now,
39664
+ summary,
39665
+ task: session.task
39666
+ });
39667
+ }
39668
+ // ── Internal ───────────────────────────────────────────────────────
39669
+ _routeEvent(subagentId, type, payload) {
39670
+ const session = this._sessions.get(subagentId);
39671
+ if (!session) return;
39672
+ switch (type) {
39673
+ case "provider.text_delta": {
39674
+ const text = payload.text;
39675
+ if (!text || text.length === 0) return;
39676
+ const iteration = payload.iteration ?? 0;
39677
+ this._addEntry(subagentId, {
39678
+ id: this._uid(),
39679
+ subagentId,
39680
+ agentName: session.agentName,
39681
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
39682
+ kind: "text",
39683
+ content: text,
39684
+ iteration
39685
+ });
39686
+ break;
39687
+ }
39688
+ case "provider.thinking_delta": {
39689
+ const text = payload.text;
39690
+ if (!text || text.length === 0) return;
39691
+ const iteration = payload.iteration ?? 0;
39692
+ this._addEntry(subagentId, {
39693
+ id: this._uid(),
39694
+ subagentId,
39695
+ agentName: session.agentName,
39696
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
39697
+ kind: "text",
39698
+ content: `\u{1F9E0} ${text}`,
39699
+ iteration
39700
+ });
39701
+ break;
39702
+ }
39703
+ case "tool.started": {
39704
+ const name = payload.name;
39705
+ if (!name) return;
39706
+ this._addEntry(subagentId, {
39707
+ id: this._uid(),
39708
+ subagentId,
39709
+ agentName: session.agentName,
39710
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
39711
+ kind: "tool_use",
39712
+ content: `\u{1F527} ${name}()`,
39713
+ iteration: payload.iteration ?? 0,
39714
+ toolName: name
39715
+ });
39716
+ break;
39717
+ }
39718
+ case "tool.executed": {
39719
+ const name = payload.name;
39720
+ const ok = payload.ok;
39721
+ const durationMs = payload.durationMs;
39722
+ if (!name) return;
39723
+ const statusIcon2 = ok ? "\u2705" : "\u274C";
39724
+ const duration = durationMs !== void 0 ? ` (${durationMs}ms)` : "";
39725
+ this._addEntry(subagentId, {
39726
+ id: this._uid(),
39727
+ subagentId,
39728
+ agentName: session.agentName,
39729
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
39730
+ kind: "tool_result",
39731
+ content: `${statusIcon2} ${name}${duration}`,
39732
+ iteration: payload.iteration ?? 0,
39733
+ toolName: name,
39734
+ toolOk: ok
39735
+ });
39736
+ break;
39737
+ }
39738
+ case "iteration.completed": {
39739
+ const index = payload.index ?? 0;
39740
+ if (index > 0 && index % 5 === 0) {
39741
+ this._addEntry(subagentId, {
39742
+ id: this._uid(),
39743
+ subagentId,
39744
+ agentName: session.agentName,
39745
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
39746
+ kind: "status",
39747
+ content: `\u{1F504} Iteration ${index}`,
39748
+ iteration: index
39749
+ });
39750
+ }
39751
+ break;
39752
+ }
39753
+ }
39754
+ }
39755
+ _addEntry(subagentId, entry) {
39756
+ const session = this._sessions.get(subagentId);
39757
+ if (!session) return;
39758
+ session.transcript.push(entry);
39759
+ if (session.transcript.length > this._maxEntries) {
39760
+ session.transcript.splice(0, session.transcript.length - this._maxEntries);
39761
+ }
39762
+ this._appendToFile(subagentId, entry).catch(() => {
39763
+ });
39764
+ this._events.emit("agent.timeline.message", {
39765
+ subagentId: entry.subagentId,
39766
+ agentName: entry.agentName,
39767
+ content: entry.content,
39768
+ kind: entry.kind === "tool_result" ? "tool_use" : entry.kind === "system" ? "status" : entry.kind,
39769
+ iteration: entry.iteration,
39770
+ ts: entry.ts,
39771
+ toolName: entry.toolName,
39772
+ costUsd: entry.costUsd
39773
+ });
39774
+ this._onEntry?.(entry);
39775
+ }
39776
+ async _appendToFile(subagentId, entry) {
39777
+ const dir = path3.join(this._transcriptsDir, subagentId);
39778
+ await fsp3.mkdir(dir, { recursive: true });
39779
+ const filePath = path3.join(dir, "transcript.jsonl");
39780
+ const line = JSON.stringify(entry) + "\n";
39781
+ await fsp3.appendFile(filePath, line, { encoding: "utf8" });
39782
+ }
39783
+ _uid() {
39784
+ return `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
39785
+ }
39786
+ };
39787
+ function createAgentMonitorService(opts) {
39788
+ return new AgentMonitorService(opts);
39789
+ }
38340
39790
 
38341
39791
  // src/tools/mcp-control.ts
38342
39792
  function createMcpControlTool(opts) {
@@ -38364,11 +39814,18 @@ function createMcpControlTool(opts) {
38364
39814
  };
38365
39815
  return {
38366
39816
  name: "mcp_control",
38367
- description: "Manage MCP server lifecycle: list available servers, search by name or capability, enable or disable servers at runtime, restart running servers. Use activate/deactivate to ephemerally toggle tool registration without disconnecting \u2014 ideal for token-saving mode where MCP tools are lazy-loaded on demand.",
39817
+ description: "Manage MCP server lifecycle: list available servers, search by name or capability, enable or disable servers at runtime, restart running servers. Use activate/deactivate to ephemerally toggle tool registration without disconnecting \u2014 ideal for token-saving mode where MCP tools are lazy-loaded on demand. NOTE: `enable`/`restart` start a server process, which for the built-in stdio presets runs `npx -y <package>` \u2014 i.e. it fetches and executes an npm package from the network. Treat it as code execution.",
38368
39818
  category: "mcp",
38369
39819
  permission: "auto",
38370
39820
  mutating: true,
38371
- riskTier: "standard",
39821
+ // `enable`/`restart` spawn a server process that, for the stdio presets,
39822
+ // fetches and runs an npm package (`npx -y <pkg>`) — effectively remote
39823
+ // code execution. Marking the tool destructive means the YOLO
39824
+ // `confirmDestructive` safety net still prompts before it runs (plain
39825
+ // non-YOLO already confirms via the CONFIG_MUTATE dangerous-capability
39826
+ // net in the executor). Read-only actions (list/search) ride the same
39827
+ // tool but are cheap to confirm/trust once.
39828
+ riskTier: "destructive",
38372
39829
  capabilities: [ToolCapabilities.CONFIG_MUTATE],
38373
39830
  inputSchema,
38374
39831
  async execute(raw) {
@@ -39489,25 +40946,29 @@ function createAgentLoopHandler(a, handlers) {
39489
40946
  const checkMailbox = attachMailboxChecker(a);
39490
40947
  async function compactContextIfNeeded() {
39491
40948
  const msgCount = a.ctx.messages.length;
39492
- if (_lastCompactionMsgCount === msgCount && _lastCompactionWasNoop && _maxContext > 0) {
40949
+ const maxContext = currentMaxContext();
40950
+ if (_lastCompactionMsgCount === msgCount && _lastCompactionWasNoop && _lastCompactionMaxContext === maxContext && maxContext > 0) {
39493
40951
  return;
39494
40952
  }
39495
40953
  await a.pipelines.contextWindow.run(a.ctx);
39496
40954
  _lastCompactionMsgCount = msgCount;
40955
+ _lastCompactionMaxContext = maxContext;
39497
40956
  const stashed = a.ctx.lastRequestTokens;
39498
40957
  const tokens = typeof stashed === "number" && stashed > 0 ? stashed : 0;
39499
- const load = _maxContext > 0 ? tokens / _maxContext : 0;
40958
+ const load = maxContext > 0 ? tokens / maxContext : 0;
39500
40959
  _lastCompactionWasNoop = tokens > 0 && load < 0.5;
39501
40960
  }
39502
40961
  const calibrationKey = (model = a.ctx.model) => `${a.ctx.provider?.id ?? "unknown"}/${model}`;
39503
40962
  function emitContextPct() {
39504
40963
  const msgCount = a.ctx.messages.length;
39505
40964
  const toolCount = (a.ctx.tools ?? []).length;
39506
- if (msgCount === _lastEmittedMsgCount && toolCount === _lastEmittedToolCount && _maxContext > 0) {
40965
+ const maxContext = currentMaxContext();
40966
+ if (msgCount === _lastEmittedMsgCount && toolCount === _lastEmittedToolCount && maxContext === _lastEmittedMaxContext && maxContext > 0) {
39507
40967
  return;
39508
40968
  }
39509
40969
  _lastEmittedMsgCount = msgCount;
39510
40970
  _lastEmittedToolCount = toolCount;
40971
+ _lastEmittedMaxContext = maxContext;
39511
40972
  if (msgCount !== _lastPreFlightMsgCount) {
39512
40973
  a.ctx.lastRequestTokens = estimateRequestTokens(
39513
40974
  a.ctx.messages,
@@ -39517,11 +40978,6 @@ function createAgentLoopHandler(a, handlers) {
39517
40978
  _lastPreFlightMsgCount = msgCount;
39518
40979
  a.ctx.meta["lastRequestTokensAt"] = { msgCount, toolCount };
39519
40980
  }
39520
- if (!_maxContext) {
39521
- const metaLimit = a.ctx.meta?.["effectiveMaxContext"];
39522
- const providerMax = a.ctx.provider.capabilities.maxContext;
39523
- _maxContext = typeof metaLimit === "number" && metaLimit > 0 ? metaLimit : typeof providerMax === "number" && providerMax > 0 ? providerMax : 2e5;
39524
- }
39525
40981
  let total;
39526
40982
  const stashed = a.ctx.lastRequestTokens;
39527
40983
  if (typeof stashed === "number" && stashed > 0) {
@@ -39536,13 +40992,19 @@ function createAgentLoopHandler(a, handlers) {
39536
40992
  );
39537
40993
  total = est.total;
39538
40994
  }
39539
- a.events.emit("ctx.pct", { load: total / _maxContext, tokens: total, maxContext: _maxContext });
40995
+ a.events.emit("ctx.pct", { load: total / maxContext, tokens: total, maxContext });
40996
+ }
40997
+ function currentMaxContext() {
40998
+ const metaLimit = a.ctx.meta?.["effectiveMaxContext"];
40999
+ const providerMax = a.ctx.provider.capabilities.maxContext;
41000
+ return typeof metaLimit === "number" && metaLimit > 0 ? metaLimit : typeof providerMax === "number" && providerMax > 0 ? providerMax : 2e5;
39540
41001
  }
39541
- let _maxContext = 0;
39542
41002
  let _lastEmittedMsgCount = -1;
39543
41003
  let _lastEmittedToolCount = -1;
41004
+ let _lastEmittedMaxContext = -1;
39544
41005
  let _lastPreFlightMsgCount = -1;
39545
41006
  let _lastCompactionMsgCount = -1;
41007
+ let _lastCompactionMaxContext = -1;
39546
41008
  let _lastCompactionWasNoop = false;
39547
41009
  function foldBlockIntoConversation(block) {
39548
41010
  const messages = a.ctx.messages;
@@ -39612,7 +41074,8 @@ function createAgentLoopHandler(a, handlers) {
39612
41074
  ts: (/* @__PURE__ */ new Date()).toISOString(),
39613
41075
  content: inputPayload.content
39614
41076
  });
39615
- await a.ctx.session.flush();
41077
+ void a.ctx.session.flush().catch(() => {
41078
+ });
39616
41079
  const promptIndex = a.ctx.messages.filter((m) => m.role === "user").length - 1;
39617
41080
  const preview = inputPayload.text.slice(0, 80) + (inputPayload.text.length > 80 ? "\u2026" : "");
39618
41081
  await a.ctx.session.writeCheckpoint(promptIndex, preview);
@@ -40365,14 +41828,20 @@ var HookRunner = class {
40365
41828
  };
40366
41829
 
40367
41830
  // src/execution/model-runtime.ts
40368
- function resolveModelRuntime(settings, reasoning) {
41831
+ function resolveModelRuntime(settings, reasoning, capabilities) {
40369
41832
  const warnings = [];
40370
41833
  if (!settings) {
40371
- return { reasoning: void 0, cache: void 0, warnings };
41834
+ return { reasoning: void 0, cache: void 0, parameters: void 0, warnings };
40372
41835
  }
40373
41836
  const reasoningField = resolveReasoningForRequest(settings, reasoning, warnings);
40374
41837
  const cacheField = resolveCacheForRequest(settings);
40375
- return { reasoning: reasoningField, cache: cacheField, warnings };
41838
+ const paramsField = resolveParametersForRequest(settings.parameters, capabilities);
41839
+ return {
41840
+ reasoning: reasoningField,
41841
+ cache: cacheField,
41842
+ parameters: paramsField,
41843
+ warnings
41844
+ };
40376
41845
  }
40377
41846
  function resolveReasoningForRequest(settings, rc, warnings) {
40378
41847
  const cfg = settings.reasoning;
@@ -40430,11 +41899,38 @@ function resolveCacheForRequest(settings, _warnings) {
40430
41899
  const out = { ttl };
40431
41900
  return out;
40432
41901
  }
41902
+ function resolveParametersForRequest(params, caps, _warnings) {
41903
+ if (!params) return void 0;
41904
+ const out = {};
41905
+ if (params.topK !== void 0 && caps?.topK !== false) {
41906
+ out.topK = params.topK;
41907
+ }
41908
+ if (params.frequencyPenalty !== void 0 && caps?.frequencyPenalty !== false) {
41909
+ out.frequencyPenalty = params.frequencyPenalty;
41910
+ }
41911
+ if (params.presencePenalty !== void 0 && caps?.presencePenalty !== false) {
41912
+ out.presencePenalty = params.presencePenalty;
41913
+ }
41914
+ if (params.seed !== void 0 && caps?.seed !== false) {
41915
+ out.seed = params.seed;
41916
+ }
41917
+ if (params.user !== void 0) {
41918
+ out.user = params.user;
41919
+ }
41920
+ if (params.logprobs !== void 0 && caps?.logprobs !== false) {
41921
+ out.logprobs = params.logprobs;
41922
+ if (params.topLogprobs !== void 0) {
41923
+ out.topLogprobs = params.topLogprobs;
41924
+ }
41925
+ }
41926
+ return Object.keys(out).length > 0 ? out : void 0;
41927
+ }
40433
41928
  function applyModelRuntime(req, opts) {
40434
41929
  const settings = opts.getSettings();
40435
41930
  if (!settings) return req;
40436
41931
  const rc = opts.getReasoningConfig();
40437
- const resolved = resolveModelRuntime(settings, rc);
41932
+ const caps = opts.getCapabilities?.();
41933
+ const resolved = resolveModelRuntime(settings, rc, caps);
40438
41934
  for (const w of resolved.warnings) opts.onWarning?.(w);
40439
41935
  const next = { ...req };
40440
41936
  if (resolved.reasoning !== void 0) {
@@ -40443,6 +41939,9 @@ function applyModelRuntime(req, opts) {
40443
41939
  if (resolved.cache !== void 0) {
40444
41940
  next.cache = resolved.cache;
40445
41941
  }
41942
+ if (resolved.parameters !== void 0) {
41943
+ Object.assign(next, resolved.parameters);
41944
+ }
40446
41945
  return next;
40447
41946
  }
40448
41947
  async function bootConfig(options = {}) {
@@ -40765,8 +42264,8 @@ var InputBuilder = class {
40765
42264
  async registerFile(input) {
40766
42265
  const ref = await this.store.add({ ...input, kind: "file" });
40767
42266
  this.refs.push(ref);
40768
- const path48 = ref.meta.filename ?? ref.meta.label ?? String(ref.seq);
40769
- return `[file:${path48}]`;
42267
+ const path50 = ref.meta.filename ?? ref.meta.label ?? String(ref.seq);
42268
+ return `[file:${path50}]`;
40770
42269
  }
40771
42270
  /**
40772
42271
  * Whether `appendPaste(text)` would collapse the text to a placeholder
@@ -40915,9 +42414,9 @@ var DefaultSystemPromptBuilder = class {
40915
42414
  skillBodyCache;
40916
42415
  /** Tools from last build — used for memory relevance scoring. */
40917
42416
  _lastBuildTools;
40918
- /** Cached rendered online agents string, keyed by array reference. */
42417
+ /** Cached rendered online agents string, keyed by content fingerprint. */
40919
42418
  _lastOnlineAgents;
40920
- /** Cached full buildToolUsage output — keyed by tools array + online agents refs. */
42419
+ /** Cached full buildToolUsage output — keyed by tools array ref + agents fingerprint. */
40921
42420
  _toolsUsageCache;
40922
42421
  /**
40923
42422
  * Normalizes `tokenSavingMode` to a boolean for backward-compatible boolean checks.
@@ -41053,13 +42552,13 @@ var DefaultSystemPromptBuilder = class {
41053
42552
  if (!planPath) return "";
41054
42553
  let raw;
41055
42554
  try {
41056
- const stat15 = await fsp3.stat(planPath);
41057
- if (this._planCache && this._planCache.path === planPath && this._planCache.mtimeMs === stat15.mtimeMs) {
42555
+ const stat16 = await fsp3.stat(planPath);
42556
+ if (this._planCache && this._planCache.path === planPath && this._planCache.mtimeMs === stat16.mtimeMs) {
41058
42557
  return this._planCache.text;
41059
42558
  }
41060
42559
  raw = await fsp3.readFile(planPath, "utf8");
41061
42560
  const text = this._formatPlan(raw);
41062
- this._planCache = { path: planPath, mtimeMs: stat15.mtimeMs, text };
42561
+ this._planCache = { path: planPath, mtimeMs: stat16.mtimeMs, text };
41063
42562
  return text;
41064
42563
  } catch {
41065
42564
  this._planCache = void 0;
@@ -41074,8 +42573,8 @@ var DefaultSystemPromptBuilder = class {
41074
42573
  return "";
41075
42574
  }
41076
42575
  if (!Array.isArray(parsed.items) || parsed.items.length === 0) return "";
41077
- const open7 = parsed.items.filter((i) => i?.status !== "done");
41078
- if (open7.length === 0) return "";
42576
+ const open10 = parsed.items.filter((i) => i?.status !== "done");
42577
+ if (open10.length === 0) return "";
41079
42578
  const lines = ["## Active plan"];
41080
42579
  if (parsed.title) lines.push(`*${parsed.title}*`, "");
41081
42580
  parsed.items.forEach((it, idx) => {
@@ -41090,7 +42589,8 @@ var DefaultSystemPromptBuilder = class {
41090
42589
  }
41091
42590
  buildToolUsage(tools, ctx) {
41092
42591
  if (tools.length === 0) return "## Tool usage\n\nNo tools registered.";
41093
- if (this._toolsUsageCache?.toolsRef === tools && this._toolsUsageCache?.agentsRef === ctx.onlineAgents) {
42592
+ const agentsHash = this.agentsFingerprint(ctx.onlineAgents);
42593
+ if (this._toolsUsageCache?.toolsRef === tools && this._toolsUsageCache?.agentsHash === agentsHash) {
41094
42594
  return this._toolsUsageCache.text;
41095
42595
  }
41096
42596
  const byCat = /* @__PURE__ */ new Map();
@@ -41377,7 +42877,7 @@ the server connection \u2014 only tool visibility changes.`);
41377
42877
 
41378
42878
  Use \`context_manager\` to manage context. Call \`{"action":"check"}\` to see token budget.`);
41379
42879
  } else {
41380
- const maxCtx = this.opts.modelCapabilities?.maxContextTokens ?? 128e3;
42880
+ const maxCtx = this.modelCapabilities()?.maxContextTokens ?? 128e3;
41381
42881
  const threshold = maxCtx <= 32e3 ? "50" : "70";
41382
42882
  lines.push(`
41383
42883
  ## Context management
@@ -41395,15 +42895,38 @@ summarize it, and let the tool result hold only the summary.`);
41395
42895
  }
41396
42896
  }
41397
42897
  const text = lines.join("\n");
41398
- this._toolsUsageCache = { toolsRef: tools, agentsRef: ctx.onlineAgents, text };
42898
+ this._toolsUsageCache = { toolsRef: tools, agentsHash, text };
41399
42899
  return text;
41400
42900
  }
41401
42901
  /**
41402
- * Render the online agents list, cached by array reference. The agents
42902
+ * Cheap content fingerprint of the online agents array. The mailbox
42903
+ * rebuilds the array as a fresh object on every status check, so caching
42904
+ * by reference always misses — this lets the renderOnlineAgents and
42905
+ * buildToolUsage caches detect membership changes instead.
42906
+ *
42907
+ * O(n) over agent names with no per-element string concatenation. Uses
42908
+ * FNV-1a over character codes so two different agent sets collide only
42909
+ * if they produce the identical sequence of name characters — astronomically
42910
+ * unlikely. A collision would produce a stale agent list in the prompt,
42911
+ * a cosmetic issue, not a correctness bug.
42912
+ */
42913
+ agentsFingerprint(agents) {
42914
+ if (!agents || agents.length === 0) return "0";
42915
+ let h = 2166136261;
42916
+ for (const a of agents) {
42917
+ for (let i = 0; i < a.name.length; i++) {
42918
+ h ^= a.name.charCodeAt(i);
42919
+ h = Math.imul(h, 16777619) >>> 0;
42920
+ }
42921
+ }
42922
+ return `${agents.length}:${h.toString(36)}`;
42923
+ }
42924
+ /**
42925
+ * Render the online agents list, cached by content fingerprint. The agents
41403
42926
  * list changes at join/leave pace (seconds to minutes), not every prompt
41404
- * build turn (hundreds of ms). Reference equality avoids re-stringifying
41405
- * the same array on every iteration while still being correct when the
41406
- * caller passes a fresh array.
42927
+ * build turn (hundreds of ms). The fingerprint detects membership changes
42928
+ * without holding the array reference the mailbox rebuilds the array as
42929
+ * a fresh object on every status check, so reference equality always misses.
41407
42930
  *
41408
42931
  * Tier behaviour:
41409
42932
  * - 'off' / 'medium' / 'aggressive' → full list with names, sessions, sources
@@ -41411,13 +42934,14 @@ summarize it, and let the tool result hold only the summary.`);
41411
42934
  */
41412
42935
  renderOnlineAgents(agents) {
41413
42936
  if (!agents || agents.length === 0) return "";
41414
- if (this._lastOnlineAgents?.ref === agents) {
42937
+ const hash = this.agentsFingerprint(agents);
42938
+ if (this._lastOnlineAgents?.hash === hash) {
41415
42939
  return this._lastOnlineAgents.text;
41416
42940
  }
41417
42941
  const totalCount = agents.length;
41418
42942
  if (this.tier === "minimal" || this.tier === "light") {
41419
42943
  const text2 = ` (${totalCount} agent${totalCount !== 1 ? "s" : ""} online)`;
41420
- this._lastOnlineAgents = { ref: agents, text: text2 };
42944
+ this._lastOnlineAgents = { hash, text: text2 };
41421
42945
  return text2;
41422
42946
  }
41423
42947
  const agentList = agents.map(
@@ -41427,11 +42951,21 @@ summarize it, and let the tool result hold only the summary.`);
41427
42951
 
41428
42952
  **Currently online (${totalCount} agent${totalCount !== 1 ? "s" : ""}):**
41429
42953
  ${agentList}`;
41430
- this._lastOnlineAgents = { ref: agents, text };
42954
+ this._lastOnlineAgents = { hash, text };
41431
42955
  return text;
41432
42956
  }
41433
42957
  async buildEnvironment(ctx) {
41434
- const cached = this.envCacheByRoot.get(ctx.projectRoot);
42958
+ const modelCapabilities = this.modelCapabilities();
42959
+ const cacheKey = [
42960
+ ctx.projectRoot,
42961
+ ctx.provider ?? "",
42962
+ ctx.model ?? "",
42963
+ modelCapabilities?.maxContextTokens ?? 0,
42964
+ modelCapabilities?.supportsTools ? 1 : 0,
42965
+ modelCapabilities?.supportsVision ? 1 : 0,
42966
+ modelCapabilities?.supportsReasoning ? 1 : 0
42967
+ ].join("\0");
42968
+ const cached = this.envCacheByRoot.get(cacheKey);
41435
42969
  if (cached) return cached;
41436
42970
  const today = this.opts.todayIso ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
41437
42971
  const platform2 = `${os6.platform()} ${os6.release()}`;
@@ -41463,15 +42997,15 @@ ${agentList}`;
41463
42997
  `- Running on: ${ctx.provider ?? "<unknown provider>"}/${ctx.model ?? "<unknown model>"}`
41464
42998
  );
41465
42999
  }
41466
- if (this.opts.modelCapabilities) {
43000
+ if (modelCapabilities) {
41467
43001
  lines.push(
41468
- `- Context window: ${this.opts.modelCapabilities.maxContextTokens.toLocaleString()} tokens max`
43002
+ `- Context window: ${modelCapabilities.maxContextTokens.toLocaleString()} tokens max`
41469
43003
  );
41470
43004
  }
41471
43005
  }
41472
- if (tier !== "aggressive" && this.opts.modelCapabilities) {
43006
+ if (tier !== "aggressive" && modelCapabilities) {
41473
43007
  lines.push(
41474
- `- Context window: ${this.opts.modelCapabilities.maxContextTokens.toLocaleString()} tokens max`
43008
+ `- Context window: ${modelCapabilities.maxContextTokens.toLocaleString()} tokens max`
41475
43009
  );
41476
43010
  }
41477
43011
  if (tier !== "aggressive" && (ctx.provider || ctx.model)) {
@@ -41493,9 +43027,13 @@ ${agentList}`;
41493
43027
  );
41494
43028
  }
41495
43029
  const text = lines.join("\n");
41496
- this.envCacheByRoot.set(ctx.projectRoot, text);
43030
+ this.envCacheByRoot.set(cacheKey, text);
41497
43031
  return text;
41498
43032
  }
43033
+ modelCapabilities() {
43034
+ const caps = this.opts.modelCapabilities;
43035
+ return typeof caps === "function" ? caps() : caps;
43036
+ }
41499
43037
  async buildMemoryAndSkills() {
41500
43038
  const parts = [];
41501
43039
  const memoryCount = this.tier === "minimal" ? 3 : this.tier === "light" ? 5 : 8;
@@ -41621,8 +43159,8 @@ ${clean.trim()}`);
41621
43159
  }
41622
43160
  async dirExists(p) {
41623
43161
  try {
41624
- const stat15 = await fsp3.stat(p);
41625
- return stat15.isDirectory();
43162
+ const stat16 = await fsp3.stat(p);
43163
+ return stat16.isDirectory();
41626
43164
  } catch {
41627
43165
  return false;
41628
43166
  }
@@ -42554,18 +44092,21 @@ Rules:
42554
44092
  - Be concise: one tight instruction per version (a few sentences at most). No preamble, no explanation, no quotes, no markdown headers.
42555
44093
  - If the message is already clear and complete, return it essentially unchanged.
42556
44094
 
42557
- You MUST output TWO versions of the refined request, separated by a line containing only "---".
42558
- - First version: refined in the SAME LANGUAGE the user wrote in (if Turkish \u2192 Turkish, if Spanish \u2192 Spanish, etc.).
42559
- - Second version: refined in ENGLISH (translate the intent into clear English while preserving all concrete details).
44095
+ Detect the language of the user's LATEST message and output accordingly:
44096
+
44097
+ - If that message is ALREADY in English: output exactly ONE refined version, in English. Nothing else \u2014 no "---" line, no second copy.
44098
+ - If that message is in ANY OTHER language (Turkish, Spanish, \u2026): output TWO versions separated by a line containing only "---":
44099
+ - First version: refined in the SAME LANGUAGE the user wrote in.
44100
+ - Second version: refined in ENGLISH (translate the intent into clear English while preserving all concrete details).
42560
44101
 
42561
- Output format:
44102
+ Output format for non-English input:
42562
44103
  <refined in user's language>
42563
44104
  ---
42564
44105
  <refined in English>
42565
44106
 
42566
- When earlier conversation turns are provided, they are CONTEXT ONLY. Use them to resolve references in the user's latest message \u2014 "it", "that", "the same", "the other one", "this file", "again" \u2014 so the refined instruction is self-contained. Refine ONLY the user's latest message; do not answer it, do not act on or restate earlier turns, and do not summarize the conversation.
44107
+ When earlier conversation turns are provided, they are CONTEXT ONLY. Use them to resolve references in the user's latest message \u2014 "it", "that", "the same", "the other one", "this file", "again" \u2014 so the refined instruction is self-contained. Refine ONLY the user's latest message; do not answer it, do not act on or restate earlier turns, and do not summarize the conversation. The conversation language does NOT decide the output language \u2014 only the language of the latest message does.
42567
44108
 
42568
- Output ONLY the two versions separated by "---" \u2014 nothing else.`;
44109
+ Output ONLY the refined request(s) in the format above \u2014 nothing else.`;
42569
44110
  var AFFIRMATION_RE = /^(y|n|yes|no|yep|nope|ok|okay|sure|go|go ahead|continue|proceed|stop|cancel|done|next|skip|retry|again|please do|do it)\b[.! ]*$/i;
42570
44111
  function shouldEnhance(text) {
42571
44112
  const t2 = text.trim();
@@ -42578,6 +44119,24 @@ function shouldEnhance(text) {
42578
44119
  if (words.length < 3) return false;
42579
44120
  return true;
42580
44121
  }
44122
+ var EFFORT_PREFERENCE = [
44123
+ "low",
44124
+ "minimal",
44125
+ "medium",
44126
+ "high",
44127
+ "xhigh",
44128
+ "max",
44129
+ "none"
44130
+ ];
44131
+ function gatedEnhancerReasoning(rc) {
44132
+ if (!rc) return void 0;
44133
+ if (rc.effortSupported && rc.effortLevels.length > 0) {
44134
+ const lowest = EFFORT_PREFERENCE.find((e) => rc.effortLevels.includes(e)) ?? rc.effortLevels[0];
44135
+ if (lowest) return { effort: lowest };
44136
+ }
44137
+ if (rc.disableSupported) return { enabled: false };
44138
+ return void 0;
44139
+ }
42581
44140
  function normalizedEqual(a, b) {
42582
44141
  const norm = (s) => s.trim().replace(/\s+/g, " ").toLowerCase();
42583
44142
  return norm(a) === norm(b);
@@ -42601,11 +44160,17 @@ async function enhanceUserPrompt(opts) {
42601
44160
  model,
42602
44161
  system: [{ type: "text", text: ENHANCER_SYSTEM_PROMPT }],
42603
44162
  messages: [{ role: "user", content: buildRefinerInput(text, opts.history) }],
42604
- maxTokens
44163
+ maxTokens,
42605
44164
  // NOTE: deliberately NO `temperature`. The main agent loop never sets it,
42606
44165
  // and reasoning models (DeepSeek reasoner, o1/o3, …) return HTTP 400 when
42607
44166
  // `temperature` is present — which would make every refine call fail and
42608
44167
  // silently fall back to the original (no panel shown).
44168
+ //
44169
+ // A reasoning hint is forwarded ONLY when the caller supplies one (it must
44170
+ // already be gated to the model's advertised support — see
44171
+ // `gatedEnhancerReasoning`). Absent it, no reasoning field is sent, which
44172
+ // is identical to the original behavior.
44173
+ ...opts.reasoning ? { reasoning: opts.reasoning } : {}
42609
44174
  };
42610
44175
  const timer = new AbortController();
42611
44176
  const to = setTimeout(() => timer.abort(new Error("enhancer timeout")), timeoutMs);
@@ -42619,7 +44184,6 @@ async function enhanceUserPrompt(opts) {
42619
44184
  }
42620
44185
  const sepIdx = raw.indexOf("\n---\n");
42621
44186
  if (sepIdx === -1) {
42622
- opts.onError?.("model did not produce two versions");
42623
44187
  return { refined: raw, english: raw };
42624
44188
  }
42625
44189
  const refined = raw.slice(0, sepIdx).trim();
@@ -44573,6 +46137,24 @@ var CollaborationBus = class {
44573
46137
  // "skip the bash call, just give it the answer I typed". The
44574
46138
  // injection is matched by tool_use_id, consumed once, and discarded.
44575
46139
  injectionQueue = /* @__PURE__ */ new Map();
46140
+ onConsumed;
46141
+ /**
46142
+ * Register a listener fired when `collabInjectMiddleware` actually splices a
46143
+ * queued injection into a tool call (Phase 4 feedback loop). The webui's
46144
+ * CollaborationWebSocketHandler uses it to broadcast a
46145
+ * `collab.injection.granted` with phase `'consumed'` so observers learn the
46146
+ * injection was applied (and to which real tool). Last registration wins.
46147
+ */
46148
+ onInjectionConsumed(fn) {
46149
+ this.onConsumed = fn;
46150
+ }
46151
+ /**
46152
+ * Invoked by `collabInjectMiddleware` immediately after it replaces a tool
46153
+ * call's result with a queued injection. No-op when no listener is set.
46154
+ */
46155
+ notifyInjectionConsumed(info) {
46156
+ this.onConsumed?.(info);
46157
+ }
44576
46158
  /**
44577
46159
  * Queue a manual tool result. The next time the agent's toolCall
44578
46160
  * pipeline sees a matching `toolUse.id`, the
@@ -44967,8 +46549,8 @@ function extractManifestPath(msg) {
44967
46549
  }
44968
46550
  return void 0;
44969
46551
  }
44970
- function isManifestFile(path48) {
44971
- const name = pathBasename(path48).toLowerCase();
46552
+ function isManifestFile(path50) {
46553
+ const name = pathBasename(path50).toLowerCase();
44972
46554
  const manifests = [
44973
46555
  "package.json",
44974
46556
  "package-lock.json",
@@ -45068,6 +46650,13 @@ function collabInjectMiddleware(bus, opts = {}) {
45068
46650
  content: typeof injected.content === "string" ? injected.content : JSON.stringify(injected.content),
45069
46651
  is_error: injected.isError
45070
46652
  };
46653
+ bus.notifyInjectionConsumed({
46654
+ toolUseId: payload.toolUse.id,
46655
+ toolName: payload.toolUse.name,
46656
+ authorId: injected.authorId,
46657
+ reason: injected.reason,
46658
+ isError: injected.isError
46659
+ });
45071
46660
  };
45072
46661
  }
45073
46662
 
@@ -46471,6 +48060,6 @@ function createChimeraPlugin() {
46471
48060
  };
46472
48061
  }
46473
48062
 
46474
- export { ACP_AGENTS, AGENTS_BY_PHASE, AGENT_CATALOG, TOOLS as AGENT_TOOL_PRESETS, AISpecBuilder, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, ALL_SYNC_CATEGORIES, AUDIT_LOG_AGENT, Agent, AgentError, AgentStatusTracker, AnnotationsStore, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutoPhasePlanner, AutoPhaseRunner, AutonomousBrain, AutonomousCoordinator, AutonomousRunner, BUG_HUNTER_AGENT, BUILD_AGENTS, BrainDecisionQueue, BrainMonitor, BudgetExceededError, BudgetThresholdSignal, CHIMERA_REVIEW_PROMPT, CONTEXT_WINDOW_MODES, CORE_RECONSTRUCT_EVENTS, ChangeManager, CheckpointManager, CloudSync, CollabSession, CollaborationBus, ConfigError, ConfigMigrationError, ConsensusProtocol, Container, Context, ConversationState, DANGEROUS_FOR_SUBAGENTS, DECISION_TIMEOUT_MS, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CIRCUIT_BREAKER_CONFIG, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_CONFIG, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_HQ_REDACTION_POLICY, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_QUALITY_CHECKS, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SESSION_LOGGING_CONFIG, DEFAULT_SESSION_PRUNE_DAYS, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DEFAULT_TOOLS_CONFIG, DEFAULT_TUI_THINKING_WORD, DELIVERY_AGENTS, DEPENDENCY_FILE_PATTERNS, DISCOVERY_AGENTS, DOMAIN_AGENTS, DefaultAttachmentStore, DefaultBrainArbiter, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMailbox, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultPromptStore, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DirectorAlertLevel, DirectorStateCheckpoint, DoneConditionChecker, ENHANCER_SYSTEM_PROMPT, ERROR_CODES, EternalAutonomyEngine, EventBus, ExtensionRegistry, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FLEET_ROSTER_WITHACP, FORBIDDEN_PROTO_KEYS, FileMemoryBackend, FleetBus, FleetCostCapError, FleetManager, FleetNotifier, FleetSpawnBudgetError, FleetUsageAggregator, FsError, GitignoreUpdater, GlobalMailbox, GraphMemoryBackend, HEAVY_BUDGET, HQ_AUTH_FILE_VERSION, HQ_PROTOCOL_VERSION, HookRegistry, HookRunner, HqPublisher, HumanEscalatingBrainArbiter, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, KNOWLEDGE_AGENTS, KnowledgeGraph, LAYER_1_IDENTITY, LIGHT_BUDGET, LLMSelector, LargeAnswerStore, MATRIX_PHASE_KEYS, MAX_JOURNAL_ENTRIES, MAX_PROGRESS_HISTORY, MAX_TUI_THINKING_WORD_LENGTH, MEDIUM_BUDGET, MEMORY_TYPE_LABELS, META_AGENTS, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, ObservableBrainArbiter, PLANNING_AGENTS, PROMETHEUS_CONTENT_TYPE, ParallelEternalEngine, PhaseGraphBuilder, PhaseOrchestrator, PhaseStore, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, REFACTOR_PLANNER_AGENT, REVIEW_AGENTS, RecoveryLock, ReplayLogStore, ReplayProviderRunner, ReportGenerator, RunController, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, STANDARD_AUDIT_EVENTS, ScopedEventBus, SddError, SddParallelRun, SddTaskDecomposer, SecurityScanner, SecurityScannerOrchestrator, SelectiveCompactor, SessionAnalyzer, SessionError, SessionMemoryConsolidator, SessionRecovery, SessionRegistry, SkillGenerator, SkillInstaller, SkillManifestStore, SlashCommandRegistry, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, StreamHangError, SubagentBudget, TIMEOUT_PREEMPT_FRACTION, TOKENS, TaskAuctioneer, TaskDAG, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, TechStackDetector, ToolAuditLog, ToolCapabilities, ToolError, ToolErrorCategory, ToolExecutor, ToolRegistry, VERIFY_AGENTS, WIDE_SUBAGENT_CAPABILITIES, WorktreeManager, WrongStackError, addPlanItem, allServers, analyzeCriticalPath, appendJournal, applyModelRuntime, applyRosterBudget, asBlocks, asText, assertNever, assertNotPrivateHost, assertSafePath, assessCommitSafety, atomicWrite, attachAutoExtend, attachDepWatcherBridge, attachMailboxChecker, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, bootConfig, braveSearchServer, buildBtwBlock, buildChildEnv, buildContextEvidenceDigest, buildGoalPreamble, buildLosslessDigest, buildMailboxBlock, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildQueuedMessagesBlock, buildRecoveryStrategies, buildSmartDigest, classifyFamily, clearPlan, collabInjectMiddleware, collabPauseMiddleware, color, compactLog, compactSchemaDescriptions, compactToolDefinitionForWire, compileGlob, compileUserRegex, completePartialObject, composeDirectorPrompt, composeSubagentPrompt, computeMessageTokens, computeTaskItemProgress, computeTaskProgress, consumeBtwNotes, consumeQueuedMessagesUpdate, context7Server, contextManagerTool, createAutoExecutor, createAutoPhaseFromTaskGraph, createAutonomyBrain, createChimeraPlugin, createContextEvidenceState, createContextManagerTool, createDefaultPipelines, createDelegateTool, createGitPlugin, createGlobalMailbox, createHqEventEnvelope, createHqPublisherFromEnv, createMailboxChecker, createMailboxEventPayload, createMailboxHooks, createMailboxSnapshotPayload, createMailboxSnapshotPayloadFromMailbox, createMcpControlTool, createMcpUseTool, createMessage, createObservabilityPlugin, createPlanPlugin, createPromptsPlugin, createSecurityPlugin, createSecuritySlashCommand, createSessionEventBridge, createSkillsPlugin, createStrategyCompactor, createSyncPlugin, createTieredBrainArbiter, createToolOutputSerializer, decryptConfigSecrets, deepMerge, defaultGitignoreUpdater, defaultHqDataDir, defaultOrchestrator, defaultReportGenerator, defaultSecurityScanner, defaultSkillGenerator, defaultTechStackDetector, definePlugin, deriveTodosFromPlanItem, describeCatalogModel, detectEcosystem, detectNewlineStyle, detectEcosystem as detectPackageEcosystem, dispatchAgent, downloadGitHubTarball, eliseOldToolResults, emptyGoal, emptyHqAuthFile, emptyPlan, emptyTaskFile, encryptConfigSecrets, encryptedPrefixForVersion, enhanceUserPrompt, ensureDir, ensureHqFirstRunAuthFile, escapeGlobSubject, estimateMessageTokens, estimateMessages, estimateRequestTokens, estimateRequestTokensCalibrated, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, expandGlob, expandIPv6, expectDefined, extractRunEnv, extractText, filesystemServer, findCriticalPath, findPreserveStart, flagsToConfigPatch, formatContextWindowModeList, formatDecisionSummary, formatGoal, formatHumanPrompt, formatPlan, formatPlanTemplates, formatTaskList, formatTaskProgress, formatTodosList, getAgentDefinition, getCalibrationState, getContextWindowMode, getDangerousCapabilities, getFileHistory, getFilesByAgent, getFullLog, getFullPackageLog, getJsonPath, getLastAuthor, getManifestPackages, getPackageAuthor, getPackagesByAgent, getPlanTemplate, getSessionRegistry, getTemplate, getTermSize, githubServer, goalFilePath, googleMapsServer, hasCapability, hasDangerousCapabilityForSubagents, hasSessionRegistry, hasTextContent, hashRequest, hookMatcherMatches, hqAuthFilePath, hqRuntimeFilePath, injectPendingMailboxMessages, isAgentError, isConfigError, isContextWindowModeId, isFsError, isImageBlock, isInteractive, isJsonObject, isPathSubjectKey, isPluginError, isPrimitiveArray, isPrivateIPv4, isPrivateIPv6, isSddError, isSecretField, isSessionError, isStdinTTY, isStdoutTTY, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isValidMatrixKey, isWrongStackError, jsonObjectFileExists, listContextWindowModes, listPlanTemplates, listTemplates, loadDirectorState, loadGoal, loadPlan, loadPlugins, loadProjectModes, loadTasks, loadTodosCheckpoint, loadUserModes, mailboxSessionTag, makeAgentSubagentRunner, makeAskResultTool, makeAskTool, makeAssignTool, makeAutonomyPromptContributor, makeAwaitTasksTool, makeCollabDebugTool, makeContinueToNextIterationTool, makeDependencyWatcherConfig, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeMailInboxTool, makeMailSendTool, makeMailboxTool, makeRollUpTool, makeSpawnTool, makeTerminateTool, makeWorkCompleteTool, mapMailboxAgentToHqSummary, mapMailboxMessageToHqSummary, markAssistantReferencedEvidence, matchAny, matchGlob, matrixKeyKind, mergeCustomModelDefs, mergeModelsPayload, migratePlaintextSecrets, miniMaxVisionServer, mintHqBrowserToken, mintHqToken, mutateHqAuthFile, mutatePlan, mutateTasks, noOpLogger, noOpVault, normalizePathSubject, normalizeRecipient, normalizeToLf, normalizeTokenSavingTier, normalizeTuiThinkingWord, normalizedEqual, onResize, parseContinueDirective, parseEncryptedVersion, parseEntries, parseHqEventPayload, parseHqFrame, parseProgressFromText, parseSkillRef, peekQueuedMessages, pendingBtwCount, phaseForRole, playwrightServer, projectHash, projectSlug, readHqAuthFile, readHqRuntimeFileSync, readJsonObjectFile, recentTextTurns, recordActualUsage, recordFileAction, recordPackageAction, recordProgress, recordToolOutputEvidence, recordUserIntentEvidence, redactHqEvent, redactHqValue, removeJsonPath, removeJsonPathInFile, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, repairToolUseAdjacency, repeatedReadPressure, resetCalibration, resolveAuditLevel, resolveCacheForRequest, resolveChimeraConfig, resolveContextWindowPolicy, resolveHqConfig, resolveHqConfigFromEnv, resolveHqDataDir, resolveMailboxIdentity, resolveModelMatrix, resolveModelRuntime, resolveProjectDir, resolveProviderModelList, resolveReasoningForRequest, resolveSessionLoggingConfig, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, rotateConfigKeys, runConfigMigrations, runProviderWithRetry, runShellHook, safeParse, safeStringify, sanitizeJsonString, saveGoal, savePlan, saveTasks, saveTodosCheckpoint, scoreAgents, scoreMessage, scrubAndTruncateHqPreview, securitySlashCommand, sentinelServer, setBtwNote, setJsonPath, setJsonPathInFile, setOutputLineGuard, setPlanItemStatus, setProgress, setQueuedMessagesSnapshot, setRawMode, shouldEnhance, slackServer, sleep, sshManagerServer, stableStringify, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, startPackageOutdatedWatcher, startTechStackConsumer, stripAnsi, subjectForToolInput, summarizeHqToolArgs, summarizeUsage, templateToMarkdown, toErrorMessage, toStyle, toWrongStackError, topologicalSort, truncate, unifiedDiff, unloadPlugins, updateJsonObjectFile, updatePackageOutdatedStatus, validateAgainstSchema, watchHqAuthFile, wireMetricsToEvents, withDisabledToolFiltering, withFileLock, wrapAsState, writeErr, writeHqAuthFile, writeHqRuntimeFile, writeJsonObjectFile, writeOut, wstackGlobalRoot, zaiVisionServer };
48063
+ export { ACP_AGENTS, AGENTS_BY_PHASE, AGENT_CATALOG, TOOLS as AGENT_TOOL_PRESETS, AISpecBuilder, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, ALL_SYNC_CATEGORIES, AUDIT_LOG_AGENT, Agent, AgentError, AgentMonitorService, AgentStatusTracker, AnnotationsStore, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutoPhasePlanner, AutoPhaseRunner, AutonomousBrain, AutonomousCoordinator, AutonomousRunner, BUG_HUNTER_AGENT, BUILD_AGENTS, BrainDecisionQueue, BrainMonitor, BudgetExceededError, BudgetThresholdSignal, CHIMERA_REVIEW_PROMPT, CONTEXT_WINDOW_MODES, CORE_RECONSTRUCT_EVENTS, ChangeManager, CheckpointManager, CloudSync, CollabSession, CollaborationBus, ConfigError, ConfigMigrationError, ConsensusProtocol, Container, Context, ConversationState, DANGEROUS_FOR_SUBAGENTS, DECISION_TIMEOUT_MS, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CIRCUIT_BREAKER_CONFIG, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_CONFIG, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_HQ_REDACTION_POLICY, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_QUALITY_CHECKS, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SESSION_LOGGING_CONFIG, DEFAULT_SESSION_PRUNE_DAYS, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DEFAULT_TOOLS_CONFIG, DEFAULT_TUI_THINKING_WORD, DELIVERY_AGENTS, DEPENDENCY_FILE_PATTERNS, DISCOVERY_AGENTS, DOMAIN_AGENTS, DefaultAttachmentStore, DefaultBrainArbiter, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMailbox, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultPromptStore, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DirectorAlertLevel, DirectorStateCheckpoint, DoneConditionChecker, ENHANCER_SYSTEM_PROMPT, ERROR_CODES, EternalAutonomyEngine, EventBus, ExtensionRegistry, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FLEET_ROSTER_WITHACP, FORBIDDEN_PROTO_KEYS, FileMemoryBackend, FleetBus, FleetCostCapError, FleetManager, FleetNotifier, FleetSpawnBudgetError, FleetUsageAggregator, FsError, GitignoreUpdater, GlobalMailbox, GraphMemoryBackend, HEAVY_BUDGET, HQ_AUTH_FILE_VERSION, HQ_PROTOCOL_VERSION, HookRegistry, HookRunner, HqPublisher, HumanEscalatingBrainArbiter, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, KNOWLEDGE_AGENTS, KnowledgeGraph, LAYER_1_IDENTITY, LIGHT_BUDGET, LLMSelector, LargeAnswerStore, MATRIX_PHASE_KEYS, MAX_JOURNAL_ENTRIES, MAX_PROGRESS_HISTORY, MAX_TUI_THINKING_WORD_LENGTH, MEDIUM_BUDGET, MEMORY_TYPE_LABELS, META_AGENTS, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, ObservableBrainArbiter, PLANNING_AGENTS, PROMETHEUS_CONTENT_TYPE, ParallelEternalEngine, PhaseGraphBuilder, PhaseOrchestrator, PhaseStore, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, REFACTOR_PLANNER_AGENT, REVIEW_AGENTS, RecoveryLock, ReplayLogStore, ReplayProviderRunner, ReportGenerator, RunController, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, STANDARD_AUDIT_EVENTS, ScopedEventBus, SddError, SddParallelRun, SddTaskDecomposer, SecurityScanner, SecurityScannerOrchestrator, SelectiveCompactor, SessionAnalyzer, SessionError, SessionMemoryConsolidator, SessionRecovery, SessionRegistry, SkillGenerator, SkillInstaller, SkillManifestStore, SlashCommandRegistry, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, StreamHangError, SubagentBudget, TIMEOUT_PREEMPT_FRACTION, TOKENS, TaskAuctioneer, TaskDAG, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, TechStackDetector, ToolAuditLog, ToolCapabilities, ToolError, ToolErrorCategory, ToolExecutor, ToolRegistry, VERIFY_AGENTS, WIDE_SUBAGENT_CAPABILITIES, WorktreeManager, WrongStackError, addPlanItem, allServers, analyzeCriticalPath, appendJournal, applyModelRuntime, applyRosterBudget, asBlocks, asText, assertNever, assertNotPrivateHost, assertSafePath, assessCommitSafety, atomicWrite, attachAutoExtend, attachDepWatcherBridge, attachMailboxChecker, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, bootConfig, braveSearchServer, buildBtwBlock, buildChildEnv, buildContextEvidenceDigest, buildGoalPreamble, buildLosslessDigest, buildMailboxBlock, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildQueuedMessagesBlock, buildRecoveryStrategies, buildSmartDigest, buildTranscriptFromEvents, classifyFamily, clearPlan, collabInjectMiddleware, collabPauseMiddleware, color, compactLog, compactSchemaDescriptions, compactToolDefinitionForWire, compileGlob, compileUserRegex, completePartialObject, composeDirectorPrompt, composeSubagentPrompt, computeMessageTokens, computeTaskItemProgress, computeTaskProgress, consumeBtwNotes, consumeQueuedMessagesUpdate, context7Server, contextManagerTool, createAgentMonitorService, createAutoExecutor, createAutoPhaseFromTaskGraph, createAutonomyBrain, createChimeraPlugin, createContextEvidenceState, createContextManagerTool, createDefaultPipelines, createDelegateTool, createGitPlugin, createGlobalMailbox, createHqEventEnvelope, createHqPublisherFromEnv, createMailboxChecker, createMailboxEventPayload, createMailboxHooks, createMailboxSnapshotPayload, createMailboxSnapshotPayloadFromMailbox, createMcpControlTool, createMcpUseTool, createMessage, createObservabilityPlugin, createPlanPlugin, createPromptsPlugin, createSecurityPlugin, createSecuritySlashCommand, createSessionEventBridge, createSkillsPlugin, createStrategyCompactor, createSyncPlugin, createTieredBrainArbiter, createToolOutputSerializer, decryptConfigSecrets, deepMerge, defaultGitignoreUpdater, defaultHqDataDir, defaultOrchestrator, defaultReportGenerator, defaultSecurityScanner, defaultSkillGenerator, defaultTechStackDetector, definePlugin, deriveTodosFromPlanItem, describeCatalogModel, detectEcosystem, detectNewlineStyle, detectEcosystem as detectPackageEcosystem, dispatchAgent, downloadGitHubTarball, eliseOldToolResults, emptyGoal, emptyHqAuthFile, emptyPlan, emptyTaskFile, encryptConfigSecrets, encryptedPrefixForVersion, enhanceUserPrompt, ensureDir, ensureHqFirstRunAuthFile, escapeGlobSubject, estimateMessageTokens, estimateMessages, estimateRequestTokens, estimateRequestTokensCalibrated, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, expandGlob, expandIPv6, expectDefined, extractRunEnv, extractText, filesystemServer, findCriticalPath, findPreserveStart, flagsToConfigPatch, formatContextWindowModeList, formatDecisionSummary, formatGoal, formatHumanPrompt, formatPlan, formatPlanTemplates, formatTaskList, formatTaskProgress, formatTodosList, gatedEnhancerReasoning, generateSessionId, getAgentDefinition, getCalibrationState, getContextWindowMode, getDangerousCapabilities, getFileHistory, getFilesByAgent, getFullLog, getFullPackageLog, getJsonPath, getLastAuthor, getManifestPackages, getPackageAuthor, getPackagesByAgent, getPlanTemplate, getSessionRegistry, getTemplate, getTermSize, githubServer, goalFilePath, googleMapsServer, hasCapability, hasDangerousCapabilityForSubagents, hasSessionRegistry, hasTextContent, hashRequest, hookMatcherMatches, hqAuthFilePath, hqRuntimeFilePath, injectPendingMailboxMessages, isAgentError, isConfigError, isContextWindowModeId, isFsError, isImageBlock, isInteractive, isJsonObject, isPathSubjectKey, isPluginError, isPrimitiveArray, isPrivateIPv4, isPrivateIPv6, isSddError, isSecretField, isSessionError, isStdinTTY, isStdoutTTY, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isValidMatrixKey, isWrongStackError, jsonObjectFileExists, listContextWindowModes, listPlanTemplates, listTemplates, loadDirectorState, loadGoal, loadPlan, loadPlugins, loadProjectModes, loadTasks, loadTodosCheckpoint, loadUserModes, mailboxSessionTag, makeAgentSubagentRunner, makeAskResultTool, makeAskTool, makeAssignTool, makeAutonomyPromptContributor, makeAwaitTasksTool, makeCollabDebugTool, makeContinueToNextIterationTool, makeDependencyWatcherConfig, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeMailInboxTool, makeMailSendTool, makeMailboxTool, makeRollUpTool, makeSpawnTool, makeTerminateTool, makeWorkCompleteTool, mapMailboxAgentToHqSummary, mapMailboxMessageToHqSummary, mapSessionEventToEntries, markAssistantReferencedEvidence, matchAny, matchGlob, matrixKeyKind, mergeCustomModelDefs, mergeModelsPayload, mergeToolResults, migratePlaintextSecrets, miniMaxVisionServer, mintHqBrowserToken, mintHqToken, mutateHqAuthFile, mutatePlan, mutateTasks, noOpLogger, noOpVault, normalizePathSubject, normalizeRecipient, normalizeToLf, normalizeTokenSavingTier, normalizeTuiThinkingWord, normalizedEqual, onResize, parseContinueDirective, parseEncryptedVersion, parseEntries, parseHqEventPayload, parseHqFrame, parseProgressFromText, parseSkillRef, peekQueuedMessages, pendingBtwCount, phaseForRole, playwrightServer, projectHash, projectSlug, readHqAuthFile, readHqRuntimeFileSync, readJsonObjectFile, recentTextTurns, recordActualUsage, recordFileAction, recordPackageAction, recordProgress, recordToolOutputEvidence, recordUserIntentEvidence, redactHqEvent, redactHqValue, removeJsonPath, removeJsonPathInFile, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, repairToolUseAdjacency, repeatedReadPressure, resetCalibration, resolveAuditLevel, resolveCacheForRequest, resolveChimeraConfig, resolveContextWindowPolicy, resolveHqConfig, resolveHqConfigFromEnv, resolveHqDataDir, resolveMailboxIdentity, resolveModelMatrix, resolveModelRuntime, resolveProjectDir, resolveProviderModelList, resolveReasoningForRequest, resolveSessionLoggingConfig, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, rotateConfigKeys, runConfigMigrations, runProviderWithRetry, runShellHook, safeParse, safeStringify, sanitizeJsonString, sanitizeModel, sanitizeNodeOptions, saveGoal, savePlan, saveTasks, saveTodosCheckpoint, scoreAgents, scoreMessage, scrubAndTruncateHqPreview, securitySlashCommand, sentinelServer, setBtwNote, setJsonPath, setJsonPathInFile, setOutputLineGuard, setPlanItemStatus, setProgress, setQueuedMessagesSnapshot, setRawMode, shouldEnhance, slackServer, sleep, sshManagerServer, stableStringify, startAgentMonitorEventBridge, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, startPackageOutdatedWatcher, startSessionTelemetryBridge, startTechStackConsumer, stripAnsi, subjectForToolInput, summarizeHqToolArgs, summarizeUsage, templateToMarkdown, toErrorMessage, toStyle, toWrongStackError, topologicalSort, truncate, unifiedDiff, unloadPlugins, updateJsonObjectFile, updatePackageOutdatedStatus, validateAgainstSchema, watchHqAuthFile, wireMetricsToEvents, withDisabledToolFiltering, withFileLock, wrapAsState, writeErr, writeHqAuthFile, writeHqRuntimeFile, writeJsonObjectFile, writeOut, wstackGlobalRoot, zaiVisionServer };
46475
48064
  //# sourceMappingURL=index.js.map
46476
48065
  //# sourceMappingURL=index.js.map