@wrongstack/core 0.270.0 → 0.272.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/{agent-bridge-PcHQl_UQ.d.ts → agent-bridge-DFQYEeXf.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-SHJW7t8q.d.ts → agent-subagent-runner-BZa_IEcd.d.ts} +7 -7
  3. package/dist/{brain-BYcK__Ym.d.ts → brain-etbcbRwV.d.ts} +95 -2
  4. package/dist/{compactor-C2RKEBtC.d.ts → compactor-72ug-ZRB.d.ts} +1 -1
  5. package/dist/{config-C_ae2k86.d.ts → config-rRS8yorV.d.ts} +71 -2
  6. package/dist/{context-Dp87Bcaq.d.ts → context-Dw55zZ_Q.d.ts} +110 -1
  7. package/dist/coordination/index.d.ts +181 -17
  8. package/dist/coordination/index.js +1018 -166
  9. package/dist/coordination/index.js.map +1 -1
  10. package/dist/defaults/index.d.ts +25 -25
  11. package/dist/defaults/index.js +804 -222
  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-DJ4EoRr0.d.ts} +145 -5
  21. package/dist/{goal-preamble-CA_4yiGQ.d.ts → goal-preamble-hM8BH7TK.d.ts} +9 -9
  22. package/dist/{goal-store-DhuJoUNG.d.ts → goal-store-CWlbT0TO.d.ts} +1 -1
  23. package/dist/hq/index.d.ts +95 -6
  24. package/dist/hq/index.js +628 -50
  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-W4VJCzHa.d.ts → index-DWm_PE9L.d.ts} +5 -5
  28. package/dist/{index-CZQ6Pwbs.d.ts → index-DqW4o62H.d.ts} +8 -8
  29. package/dist/index.d.ts +96 -56
  30. package/dist/index.js +2464 -519
  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-BpWHTKlE.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-CXQFUn5t.d.ts} +1 -1
  42. package/dist/{multi-agent-coordinator-CJSpTe5O.d.ts → multi-agent-coordinator-jyimfo7D.d.ts} +1 -1
  43. package/dist/{null-fleet-bus-QVshIsDx.d.ts → null-fleet-bus-DOGQcvrY.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-rItJBYp9.d.ts} +9 -9
  46. package/dist/{path-resolver-CnQ8SIfh.d.ts → path-resolver-DrpF5MGK.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-Ckkn3AOA.d.ts} +2 -2
  50. package/dist/{plan-templates-NtPgyeJA.d.ts → plan-templates-BvHw5Znw.d.ts} +33 -9
  51. package/dist/{provider-model-resolve-d5poT5y0.d.ts → provider-model-resolve-nZqnCeaR.d.ts} +3 -3
  52. package/dist/{provider-runner-gkctlQV_.d.ts → provider-runner-zVOn1p67.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 +50 -13
  65. package/dist/storage/index.js +620 -220
  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
  }
@@ -111,7 +113,7 @@ async function withFileLock(targetPath, fn, opts = {}) {
111
113
  if (Date.now() - started >= timeoutMs) {
112
114
  throw new Error(`Timed out waiting for file lock: ${targetPath}`);
113
115
  }
114
- await new Promise((resolve18) => setTimeout(resolve18, 25));
116
+ await new Promise((resolve19) => setTimeout(resolve19, 25));
115
117
  }
116
118
  }
117
119
  try {
@@ -144,7 +146,7 @@ async function renameWithRetry(from, to) {
144
146
  if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
145
147
  throw err;
146
148
  }
147
- await new Promise((resolve18) => setTimeout(resolve18, delays[i]));
149
+ await new Promise((resolve19) => setTimeout(resolve19, delays[i]));
148
150
  }
149
151
  }
150
152
  throw lastErr;
@@ -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, path51) {
731
758
  let current = root;
732
- for (const segment of path48) {
759
+ for (const segment of path51) {
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, path51, value) {
771
+ if (path51.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, path51);
776
+ const leaf = lastPathSegment(path51);
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, path51) {
787
+ if (path51.length === 0) return false;
788
+ const parent = getJsonPath(root, path51.slice(0, -1));
789
+ const leaf = lastPathSegment(path51);
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, path51, value) {
800
+ return updateJsonObjectFile(filePath, (config) => setJsonPath(config, path51, value));
774
801
  }
775
- async function removeJsonPathInFile(filePath, path48) {
802
+ async function removeJsonPathInFile(filePath, path51) {
776
803
  return updateJsonObjectFile(filePath, (config) => {
777
- removeJsonPath(config, path48);
804
+ removeJsonPath(config, path51);
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(path51) {
811
+ const segment = path51[path51.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, path51) {
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 < path51.length - 1; i += 1) {
818
+ const segment = path51[i];
819
+ const nextSegment = path51[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, path51, 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: path51 || "<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: path51 || "<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(path51, 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(path51, 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, `${path51}[${i}]`, errors);
1773
1812
  }
1774
1813
  }
1775
1814
  }
@@ -2168,7 +2207,7 @@ function stripSingleLineComments(s) {
2168
2207
 
2169
2208
  // src/utils/sleep.ts
2170
2209
  function sleep(ms) {
2171
- return new Promise((resolve18) => setTimeout(resolve18, ms));
2210
+ return new Promise((resolve19) => setTimeout(resolve19, ms));
2172
2211
  }
2173
2212
 
2174
2213
  // src/utils/string.ts
@@ -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
  }
@@ -6863,7 +7063,7 @@ var InMemoryAgentBridge = class {
6863
7063
  });
6864
7064
  }
6865
7065
  this.inflightGuards.add(correlationId);
6866
- return new Promise((resolve18, reject) => {
7066
+ return new Promise((resolve19, reject) => {
6867
7067
  const timer = setTimeout(() => {
6868
7068
  this.inflightGuards.delete(correlationId);
6869
7069
  this.pendingRequests.delete(correlationId);
@@ -6882,7 +7082,7 @@ var InMemoryAgentBridge = class {
6882
7082
  return;
6883
7083
  }
6884
7084
  this.pendingRequests.set(correlationId, {
6885
- resolve: resolve18,
7085
+ resolve: resolve19,
6886
7086
  reject,
6887
7087
  timer
6888
7088
  });
@@ -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;
@@ -8209,7 +8412,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
8209
8412
  this._loadCache.clear();
8210
8413
  }
8211
8414
  }
8212
- // ── Storage event helpers ───────────────────────────────────────────────────
8415
+ // ── Storage event helpers ───────────────────────────────────────────────────
8213
8416
  emitRead(sessionId, filePath, operation, outcome, durationMs, error) {
8214
8417
  this.events?.emit("storage.read", {
8215
8418
  sessionId,
@@ -8324,7 +8527,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
8324
8527
  this.events,
8325
8528
  {
8326
8529
  resumed: true,
8327
- // Shard directory (sessions/<date>/) must match create() so the
8530
+ // Shard directory (sessions/<date>/) — must match create() so the
8328
8531
  // .summary.json sidecar lands next to the JSONL instead of the
8329
8532
  // sessions root (where summaryFor() would never find it).
8330
8533
  dir: path3.dirname(file),
@@ -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);
@@ -8365,26 +8568,100 @@ var DefaultSessionStore = class _DefaultSessionStore {
8365
8568
  const raw = await fsp3.readFile(file, "utf8");
8366
8569
  const lines = raw.split("\n").filter((l) => l.trim());
8367
8570
  const events = [];
8571
+ let sessionStartEvent;
8572
+ let sessionEndEvent;
8573
+ let sessionModel;
8574
+ let sessionProvider;
8575
+ let sessionPendingToolUses;
8576
+ const messages = [];
8577
+ const openToolUses = /* @__PURE__ */ new Set();
8578
+ let usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
8368
8579
  for (const line of lines) {
8369
8580
  try {
8370
8581
  const parsed = JSON.parse(line);
8371
8582
  if (parsed !== null && typeof parsed === "object" && typeof parsed.type === "string" && typeof parsed.ts === "string") {
8372
- events.push(parsed);
8583
+ const ev = parsed;
8584
+ events.push(ev);
8585
+ if (ev.type === "session_start" && !sessionStartEvent) {
8586
+ sessionStartEvent = ev;
8587
+ sessionModel = ev.model;
8588
+ sessionProvider = ev.provider;
8589
+ }
8590
+ if (ev.type === "session_end") {
8591
+ sessionEndEvent = ev;
8592
+ sessionPendingToolUses = ev.pendingToolUses;
8593
+ }
8594
+ if (ev.type === "user_input") {
8595
+ openToolUses.clear();
8596
+ messages.push({ role: "user", content: ev.content, ts: ev.ts });
8597
+ } else if (ev.type === "llm_response") {
8598
+ messages.push({ role: "assistant", content: ev.content, ts: ev.ts });
8599
+ for (const b of ev.content) {
8600
+ if (b.type === "tool_use") openToolUses.add(b.id);
8601
+ }
8602
+ usage = {
8603
+ input: usage.input + (ev.usage.input ?? 0),
8604
+ output: usage.output + (ev.usage.output ?? 0),
8605
+ cacheRead: (usage.cacheRead ?? 0) + (ev.usage.cacheRead ?? 0),
8606
+ cacheWrite: (usage.cacheWrite ?? 0) + (ev.usage.cacheWrite ?? 0)
8607
+ };
8608
+ } else if (ev.type === "tool_result") {
8609
+ if (!openToolUses.has(ev.id)) {
8610
+ this.events?.emit("session.damaged", {
8611
+ sessionId: id,
8612
+ detail: `Orphan tool_result "${ev.id}" has no matching tool_use`
8613
+ });
8614
+ continue;
8615
+ }
8616
+ openToolUses.delete(ev.id);
8617
+ const resultBlock = {
8618
+ type: "tool_result",
8619
+ tool_use_id: ev.id,
8620
+ content: typeof ev.content === "string" ? ev.content : JSON.stringify(ev.content),
8621
+ is_error: ev.isError
8622
+ };
8623
+ const last = messages[messages.length - 1];
8624
+ const lastIsToolResultUser = last?.role === "user" && Array.isArray(last.content) && last.content.every((b) => b.type === "tool_result");
8625
+ if (lastIsToolResultUser && Array.isArray(last.content)) {
8626
+ last.content.push(resultBlock);
8627
+ } else {
8628
+ messages.push({ role: "user", content: [resultBlock], ts: ev.ts });
8629
+ }
8630
+ }
8373
8631
  }
8374
8632
  } catch {
8375
8633
  }
8376
8634
  }
8377
- const meta = this.metaFromEvents(id, events);
8378
- const { messages, usage } = this.replay(events, id);
8635
+ if (openToolUses.size > 0) {
8636
+ this.events?.emit("session.damaged", {
8637
+ sessionId: id,
8638
+ detail: `${openToolUses.size} tool_use blocks without matching results - replay repaired`
8639
+ });
8640
+ }
8641
+ const repaired = repairToolUseAdjacency(messages);
8642
+ if (repaired.report.changed) {
8643
+ this.events?.emit("session.damaged", {
8644
+ sessionId: id,
8645
+ detail: `Repaired replay adjacency: removed ${repaired.report.removedToolUses.length} tool_use, ${repaired.report.removedToolResults.length} tool_result, ${repaired.report.removedMessages} empty messages`
8646
+ });
8647
+ }
8648
+ const meta = {
8649
+ id,
8650
+ startedAt: sessionStartEvent?.ts ?? (/* @__PURE__ */ new Date(0)).toISOString(),
8651
+ endedAt: sessionEndEvent?.ts,
8652
+ model: sessionModel,
8653
+ provider: sessionProvider,
8654
+ pendingToolUses: sessionPendingToolUses
8655
+ };
8379
8656
  const toolCallEnds = extractToolCallEnds(events);
8380
- const data = { metadata: meta, events, messages, usage, toolCallEnds };
8657
+ const data = { metadata: meta, events, messages: repaired.messages, usage, toolCallEnds };
8381
8658
  if (this._loadCache.size >= _DefaultSessionStore.LOAD_CACHE_MAX_ENTRIES) {
8382
8659
  const oldest = this._loadCache.keys().next().value;
8383
8660
  if (oldest !== void 0) {
8384
8661
  this._loadCache.delete(oldest);
8385
8662
  }
8386
8663
  }
8387
- this._loadCache.set(id, { mtimeMs: stat15.mtimeMs, size: stat15.size, data });
8664
+ this._loadCache.set(id, { mtimeMs: stat16.mtimeMs, size: stat16.size, data });
8388
8665
  return data;
8389
8666
  } catch (err) {
8390
8667
  outcome = "failure";
@@ -8415,20 +8692,12 @@ var DefaultSessionStore = class _DefaultSessionStore {
8415
8692
  });
8416
8693
  return indexed.slice(0, limit);
8417
8694
  }
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);
8695
+ return await this.listFromDirectoryScan(limit);
8427
8696
  } catch {
8428
8697
  return [];
8429
8698
  }
8430
8699
  }
8431
- // ── Session index (_index.jsonl) ─────────────────────────────────────────
8700
+ // ── Session index (_index.jsonl) ─────────────────────────────────────────
8432
8701
  //
8433
8702
  // One JSON line per closed session, appended atomically on close().
8434
8703
  // When a session is deleted, a tombstone {action:"delete",id:"..."} is
@@ -8494,15 +8763,15 @@ var DefaultSessionStore = class _DefaultSessionStore {
8494
8763
  * Returns empty array when the index doesn't exist or is corrupt.
8495
8764
  */
8496
8765
  async readIndex() {
8497
- let stat15;
8766
+ let stat16;
8498
8767
  try {
8499
8768
  const s = await fsp3.stat(this.indexFile);
8500
- stat15 = { mtimeMs: s.mtimeMs, size: s.size };
8769
+ stat16 = { mtimeMs: s.mtimeMs, size: s.size };
8501
8770
  } catch {
8502
8771
  this._indexCache = null;
8503
8772
  return [];
8504
8773
  }
8505
- if (this._indexCache !== null && this._indexCache.mtimeMs === stat15.mtimeMs && this._indexCache.size === stat15.size) {
8774
+ if (this._indexCache !== null && this._indexCache.mtimeMs === stat16.mtimeMs && this._indexCache.size === stat16.size) {
8506
8775
  return [...this._indexCache.summaries];
8507
8776
  }
8508
8777
  let raw;
@@ -8530,7 +8799,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
8530
8799
  }
8531
8800
  }
8532
8801
  const summaries = Array.from(seen.values());
8533
- this._indexCache = { ...stat15, summaries };
8802
+ this._indexCache = { ...stat16, summaries };
8534
8803
  return [...summaries];
8535
8804
  }
8536
8805
  /**
@@ -8548,46 +8817,105 @@ var DefaultSessionStore = class _DefaultSessionStore {
8548
8817
  this._indexCache = null;
8549
8818
  return valid.length;
8550
8819
  }
8820
+ async listFromDirectoryScan(limit) {
8821
+ const refs = await this.collectSessionFiles(this.dir);
8822
+ const candidates = await mapWithConcurrency(
8823
+ refs,
8824
+ _DefaultSessionStore.LIST_SCAN_CONCURRENCY,
8825
+ async (ref) => {
8826
+ const manifest = await this.readSummaryManifest(ref.id);
8827
+ if (manifest) return { summary: manifest, needsBackfill: false };
8828
+ const summary = await this.summaryHeaderFor(ref);
8829
+ return summary ? { summary, needsBackfill: true } : null;
8830
+ }
8831
+ );
8832
+ const out = candidates.filter((s) => s !== null);
8833
+ out.sort((a, b) => compareSessionSummaries(a.summary, b.summary));
8834
+ const selected = out.slice(0, limit);
8835
+ const summaries = await mapWithConcurrency(
8836
+ selected,
8837
+ Math.min(_DefaultSessionStore.LIST_SCAN_CONCURRENCY, Math.max(1, limit)),
8838
+ async (candidate) => {
8839
+ if (!candidate.needsBackfill) return candidate.summary;
8840
+ return await this.summaryFor(candidate.summary.id).catch(() => candidate.summary);
8841
+ }
8842
+ );
8843
+ return summaries.filter((s) => s !== null);
8844
+ }
8845
+ async collectSessionFiles(dir, prefix = "", depth = 0) {
8846
+ let entries;
8847
+ try {
8848
+ entries = await fsp3.readdir(dir, { withFileTypes: true });
8849
+ } catch {
8850
+ return [];
8851
+ }
8852
+ const dirEntries = [];
8853
+ const files = [];
8854
+ for (const entry of entries) {
8855
+ if (entry.name.startsWith(".") && entry.name !== ".wrongstack") continue;
8856
+ if (entry.name === "shared" || entry.name === "subagents" || entry.name === "attachments")
8857
+ continue;
8858
+ if (entry.isDirectory()) {
8859
+ dirEntries.push(entry);
8860
+ } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
8861
+ if (entry.name === "_index.jsonl") continue;
8862
+ const base = entry.name.replace(/\.jsonl$/, "");
8863
+ const id = prefix ? `${prefix}/${base}` : base;
8864
+ files.push({ id, filePath: path3.join(dir, entry.name) });
8865
+ }
8866
+ }
8867
+ const childFileArrays = await Promise.all(
8868
+ dirEntries.map((entry) => {
8869
+ const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
8870
+ return this.collectSessionFiles(path3.join(dir, entry.name), childPrefix, depth + 1);
8871
+ })
8872
+ );
8873
+ return [...childFileArrays.flat(), ...files];
8874
+ }
8551
8875
  /** Recursively collect session IDs from date-shard subdirectories.
8552
- * IDs include the date-prefix path (e.g. "2026-06-06/17-46-57Z_").
8876
+ * IDs include the date-prefix path (e.g. "2026-06-06/17-46-57Z_…").
8553
8877
  * Skips `.jsonl`/`.summary.json` root files, dot-files, and
8554
8878
  * sub-directories that belong to fleet/subagent sessions. */
8555
8879
  async collectSessionIds(dir, prefix = "", depth = 0) {
8556
- const ids = [];
8557
8880
  let entries;
8558
8881
  try {
8559
8882
  entries = await fsp3.readdir(dir, { withFileTypes: true });
8560
8883
  } catch {
8561
- return ids;
8884
+ return [];
8562
8885
  }
8886
+ const dirEntries = [];
8887
+ const fileIds = [];
8563
8888
  for (const entry of entries) {
8564
8889
  if (entry.name.startsWith(".") && entry.name !== ".wrongstack") continue;
8565
8890
  if (entry.name === "shared" || entry.name === "subagents" || entry.name === "attachments")
8566
8891
  continue;
8567
8892
  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));
8893
+ dirEntries.push(entry);
8570
8894
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
8571
8895
  if (entry.name === "_index.jsonl") continue;
8572
8896
  const base = entry.name.replace(/\.jsonl$/, "");
8573
- ids.push(prefix ? `${prefix}/${base}` : base);
8897
+ fileIds.push(prefix ? `${prefix}/${base}` : base);
8574
8898
  }
8575
8899
  }
8576
- return ids;
8900
+ const childIdArrays = await Promise.all(
8901
+ dirEntries.map((entry) => {
8902
+ const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
8903
+ return this.collectSessionIds(path3.join(dir, entry.name), childPrefix, depth + 1);
8904
+ })
8905
+ );
8906
+ return [...childIdArrays.flat(), ...fileIds];
8577
8907
  }
8578
8908
  async summaryFor(id) {
8579
8909
  const manifest = this.sessionPath(id, ".summary.json");
8580
8910
  const t0 = Date.now();
8581
8911
  let outcome = "success";
8582
8912
  let errorMsg;
8913
+ const fromManifest = await this.readSummaryManifest(id, t0);
8914
+ if (fromManifest) return fromManifest;
8583
8915
  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
8916
  const full = this.sessionPath(id, ".jsonl");
8589
- const stat15 = await fsp3.stat(full);
8590
- const summary = await this.summarize(id, stat15.mtime.toISOString());
8917
+ const stat16 = await fsp3.stat(full);
8918
+ const summary = await this.summarize(id, stat16.mtime.toISOString());
8591
8919
  await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
8592
8920
  const msg = toErrorMessage(err);
8593
8921
  this.emitError(id, manifest, "summary_fallback", msg, true);
@@ -8600,9 +8928,81 @@ var DefaultSessionStore = class _DefaultSessionStore {
8600
8928
  }));
8601
8929
  });
8602
8930
  outcome = "failure";
8603
- errorMsg = "summary fallback \u2014 manifest rebuilt";
8931
+ errorMsg = "summary fallback \xE2\u20AC\u201D manifest rebuilt";
8604
8932
  this.emitRead(id, manifest, "summary", outcome, Date.now() - t0, errorMsg);
8605
8933
  return summary;
8934
+ } catch (err) {
8935
+ outcome = "failure";
8936
+ errorMsg = toErrorMessage(err);
8937
+ this.emitRead(id, manifest, "summary", outcome, Date.now() - t0, errorMsg);
8938
+ return {
8939
+ id,
8940
+ title: "(damaged)",
8941
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
8942
+ model: "unknown",
8943
+ provider: "unknown",
8944
+ tokenTotal: 0
8945
+ };
8946
+ }
8947
+ }
8948
+ async readSummaryManifest(id, startTime = Date.now()) {
8949
+ const manifest = this.sessionPath(id, ".summary.json");
8950
+ try {
8951
+ const raw = await fsp3.readFile(manifest, "utf8");
8952
+ this.emitRead(id, manifest, "summary", "success", Date.now() - startTime);
8953
+ return JSON.parse(raw);
8954
+ } catch {
8955
+ return null;
8956
+ }
8957
+ }
8958
+ async summaryHeaderFor(ref) {
8959
+ let mtime = (/* @__PURE__ */ new Date(0)).toISOString();
8960
+ try {
8961
+ const stat16 = await fsp3.stat(ref.filePath);
8962
+ if (!stat16.isFile()) {
8963
+ return {
8964
+ id: ref.id,
8965
+ title: "(damaged)",
8966
+ startedAt: stat16.mtime.toISOString(),
8967
+ model: "unknown",
8968
+ provider: "unknown",
8969
+ tokenTotal: 0
8970
+ };
8971
+ }
8972
+ mtime = stat16.mtime.toISOString();
8973
+ } catch {
8974
+ return null;
8975
+ }
8976
+ try {
8977
+ for await (const event of this.iterSessionEvents(ref.filePath)) {
8978
+ if (event.type === "session_start") {
8979
+ return {
8980
+ id: ref.id,
8981
+ title: "(empty session)",
8982
+ startedAt: event.ts,
8983
+ model: event.model ?? "unknown",
8984
+ provider: event.provider ?? "unknown",
8985
+ tokenTotal: 0
8986
+ };
8987
+ }
8988
+ }
8989
+ return {
8990
+ id: ref.id,
8991
+ title: "(empty session)",
8992
+ startedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
8993
+ model: "unknown",
8994
+ provider: "unknown",
8995
+ tokenTotal: 0
8996
+ };
8997
+ } catch {
8998
+ return {
8999
+ id: ref.id,
9000
+ title: "(damaged)",
9001
+ startedAt: mtime,
9002
+ model: "unknown",
9003
+ provider: "unknown",
9004
+ tokenTotal: 0
9005
+ };
8606
9006
  }
8607
9007
  }
8608
9008
  /**
@@ -8669,8 +9069,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
8669
9069
  const pruneFile = async (dir, name, prefix) => {
8670
9070
  const jsonlPath = path3.join(dir, name);
8671
9071
  try {
8672
- const stat15 = await fsp3.stat(jsonlPath);
8673
- if (stat15.mtimeMs >= cutoff) return;
9072
+ const stat16 = await fsp3.stat(jsonlPath);
9073
+ if (stat16.mtimeMs >= cutoff) return;
8674
9074
  } catch {
8675
9075
  return;
8676
9076
  }
@@ -8727,39 +9127,62 @@ var DefaultSessionStore = class _DefaultSessionStore {
8727
9127
  }
8728
9128
  async summarize(id, mtime) {
8729
9129
  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)";
9130
+ const file = this.sessionPath(id, ".jsonl");
9131
+ let title = "(empty session)";
9132
+ let startedAt = (/* @__PURE__ */ new Date(0)).toISOString();
9133
+ let endedAt;
9134
+ let model = "unknown";
9135
+ let provider = "unknown";
9136
+ let tokenIn = 0;
9137
+ let tokenOut = 0;
8733
9138
  let iterationCount = 0;
8734
9139
  let toolCallCount = 0;
8735
9140
  let toolErrorCount = 0;
8736
9141
  let fileChangeCount = 0;
8737
9142
  const toolBreakdown = {};
8738
9143
  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++;
9144
+ let lastEventType;
9145
+ let hasError = false;
9146
+ let sawStart = false;
9147
+ for await (const e of this.iterSessionEvents(file)) {
9148
+ lastEventType = e.type;
9149
+ if (e.type === "session_start") {
9150
+ if (!sawStart) {
9151
+ sawStart = true;
9152
+ startedAt = e.ts;
9153
+ model = e.model ?? "unknown";
9154
+ provider = e.provider ?? "unknown";
9155
+ }
9156
+ } else if (e.type === "session_end") {
9157
+ endedAt = e.ts;
9158
+ } else if (e.type === "user_input") {
9159
+ if (title === "(empty session)") title = userInputTitle(e.content);
9160
+ } else if (e.type === "llm_response") {
9161
+ tokenIn += e.usage.input ?? 0;
9162
+ tokenOut += e.usage.output ?? 0;
9163
+ } else if (e.type === "in_flight_start") iterationCount++;
8742
9164
  else if (e.type === "tool_call_start") {
8743
9165
  toolCallCount++;
8744
9166
  toolBreakdown[e.name] = (toolBreakdown[e.name] ?? 0) + 1;
8745
9167
  } else if (e.type === "tool_result" && e.isError) toolErrorCount++;
8746
9168
  else if (e.type === "file_snapshot") fileChangeCount += e.files.length;
9169
+ else if (e.type === "error" || e.type === "provider_error") hasError = true;
8747
9170
  }
8748
- if (lastEvent?.type === "session_end") {
9171
+ if (lastEventType === "session_end") {
8749
9172
  outcome = "completed";
8750
- } else if (lastEvent?.type === "in_flight_start") {
9173
+ } else if (lastEventType === "in_flight_start") {
8751
9174
  outcome = "aborted";
8752
- } else if (data.events.some((e) => e.type === "error")) {
9175
+ } else if (hasError) {
8753
9176
  outcome = "error";
8754
9177
  }
8755
9178
  return {
8756
9179
  id,
8757
9180
  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,
9181
+ startedAt,
9182
+ endedAt,
9183
+ model,
9184
+ provider,
9185
+ tokenTotal: tokenIn + tokenOut,
8763
9186
  iterationCount: iterationCount > 0 ? iterationCount : void 0,
8764
9187
  toolCallCount: toolCallCount > 0 ? toolCallCount : void 0,
8765
9188
  toolErrorCount: toolErrorCount > 0 ? toolErrorCount : void 0,
@@ -8778,75 +9201,24 @@ var DefaultSessionStore = class _DefaultSessionStore {
8778
9201
  };
8779
9202
  }
8780
9203
  }
8781
- metaFromEvents(id, events) {
8782
- const start = events.find((e) => e.type === "session_start");
8783
- const end = events.findLast((e) => e.type === "session_end");
8784
- return {
8785
- id,
8786
- startedAt: start?.ts ?? (/* @__PURE__ */ new Date(0)).toISOString(),
8787
- endedAt: end?.ts,
8788
- model: start?.model,
8789
- provider: start?.provider,
8790
- pendingToolUses: end?.pendingToolUses
8791
- };
8792
- }
8793
- replay(events, sessionId = "unknown") {
8794
- const messages = [];
8795
- let usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
8796
- const openToolUses = /* @__PURE__ */ new Set();
8797
- for (const e of events) {
8798
- if (e.type === "user_input") {
8799
- openToolUses.clear();
8800
- messages.push({ role: "user", content: e.content, ts: e.ts });
8801
- } else if (e.type === "llm_response") {
8802
- messages.push({ role: "assistant", content: e.content, ts: e.ts });
8803
- for (const b of e.content) {
8804
- if (b.type === "tool_use") openToolUses.add(b.id);
8805
- }
8806
- usage = {
8807
- input: usage.input + (e.usage.input ?? 0),
8808
- output: usage.output + (e.usage.output ?? 0),
8809
- cacheRead: (usage.cacheRead ?? 0) + (e.usage.cacheRead ?? 0),
8810
- cacheWrite: (usage.cacheWrite ?? 0) + (e.usage.cacheWrite ?? 0)
8811
- };
8812
- } else if (e.type === "tool_result") {
8813
- if (!openToolUses.has(e.id)) {
8814
- this.events?.emit("session.damaged", {
8815
- sessionId,
8816
- detail: `Orphan tool_result "${e.id}" has no matching tool_use`
8817
- });
8818
- continue;
8819
- }
8820
- openToolUses.delete(e.id);
8821
- const resultBlock = {
8822
- type: "tool_result",
8823
- tool_use_id: e.id,
8824
- content: typeof e.content === "string" ? e.content : JSON.stringify(e.content),
8825
- is_error: e.isError
8826
- };
8827
- const last = messages[messages.length - 1];
8828
- const lastIsToolResultUser = last?.role === "user" && Array.isArray(last.content) && last.content.every((b) => b.type === "tool_result");
8829
- if (lastIsToolResultUser && Array.isArray(last.content)) {
8830
- last.content.push(resultBlock);
8831
- } else {
8832
- messages.push({ role: "user", content: [resultBlock], ts: e.ts });
9204
+ async *iterSessionEvents(file) {
9205
+ const stream = createReadStream(file, { encoding: "utf8" });
9206
+ const lines = createInterface({ input: stream, crlfDelay: Infinity });
9207
+ try {
9208
+ for await (const line of lines) {
9209
+ if (!line.trim()) continue;
9210
+ try {
9211
+ const parsed = JSON.parse(line);
9212
+ if (parsed !== null && typeof parsed === "object" && typeof parsed.type === "string" && typeof parsed.ts === "string") {
9213
+ yield parsed;
9214
+ }
9215
+ } catch {
8833
9216
  }
8834
9217
  }
9218
+ } finally {
9219
+ lines.close();
9220
+ stream.destroy();
8835
9221
  }
8836
- if (openToolUses.size > 0) {
8837
- this.events?.emit("session.damaged", {
8838
- sessionId,
8839
- detail: `${openToolUses.size} tool_use blocks without matching results - replay repaired`
8840
- });
8841
- }
8842
- const repaired = repairToolUseAdjacency(messages);
8843
- if (repaired.report.changed) {
8844
- this.events?.emit("session.damaged", {
8845
- sessionId,
8846
- detail: `Repaired replay adjacency: removed ${repaired.report.removedToolUses.length} tool_use, ${repaired.report.removedToolResults.length} tool_result, ${repaired.report.removedMessages} empty messages`
8847
- });
8848
- }
8849
- return { messages: repaired.messages, usage };
8850
9222
  }
8851
9223
  };
8852
9224
  function extractToolCallEnds(events) {
@@ -8906,7 +9278,7 @@ var FileSessionWriter = class _FileSessionWriter {
8906
9278
  /**
8907
9279
  * Lazy session_start/session_resumed init, shared by all appenders.
8908
9280
  * A single promise (not a boolean) so a second append racing the first
8909
- * can't push its event into the buffer BEFORE the first append's event
9281
+ * can't push its event into the buffer BEFORE the first append's event —
8910
9282
  * every appender awaits the same init and resumes in FIFO call order.
8911
9283
  */
8912
9284
  initPromise = null;
@@ -8919,24 +9291,24 @@ var FileSessionWriter = class _FileSessionWriter {
8919
9291
  lastAppendWarnAt = 0;
8920
9292
  secretScrubber;
8921
9293
  onCloseCb;
8922
- /** Implements SessionWriter.traceId propagated from ContextInit.traceId. */
9294
+ /** Implements SessionWriter.traceId — propagated from ContextInit.traceId. */
8923
9295
  traceId;
8924
- // ── Write buffer batches events to reduce per-event disk I/O ─────────
9296
+ // ── Write buffer — batches events to reduce per-event disk I/O ─────────
8925
9297
  //
8926
9298
  // Every append() pushes the scrubbed event into an in-memory buffer instead
8927
9299
  // of calling handle.appendFile() synchronously. The buffer flushes to disk
8928
9300
  // when it reaches FLUSH_SIZE events OR after FLUSH_INTERVAL_MS of inactivity.
8929
9301
  // This cuts the number of disk writes by ~95% without changing the on-disk
8930
- // format the JSONL is still one JSON object per line.
9302
+ // format — the JSONL is still one JSON object per line.
8931
9303
  writeBuffer = [];
8932
9304
  flushTimer = null;
8933
9305
  static FLUSH_INTERVAL_MS = 500;
8934
9306
  static FLUSH_SIZE = 50;
8935
- // ── Write serialization ─────────────────────────────────────────────────
9307
+ // ── Write serialization ─────────────────────────────────────────────────
8936
9308
  //
8937
9309
  // All disk writes are funneled through a FIFO promise chain. Without it,
8938
9310
  // a timer-driven flush racing an explicit flush()/close() issues two
8939
- // concurrent appendFile() calls on the shared O_APPEND handle the kernel
9311
+ // concurrent appendFile() calls on the shared O_APPEND handle — the kernel
8940
9312
  // may complete them out of order (chronology breaks) or, for large
8941
9313
  // batches, interleave partial writes (torn JSONL lines). The chain keeps
8942
9314
  // exactly one write in flight; failures don't break the chain.
@@ -8950,7 +9322,7 @@ var FileSessionWriter = class _FileSessionWriter {
8950
9322
  );
8951
9323
  return write;
8952
9324
  }
8953
- // ── Enriched summary tracking ──────────────────────────────────────────
9325
+ // ── Enriched summary tracking ──────────────────────────────────────────
8954
9326
  iterationCount = 0;
8955
9327
  toolCallCount = 0;
8956
9328
  toolErrorCount = 0;
@@ -9042,7 +9414,7 @@ var FileSessionWriter = class _FileSessionWriter {
9042
9414
  * (user_input, llm_response) call this so they survive SIGKILL/crash
9043
9415
  * instead of sitting in the in-memory buffer for up to 500ms.
9044
9416
  *
9045
- * Idempotent cancels any pending timer and writes whatever has
9417
+ * Idempotent — cancels any pending timer and writes whatever has
9046
9418
  * accumulated in the buffer. Safe to call even when the buffer
9047
9419
  * is empty (no-op).
9048
9420
  */
@@ -9065,7 +9437,7 @@ var FileSessionWriter = class _FileSessionWriter {
9065
9437
  /**
9066
9438
  * Flush all buffered events to disk as a single appendFile call.
9067
9439
  * Errors use the same throttled-warning pattern the old per-event
9068
- * append path used one warning every 5s with a suppressed count.
9440
+ * append path used — one warning every 5s with a suppressed count.
9069
9441
  * On failure the buffer is cleared (events are best-effort, same as
9070
9442
  * the old per-event path where a failed write was silently dropped).
9071
9443
  */
@@ -9248,7 +9620,7 @@ var FileSessionWriter = class _FileSessionWriter {
9248
9620
  /**
9249
9621
  * Truncate the session file to the checkpoint with the given promptIndex,
9250
9622
  * removing all events that follow it. Uses a single-pass byte-offset scan
9251
- * so post-checkpoint content is never read or parsed O(1) memory instead
9623
+ * so post-checkpoint content is never read or parsed — O(1) memory instead
9252
9624
  * of O(N) JSON.parse calls over the full file.
9253
9625
  */
9254
9626
  async truncateToCheckpoint(targetPromptIndex) {
@@ -9405,7 +9777,7 @@ var FileSessionWriter = class _FileSessionWriter {
9405
9777
  await fsp3.writeFile(this.filePath, record, "utf8");
9406
9778
  }
9407
9779
  /**
9408
- * Idea #1 write an in-flight marker. The agent loop should call
9780
+ * Idea #1 — write an in-flight marker. The agent loop should call
9409
9781
  * this at the start of each long-running operation; a matching
9410
9782
  * `clearInFlightMarker` follows on clean exit. A stale marker
9411
9783
  * (no end) is what `SessionRecovery.detectStale` looks for.
@@ -9422,9 +9794,9 @@ var FileSessionWriter = class _FileSessionWriter {
9422
9794
  this.events?.emit("in_flight.started", { context, ts: (/* @__PURE__ */ new Date()).toISOString() });
9423
9795
  }
9424
9796
  /**
9425
- * Idea #1 close the in-flight marker. Idempotent in spirit
9797
+ * Idea #1 — close the in-flight marker. Idempotent in spirit
9426
9798
  * (you can call it after a successful iteration even if you
9427
- * didn't open one this round) but the session log records
9799
+ * didn't open one this round) — but the session log records
9428
9800
  * every call so postmortem tooling can see "the agent finished
9429
9801
  * cleanly X times, then died without finishing Y".
9430
9802
  */
@@ -9441,6 +9813,27 @@ function userInputTitle(content) {
9441
9813
  const text = typeof content === "string" ? content : content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
9442
9814
  return (text || "(non-text input)").slice(0, 60);
9443
9815
  }
9816
+ function compareSessionSummaries(a, b) {
9817
+ if (a.startedAt < b.startedAt) return 1;
9818
+ if (a.startedAt > b.startedAt) return -1;
9819
+ return a.id.localeCompare(b.id);
9820
+ }
9821
+ async function mapWithConcurrency(items, concurrency, fn) {
9822
+ if (items.length === 0) return [];
9823
+ const out = new Array(items.length);
9824
+ let next = 0;
9825
+ const workerCount = Math.min(Math.max(1, concurrency), items.length);
9826
+ const workers = Array.from({ length: workerCount }, async () => {
9827
+ for (; ; ) {
9828
+ const idx = next++;
9829
+ if (idx >= items.length) return;
9830
+ const item = items[idx];
9831
+ if (item !== void 0) out[idx] = await fn(item);
9832
+ }
9833
+ });
9834
+ await Promise.all(workers);
9835
+ return out;
9836
+ }
9444
9837
 
9445
9838
  // src/storage/queue-store.ts
9446
9839
  init_atomic_write();
@@ -10016,6 +10409,20 @@ var DefaultMemoryStore = class {
10016
10409
  */
10017
10410
  persistBackup;
10018
10411
  backupDir;
10412
+ /**
10413
+ * Per-scope tracked byte sizes — incremented on `remember()`, decremented on
10414
+ * `forget()`, recalculated after `consolidate()`. Eliminates the redundant
10415
+ * readAll() call that previously checked the file size after every write.
10416
+ */
10417
+ _trackedByteSizes = {};
10418
+ /** Result cache for scoreRelevant() — keyed by scope + context hash, TTL 30s. */
10419
+ _scoreCache = /* @__PURE__ */ new Map();
10420
+ /**
10421
+ * Per-entry cached lowercase strings — computed once per scoreRelevant() call,
10422
+ * stored here so repeated scoring of the same entries avoids re-computation.
10423
+ * Cleared on every mutation (remember/forget/consolidate/clear).
10424
+ */
10425
+ _cachedLower = null;
10019
10426
  constructor(opts) {
10020
10427
  this.files = {
10021
10428
  "project-agents": opts.paths.inProjectAgentsFile,
@@ -10049,6 +10456,20 @@ var DefaultMemoryStore = class {
10049
10456
  }
10050
10457
  }
10051
10458
  }
10459
+ /**
10460
+ * Recalculate the tracked byte size for a scope by re-reading the file and
10461
+ * summing the serialized byte length of each line. Called after consolidate()
10462
+ * (which modifies the file) to keep the tracker accurate.
10463
+ */
10464
+ async _recalcTrackedByteSize(scope) {
10465
+ const raw = await this.backend.readAll(scope, this.files[scope]);
10466
+ let total = 0;
10467
+ for (const line of raw.split("\n")) {
10468
+ if (line.trim()) total += Buffer.byteLength(line, "utf8");
10469
+ }
10470
+ this._trackedByteSizes[scope] = total;
10471
+ return total;
10472
+ }
10052
10473
  async readAll() {
10053
10474
  const parts = [];
10054
10475
  for (const scope of ["project-agents", "project-memory", "user-memory"]) {
@@ -10149,6 +10570,7 @@ ${body.trim()}`);
10149
10570
  const t0 = Date.now();
10150
10571
  try {
10151
10572
  await this.backend.remember(scope, entry, filePath);
10573
+ this._scoreCache.clear();
10152
10574
  const dur = Date.now() - t0;
10153
10575
  this.events?.emit("storage.write", {
10154
10576
  sessionId: "~memory~",
@@ -10173,16 +10595,21 @@ ${body.trim()}`);
10173
10595
  });
10174
10596
  throw err;
10175
10597
  }
10176
- const raw = await this.backend.readAll(scope, this.files[scope]);
10177
- if (Buffer.byteLength(raw, "utf8") > MAX_BYTES_TOTAL) {
10598
+ let trackedSize = this._trackedByteSizes[scope];
10599
+ if (trackedSize === void 0) {
10600
+ trackedSize = await this._recalcTrackedByteSize(scope);
10601
+ }
10602
+ if (trackedSize > MAX_BYTES_TOTAL) {
10178
10603
  const removed = await this.backend.consolidate(scope, this.files[scope]);
10179
10604
  if (removed > 0) {
10180
10605
  this.events?.emit("memory.consolidated", {
10181
10606
  scope,
10182
10607
  removed
10183
10608
  });
10609
+ await this._recalcTrackedByteSize(scope);
10184
10610
  }
10185
10611
  }
10612
+ this._trackedByteSizes[scope] = (this._trackedByteSizes[scope] ?? 0) + Buffer.byteLength(JSON.stringify(entry), "utf8");
10186
10613
  await this.mirrorBackup(scope);
10187
10614
  this.events?.emit("memory.remembered", {
10188
10615
  scope,
@@ -10201,16 +10628,30 @@ ${body.trim()}`);
10201
10628
  async scoreRelevant(ctx, scope = "project-memory", limit = 8) {
10202
10629
  const all = await this.list(scope);
10203
10630
  if (all.length === 0) return [];
10631
+ const ctxHash = `${scope}|${ctx.currentTask}|${(ctx.activeSkills ?? []).join(",")}|${(ctx.toolNames ?? []).join(",")}`;
10632
+ const now = Date.now();
10633
+ const TTL_MS = 3e4;
10634
+ const cached = this._scoreCache.get(ctxHash);
10635
+ if (cached && cached.expiresAt > now && cached.entries === all) {
10636
+ return cached.scored.slice(0, Math.min(limit, 15));
10637
+ }
10204
10638
  const taskWords = ctx.currentTask.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
10205
10639
  const skillWords = (ctx.activeSkills ?? []).flatMap((s) => s.split("-"));
10206
10640
  const toolWords = (ctx.toolNames ?? []).flatMap((t2) => t2.toLowerCase().split("_"));
10207
- const now = Date.now();
10641
+ this._cachedLower = /* @__PURE__ */ new WeakMap();
10208
10642
  const scored = [];
10209
10643
  for (const entry of all) {
10210
10644
  let score = 0;
10211
10645
  const reasons = [];
10212
- const textLower = entry.text.toLowerCase();
10213
- const tagsLower = (entry.tags ?? []).map((t2) => t2.toLowerCase());
10646
+ let cachedLower = this._cachedLower.get(entry);
10647
+ if (!cachedLower) {
10648
+ cachedLower = {
10649
+ textLower: entry.text.toLowerCase(),
10650
+ tagsLower: (entry.tags ?? []).map((t2) => t2.toLowerCase())
10651
+ };
10652
+ this._cachedLower.set(entry, cachedLower);
10653
+ }
10654
+ const { textLower, tagsLower } = cachedLower;
10214
10655
  let taskHits = 0;
10215
10656
  for (const w of taskWords) {
10216
10657
  if (textLower.includes(w)) {
@@ -10296,6 +10737,7 @@ ${body.trim()}`);
10296
10737
  const relevant = scored.filter(
10297
10738
  (s) => s.score >= threshold || s.priority === "critical" || s.priority === "high"
10298
10739
  );
10740
+ this._scoreCache.set(ctxHash, { entries: all, scored: relevant, expiresAt: now + TTL_MS });
10299
10741
  return relevant.slice(0, Math.min(limit, 15));
10300
10742
  }
10301
10743
  async forget(query, scope = "project-memory") {
@@ -10305,6 +10747,7 @@ ${body.trim()}`);
10305
10747
  let removed = 0;
10306
10748
  try {
10307
10749
  removed = await this.backend.forget(scope, query, filePath);
10750
+ this._scoreCache.clear();
10308
10751
  const dur = Date.now() - t0;
10309
10752
  this.events?.emit("storage.write", {
10310
10753
  sessionId: "~memory~",
@@ -10336,6 +10779,7 @@ ${body.trim()}`);
10336
10779
  removed
10337
10780
  });
10338
10781
  await this.mirrorBackup(scope);
10782
+ await this._recalcTrackedByteSize(scope);
10339
10783
  }
10340
10784
  return removed;
10341
10785
  });
@@ -10347,6 +10791,7 @@ ${body.trim()}`);
10347
10791
  let removed = 0;
10348
10792
  try {
10349
10793
  removed = await this.backend.consolidate(scope, filePath);
10794
+ this._scoreCache.clear();
10350
10795
  const dur = Date.now() - t0;
10351
10796
  this.events?.emit("storage.write", {
10352
10797
  sessionId: "~memory~",
@@ -10377,6 +10822,7 @@ ${body.trim()}`);
10377
10822
  removed
10378
10823
  });
10379
10824
  await this.mirrorBackup(scope);
10825
+ await this._recalcTrackedByteSize(scope);
10380
10826
  }
10381
10827
  });
10382
10828
  }
@@ -10387,6 +10833,7 @@ ${body.trim()}`);
10387
10833
  const t0 = Date.now();
10388
10834
  try {
10389
10835
  await this.backend.clear(scope, filePath);
10836
+ this._scoreCache.clear();
10390
10837
  const dur = Date.now() - t0;
10391
10838
  this.events?.emit("storage.write", {
10392
10839
  sessionId: "~memory~",
@@ -10413,6 +10860,7 @@ ${body.trim()}`);
10413
10860
  }
10414
10861
  this.events?.emit("memory.cleared", { scope });
10415
10862
  await this.mirrorBackup(scope);
10863
+ this._trackedByteSizes[scope] = 0;
10416
10864
  });
10417
10865
  return;
10418
10866
  }
@@ -10470,8 +10918,8 @@ ${body.trim()}`);
10470
10918
  if (!this.persistBackup || scope === "project-agents") return;
10471
10919
  try {
10472
10920
  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 });
10921
+ const { writeFile: writeFile14, mkdir: mkdir24 } = await import('fs/promises');
10922
+ await mkdir24(this.backupDir, { recursive: true });
10475
10923
  await writeFile14(`${this.backupDir}/${scope}.md`, content, "utf8");
10476
10924
  } catch {
10477
10925
  }
@@ -10657,6 +11105,51 @@ var defaultIndexing = {
10657
11105
  watchExternal: true,
10658
11106
  debounceMs: 400
10659
11107
  };
11108
+ var IN_PROJECT_FORBIDDEN_KEYS = /* @__PURE__ */ new Set([
11109
+ "provider",
11110
+ "apiKey",
11111
+ "baseUrl",
11112
+ "providers",
11113
+ "mcpServers",
11114
+ "hooks",
11115
+ "plugins",
11116
+ "sync",
11117
+ "yolo",
11118
+ "extensions"
11119
+ ]);
11120
+ function stripUnsafeInProjectFields(inProject, sourcePath, warn = (msg) => console.warn(msg)) {
11121
+ const stripped = [];
11122
+ const out = {};
11123
+ for (const [k, v] of Object.entries(inProject)) {
11124
+ if (IN_PROJECT_FORBIDDEN_KEYS.has(k)) {
11125
+ stripped.push(k);
11126
+ continue;
11127
+ }
11128
+ out[k] = v;
11129
+ }
11130
+ if (stripped.length > 0) {
11131
+ warn(
11132
+ JSON.stringify({
11133
+ level: "warn",
11134
+ event: "config.in_project_unsafe_fields_ignored",
11135
+ path: sourcePath,
11136
+ ignoredKeys: stripped,
11137
+ 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.`,
11138
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
11139
+ })
11140
+ );
11141
+ }
11142
+ return out;
11143
+ }
11144
+ function samePath(a, b) {
11145
+ let ra = path3.resolve(a);
11146
+ let rb = path3.resolve(b);
11147
+ if (process.platform === "win32" || process.platform === "darwin") {
11148
+ ra = ra.toLowerCase();
11149
+ rb = rb.toLowerCase();
11150
+ }
11151
+ return ra === rb;
11152
+ }
10660
11153
  function deepMerge2(base, patch) {
10661
11154
  const opts = { arrayMode: "concat-primitives" };
10662
11155
  if (envBoolOptional(process.env.WRONGSTACK_DEBUG_CONFIG)) {
@@ -10685,14 +11178,15 @@ var DefaultConfigLoader = class {
10685
11178
  }
10686
11179
  async load(opts = {}) {
10687
11180
  let cfg = { ...BEHAVIOR_DEFAULTS };
11181
+ const inProjectCollides = samePath(this.paths.inProjectConfig, this.paths.globalConfig) || samePath(this.paths.inProjectConfig, this.paths.projectLocalConfig);
10688
11182
  const [global, local, inProject] = await Promise.all([
10689
11183
  this.readJson(this.paths.globalConfig),
10690
11184
  this.readJson(this.paths.projectLocalConfig),
10691
- this.readJson(this.paths.inProjectConfig)
11185
+ inProjectCollides ? Promise.resolve({}) : this.readJson(this.paths.inProjectConfig)
10692
11186
  ]);
10693
11187
  cfg = deepMerge2(cfg, global);
10694
11188
  cfg = deepMerge2(cfg, local);
10695
- cfg = deepMerge2(cfg, inProject);
11189
+ cfg = deepMerge2(cfg, stripUnsafeInProjectFields(inProject, this.paths.inProjectConfig));
10696
11190
  for (const [key, fn] of Object.entries(ENV_MAP)) {
10697
11191
  const v = process.env[key];
10698
11192
  if (v) fn(cfg, v);
@@ -12085,6 +12579,9 @@ function isClearlyDestructiveBashCommand(command, projectRoot) {
12085
12579
  }
12086
12580
 
12087
12581
  // src/security/permission-policy.ts
12582
+ function matchesTrust(patterns, subject) {
12583
+ return patterns.includes(subject) || matchAny(patterns, subject);
12584
+ }
12088
12585
  var DefaultPermissionPolicy = class {
12089
12586
  policy = {};
12090
12587
  loaded = false;
@@ -12221,7 +12718,7 @@ var DefaultPermissionPolicy = class {
12221
12718
  this._evalCache.set(cacheKey, decision);
12222
12719
  return decision;
12223
12720
  }
12224
- if (entry?.deny && subject && matchAny(entry.deny, subject)) {
12721
+ if (entry?.deny && subject && matchesTrust(entry.deny, subject)) {
12225
12722
  const decision = { permission: "deny", source: "deny", reason: "matched deny pattern" };
12226
12723
  this._evalCache.set(cacheKey, decision);
12227
12724
  return decision;
@@ -12231,7 +12728,7 @@ var DefaultPermissionPolicy = class {
12231
12728
  this._evalCache.set(cacheKey, decision);
12232
12729
  return decision;
12233
12730
  }
12234
- if (entry?.allow && subject && matchAny(entry.allow, subject)) {
12731
+ if (entry?.allow && subject && matchesTrust(entry.allow, subject)) {
12235
12732
  const decision = { permission: "auto", source: "trust", reason: "matched allow pattern" };
12236
12733
  this._evalCache.set(cacheKey, decision);
12237
12734
  return decision;
@@ -12767,6 +13264,16 @@ function handleMessageStop(state, ev) {
12767
13264
  async function streamProviderToResponse(provider, req, signal, ctx, events, logger) {
12768
13265
  const state = createStreamingState(req.model);
12769
13266
  logger.debug("Stream started", { providerId: provider.id, model: req.model });
13267
+ const TEXT_BATCH_SIZE = 4;
13268
+ let pendingText = "";
13269
+ let pendingCount = 0;
13270
+ const flushText = () => {
13271
+ if (pendingCount > 0) {
13272
+ events.emit("provider.text_delta", { ctx, text: pendingText });
13273
+ pendingText = "";
13274
+ pendingCount = 0;
13275
+ }
13276
+ };
12770
13277
  const iter = provider.stream(req, { signal })[Symbol.asyncIterator]();
12771
13278
  try {
12772
13279
  for (; ; ) {
@@ -12786,9 +13293,12 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
12786
13293
  break;
12787
13294
  case "text_delta":
12788
13295
  handleTextDelta(state, ev.text);
12789
- events.emit("provider.text_delta", { ctx, text: ev.text });
13296
+ pendingText += ev.text;
13297
+ pendingCount++;
13298
+ if (pendingCount >= TEXT_BATCH_SIZE) flushText();
12790
13299
  break;
12791
13300
  case "tool_use_start": {
13301
+ flushText();
12792
13302
  const idVal = ev.id;
12793
13303
  const nameVal = ev.name;
12794
13304
  handleToolUseStart(state, { id: idVal, name: nameVal });
@@ -12800,6 +13310,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
12800
13310
  handleToolUseInputDelta(state, ev);
12801
13311
  break;
12802
13312
  case "tool_use_stop": {
13313
+ flushText();
12803
13314
  const stoppedName = state.tools.get(ev.id)?.name ?? "unknown";
12804
13315
  handleToolUseStop(state, ev);
12805
13316
  events.emit("provider.tool_use_stop", { ctx, id: ev.id, name: stoppedName });
@@ -12809,6 +13320,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
12809
13320
  handleThinkingStart(state, ev);
12810
13321
  break;
12811
13322
  case "thinking_delta":
13323
+ flushText();
12812
13324
  handleThinkingDelta(state, ev.text);
12813
13325
  events.emit("provider.thinking_delta", { ctx, text: ev.text });
12814
13326
  break;
@@ -12840,6 +13352,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
12840
13352
  eventType: String(evAny.type),
12841
13353
  errorMessage: errMsg
12842
13354
  });
13355
+ flushText();
12843
13356
  events.emit("provider.stream_error", {
12844
13357
  ctx,
12845
13358
  eventType: String(evAny.type),
@@ -12850,6 +13363,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
12850
13363
  } catch (err) {
12851
13364
  if (signal.aborted) {
12852
13365
  state.stopReason = "end_turn";
13366
+ flushText();
12853
13367
  logger.debug("Stream aborted \u2014 returning partial state", {
12854
13368
  providerId: provider.id,
12855
13369
  model: req.model,
@@ -12868,8 +13382,8 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
12868
13382
  });
12869
13383
  await Promise.race([
12870
13384
  drainPromise,
12871
- new Promise((resolve18) => {
12872
- drainTimer = setTimeout(resolve18, STREAM_DRAIN_TIMEOUT_MS);
13385
+ new Promise((resolve19) => {
13386
+ drainTimer = setTimeout(resolve19, STREAM_DRAIN_TIMEOUT_MS);
12873
13387
  })
12874
13388
  ]);
12875
13389
  } finally {
@@ -12878,6 +13392,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
12878
13392
  } catch {
12879
13393
  }
12880
13394
  }
13395
+ flushText();
12881
13396
  logger.debug("Stream completed", {
12882
13397
  providerId: provider.id,
12883
13398
  model: req.model,
@@ -12975,7 +13490,7 @@ async function runProviderWithRetry(opts) {
12975
13490
  description
12976
13491
  });
12977
13492
  }
12978
- await new Promise((resolve18, reject) => {
13493
+ await new Promise((resolve19, reject) => {
12979
13494
  let settled = false;
12980
13495
  const cleanup = () => {
12981
13496
  clearTimeout(t2);
@@ -12991,7 +13506,7 @@ async function runProviderWithRetry(opts) {
12991
13506
  if (settled) return;
12992
13507
  settled = true;
12993
13508
  cleanup();
12994
- resolve18();
13509
+ resolve19();
12995
13510
  }, delay);
12996
13511
  if (signal.aborted) {
12997
13512
  onAbort();
@@ -14734,7 +15249,14 @@ var EternalAutonomyEngine = class {
14734
15249
  try {
14735
15250
  const reloaded = await loadGoal(this.goalPath, this.opts.events);
14736
15251
  iterationIndex = reloaded?.iterations ?? 0;
14737
- } catch {
15252
+ } catch (err) {
15253
+ console.error(JSON.stringify({
15254
+ level: "warn",
15255
+ event: "autonomy.goal_reload_failed",
15256
+ message: toErrorMessage(err),
15257
+ context: { goalPath: this.goalPath },
15258
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
15259
+ }));
14738
15260
  }
14739
15261
  this.opts.onIteration?.({
14740
15262
  at: (this.opts.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
@@ -14969,7 +15491,14 @@ ${lastFew}` : "No prior iterations yet.",
14969
15491
  } finally {
14970
15492
  clearTimeout(timer);
14971
15493
  }
14972
- } catch {
15494
+ } catch (err) {
15495
+ console.error(JSON.stringify({
15496
+ level: "warn",
15497
+ event: "autonomy.brainstorm_failed",
15498
+ message: toErrorMessage(err),
15499
+ context: { goal: goal.goal.slice(0, 100) },
15500
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
15501
+ }));
14973
15502
  return null;
14974
15503
  }
14975
15504
  }
@@ -15492,13 +16021,13 @@ var SubagentBudget = class _SubagentBudget {
15492
16021
  if (!bus?.hasListenerFor("budget.threshold_reached")) {
15493
16022
  return Promise.resolve("stop");
15494
16023
  }
15495
- return new Promise((resolve18) => {
16024
+ return new Promise((resolve19) => {
15496
16025
  let resolved = false;
15497
16026
  const respond = (d) => {
15498
16027
  if (resolved) return;
15499
16028
  resolved = true;
15500
16029
  clearTimeout(fallback);
15501
- resolve18(d);
16030
+ resolve19(d);
15502
16031
  };
15503
16032
  const fallback = setTimeout(() => respond("stop"), _SubagentBudget.DECISION_TIMEOUT_MS);
15504
16033
  bus.emit("budget.threshold_reached", {
@@ -18849,6 +19378,68 @@ Working rules:
18849
19378
  - When in doubt, flag as medium rather than ignoring potential issues`
18850
19379
  // Budgets are set by the orchestrator per task — see fleet.ts header.
18851
19380
  };
19381
+ var SHADOW_AGENT = {
19382
+ id: "shadow-agent",
19383
+ name: "Shadow",
19384
+ role: "shadow-agent",
19385
+ prompt: `You are the Shadow Agent \u2014 a silent background monitor for the WrongStack fleet.
19386
+
19387
+ Your job is to observe, detect anomalies, and be ready to intervene \u2014 but only when commanded.
19388
+
19389
+ ## Core Responsibilities
19390
+
19391
+ 1. **Fleet Monitoring** (every 30s)
19392
+ - Call \`fleet_status\` + \`fleet_health\` on each heartbeat
19393
+ - Track what each agent is doing (task descriptions)
19394
+ - Detect stuck agents (>5min no events), idle agents, crashed agents
19395
+
19396
+ 2. **FleetBus Subscription**
19397
+ - Subscribe to \`subagent.*\` events to track lifecycle
19398
+ - Subscribe to \`tool.executed\` to monitor activity
19399
+ - Track agent joins (subagent.started) and leaves (subagent.stopped)
19400
+
19401
+ 3. **Mailbox Surveillance**
19402
+ - Monitor for \`control\` type messages starting with "hoop"
19403
+ - Detect orphan assigns (assign without result within 5min)
19404
+ - Cross-session awareness via shared project mailbox
19405
+
19406
+ 4. **Spike Detection**
19407
+ - Track task duration per agent
19408
+ - Flag agents that spawn and die within <5 seconds
19409
+ - Log spike events with reason (completed/error/killed/timeout)
19410
+
19411
+ 5. **Intervention Commands**
19412
+ Parse these from mailbox control messages:
19413
+ - \`hoop <agentId>\` \u2014 terminate specific agent
19414
+ - \`hoop all\` \u2014 terminate all running agents
19415
+ - \`shadow status\` \u2014 report current fleet snapshot
19416
+ - \`shadow mute\` \u2014 pause heartbeat monitoring
19417
+ - \`shadow resume\` \u2014 resume heartbeat monitoring
19418
+ - \`shadow interval <ms>\` \u2014 change heartbeat interval
19419
+ - \`shadow model <model-id>\` \u2014 change analysis model
19420
+
19421
+ ## Operating Rules
19422
+
19423
+ - **Silent by default**: Use DEBUG level logging unless anomaly detected
19424
+ - **Deterministic**: Same state always produces same actions \u2014 no randomness
19425
+ - **Report on anomaly**: When anomaly detected, use \`mail_send\` to broadcast warning
19426
+ - **Never auto-intervene**: Always report unless explicitly commanded
19427
+ - **Minimal footprint**: Small state, efficient snapshots
19428
+
19429
+ ## Startup Sequence
19430
+
19431
+ 1. Send broadcast: \`shadow:started { intervalMs, model, startTime }\`
19432
+ 2. Subscribe to FleetBus for all relevant events
19433
+ 3. Schedule heartbeat cron job at configured interval
19434
+ 4. Wait for commands or anomalies
19435
+
19436
+ ## Shutdown Sequence
19437
+
19438
+ 1. Cancel all cron jobs (\`cron_cancel\`)
19439
+ 2. Send broadcast: \`shadow:stopped { reason, finalState }\`
19440
+ 3. Clean up FleetBus subscriptions`
19441
+ // Budgets are set by the orchestrator per task — see fleet.ts header.
19442
+ };
18852
19443
  var CRITIC_AGENT = {
18853
19444
  id: "critic",
18854
19445
  name: "Critic",
@@ -18889,6 +19480,7 @@ var FLEET_ROSTER = {
18889
19480
  "refactor-planner": REFACTOR_PLANNER_AGENT,
18890
19481
  "security-scanner": SECURITY_SCANNER_AGENT,
18891
19482
  "critic": CRITIC_AGENT,
19483
+ "shadow-agent": SHADOW_AGENT,
18892
19484
  ...Object.fromEntries(
18893
19485
  ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
18894
19486
  )
@@ -18900,6 +19492,8 @@ var FLEET_ROSTER_BUDGETS = {
18900
19492
  "refactor-planner": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 6e3, maxToolCalls: 18e3 },
18901
19493
  "security-scanner": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
18902
19494
  "critic": { timeoutMs: 5 * 60 * 60 * 1e3, maxIterations: 4e3, maxToolCalls: 12e3 },
19495
+ "shadow-agent": { timeoutMs: 24 * 60 * 60 * 1e3, maxIterations: 1e4, maxToolCalls: 5e3 },
19496
+ // Long-running background monitor
18903
19497
  ...Object.fromEntries(
18904
19498
  ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.budget])
18905
19499
  )
@@ -19338,7 +19932,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
19338
19932
  taskIds.map((id) => {
19339
19933
  const cached = this.completedResults.find((r) => r.taskId === id);
19340
19934
  if (cached) return cached;
19341
- return new Promise((resolve18, reject) => {
19935
+ return new Promise((resolve19, reject) => {
19342
19936
  const timeout = setTimeout(() => {
19343
19937
  this.off("task.completed", handler);
19344
19938
  reject(new Error(`awaitTasks timed out waiting for task "${id}"`));
@@ -19347,7 +19941,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
19347
19941
  if (result.taskId === id) {
19348
19942
  clearTimeout(timeout);
19349
19943
  this.off("task.completed", handler);
19350
- resolve18(result);
19944
+ resolve19(result);
19351
19945
  }
19352
19946
  };
19353
19947
  this.on("task.completed", handler);
@@ -19615,12 +20209,12 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
19615
20209
  }
19616
20210
  return new Promise((resolveDecision) => {
19617
20211
  let settled = false;
19618
- const resolve18 = (d) => {
20212
+ const resolve19 = (d) => {
19619
20213
  if (settled) return;
19620
20214
  settled = true;
19621
20215
  resolveDecision(d);
19622
20216
  };
19623
- const fallback = setTimeout(() => resolve18("stop"), DECISION_TIMEOUT_MS);
20217
+ const fallback = setTimeout(() => resolve19("stop"), DECISION_TIMEOUT_MS);
19624
20218
  budget._events?.emit("budget.threshold_reached", {
19625
20219
  kind: "timeout",
19626
20220
  used,
@@ -19636,11 +20230,11 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
19636
20230
  // disagreeing, resolves as a stop). Async grants still resolve.
19637
20231
  extend: (extra) => {
19638
20232
  clearTimeout(fallback);
19639
- queueMicrotask(() => resolve18({ extend: extra }));
20233
+ queueMicrotask(() => resolve19({ extend: extra }));
19640
20234
  },
19641
20235
  deny: () => {
19642
20236
  clearTimeout(fallback);
19643
- resolve18("stop");
20237
+ resolve19("stop");
19644
20238
  }
19645
20239
  });
19646
20240
  });
@@ -20107,7 +20701,14 @@ ${personaLine}Task: ${task}
20107
20701
  subagentIds.push(subagentId);
20108
20702
  taskIds.push(taskId);
20109
20703
  await coordinator.assign(spec);
20110
- } catch {
20704
+ } catch (err) {
20705
+ console.error(JSON.stringify({
20706
+ level: "warn",
20707
+ event: "parallel_engine.spawn_failed",
20708
+ message: toErrorMessage(err),
20709
+ context: { slot: i, task, subagentId },
20710
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
20711
+ }));
20111
20712
  }
20112
20713
  })()
20113
20714
  );
@@ -20132,7 +20733,14 @@ ${personaLine}Task: ${task}
20132
20733
  } finally {
20133
20734
  clearTimeout(timer);
20134
20735
  }
20135
- } catch {
20736
+ } catch (err) {
20737
+ console.error(JSON.stringify({
20738
+ level: "warn",
20739
+ event: "parallel_engine.brainstorm_results_failed",
20740
+ message: toErrorMessage(err),
20741
+ context: { slotCount, taskIds },
20742
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
20743
+ }));
20136
20744
  results = coordinator.results().slice(-taskIds.length);
20137
20745
  }
20138
20746
  await Promise.allSettled(subagentIds.map((id) => coordinator.remove(id)));
@@ -20166,7 +20774,14 @@ ${personaLine}Task: ${task}
20166
20774
  if (file) tasks.push(`[git] inspect and fix: ${file}`);
20167
20775
  }
20168
20776
  }
20169
- } catch {
20777
+ } catch (err) {
20778
+ console.error(JSON.stringify({
20779
+ level: "warn",
20780
+ event: "parallel_engine.git_status_failed",
20781
+ message: toErrorMessage(err),
20782
+ context: { projectRoot: this.opts.projectRoot },
20783
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
20784
+ }));
20170
20785
  }
20171
20786
  }
20172
20787
  if (tasks.length < this.slots) {
@@ -20520,7 +21135,7 @@ var CollabSession = class extends EventEmitter {
20520
21135
  }
20521
21136
  for (const filePath of allFiles) {
20522
21137
  try {
20523
- const [content, stat15] = await Promise.all([
21138
+ const [content, stat16] = await Promise.all([
20524
21139
  fsp3.readFile(filePath, "utf8"),
20525
21140
  fsp3.stat(filePath)
20526
21141
  ]);
@@ -20530,8 +21145,8 @@ var CollabSession = class extends EventEmitter {
20530
21145
  path: filePath,
20531
21146
  content,
20532
21147
  language,
20533
- snapshotMtimeMs: stat15.mtimeMs,
20534
- snapshotSizeBytes: stat15.size
21148
+ snapshotMtimeMs: stat16.mtimeMs,
21149
+ snapshotSizeBytes: stat16.size
20535
21150
  });
20536
21151
  } catch {
20537
21152
  this.snapshot.files.push({ path: filePath, content: "", language: void 0 });
@@ -20944,9 +21559,9 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
20944
21559
  for (const file of this.snapshot.files) {
20945
21560
  if (file.snapshotMtimeMs === void 0 && file.snapshotSizeBytes === void 0) continue;
20946
21561
  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;
21562
+ const stat16 = await fsp3.stat(file.path);
21563
+ const mtimeChanged = file.snapshotMtimeMs !== void 0 && stat16.mtimeMs > file.snapshotMtimeMs + 1;
21564
+ const sizeChanged = file.snapshotSizeBytes !== void 0 && stat16.size !== file.snapshotSizeBytes;
20950
21565
  if (mtimeChanged || sizeChanged) {
20951
21566
  warnings.push(`${file.path} changed after the collab snapshot was captured.`);
20952
21567
  }
@@ -22865,11 +23480,11 @@ var Director = class _Director {
22865
23480
  if (cached) return cached;
22866
23481
  const existing = this.taskWaiters.get(id);
22867
23482
  if (existing) return existing.promise;
22868
- let resolve18;
23483
+ let resolve19;
22869
23484
  const promise = new Promise((res) => {
22870
- resolve18 = res;
23485
+ resolve19 = res;
22871
23486
  });
22872
- this.taskWaiters.set(id, { promise, resolve: resolve18 });
23487
+ this.taskWaiters.set(id, { promise, resolve: resolve19 });
22873
23488
  return promise;
22874
23489
  })
22875
23490
  );
@@ -23265,7 +23880,7 @@ function createDelegateTool(opts) {
23265
23880
  subagentId
23266
23881
  });
23267
23882
  const dir = director;
23268
- const result = await new Promise((resolve18) => {
23883
+ const result = await new Promise((resolve19) => {
23269
23884
  let settled = false;
23270
23885
  let timer;
23271
23886
  const finish = (value) => {
@@ -23275,7 +23890,7 @@ function createDelegateTool(opts) {
23275
23890
  offTool();
23276
23891
  offIter();
23277
23892
  offProgress();
23278
- resolve18(value);
23893
+ resolve19(value);
23279
23894
  };
23280
23895
  const arm = () => {
23281
23896
  if (timer) clearTimeout(timer);
@@ -23707,8 +24322,8 @@ async function loadProjectModes(modesDir) {
23707
24322
  for (const entry of entries) {
23708
24323
  if (!entry.endsWith(".md") && !entry.endsWith(".txt")) continue;
23709
24324
  const filePath = path3.join(modesDir, entry);
23710
- const stat15 = await fsp3.stat(filePath);
23711
- if (!stat15.isFile()) continue;
24325
+ const stat16 = await fsp3.stat(filePath);
24326
+ if (!stat16.isFile()) continue;
23712
24327
  const content = await fsp3.readFile(filePath, "utf8");
23713
24328
  const id = path3.basename(entry, path3.extname(entry));
23714
24329
  modes.push({
@@ -25045,10 +25660,10 @@ var AISpecBuilder = class {
25045
25660
  async saveSession() {
25046
25661
  if (!this.sessionPath) return;
25047
25662
  try {
25048
- const fsp25 = await import('fs/promises');
25049
- const path48 = await import('path');
25663
+ const fsp26 = await import('fs/promises');
25664
+ const path51 = await import('path');
25050
25665
  const { atomicWrite: atomicWrite2 } = await Promise.resolve().then(() => (init_atomic_write(), atomic_write_exports));
25051
- await fsp25.mkdir(path48.dirname(this.sessionPath), { recursive: true });
25666
+ await fsp26.mkdir(path51.dirname(this.sessionPath), { recursive: true });
25052
25667
  await atomicWrite2(this.sessionPath, JSON.stringify(this.session, null, 2));
25053
25668
  } catch {
25054
25669
  }
@@ -25057,8 +25672,8 @@ var AISpecBuilder = class {
25057
25672
  async loadSession() {
25058
25673
  if (!this.sessionPath) return false;
25059
25674
  try {
25060
- const fsp25 = await import('fs/promises');
25061
- const raw = await fsp25.readFile(this.sessionPath, "utf8");
25675
+ const fsp26 = await import('fs/promises');
25676
+ const raw = await fsp26.readFile(this.sessionPath, "utf8");
25062
25677
  const loaded = JSON.parse(raw);
25063
25678
  if (loaded?.id && loaded?.phase && loaded?.title) {
25064
25679
  this.session = loaded;
@@ -25072,8 +25687,8 @@ var AISpecBuilder = class {
25072
25687
  async deleteSession() {
25073
25688
  if (!this.sessionPath) return;
25074
25689
  try {
25075
- const fsp25 = await import('fs/promises');
25076
- await fsp25.unlink(this.sessionPath);
25690
+ const fsp26 = await import('fs/promises');
25691
+ await fsp26.unlink(this.sessionPath);
25077
25692
  } catch {
25078
25693
  }
25079
25694
  }
@@ -25775,15 +26390,15 @@ function computeCriticalPath(graph, _topoOrder, blockedByMap) {
25775
26390
  maxId = id;
25776
26391
  }
25777
26392
  }
25778
- const path48 = [];
26393
+ const path51 = [];
25779
26394
  let current = maxId;
25780
26395
  const visited = /* @__PURE__ */ new Set();
25781
26396
  while (current && !visited.has(current)) {
25782
26397
  visited.add(current);
25783
- path48.unshift(current);
26398
+ path51.unshift(current);
25784
26399
  current = prev.get(current) ?? null;
25785
26400
  }
25786
- return path48;
26401
+ return path51;
25787
26402
  }
25788
26403
  function computeParallelGroups(graph, blockedByMap) {
25789
26404
  const groups = [];
@@ -26606,9 +27221,9 @@ var DefaultHealthRegistry = class {
26606
27221
  }
26607
27222
  async runOne(check) {
26608
27223
  let timer = null;
26609
- const timeout = new Promise((resolve18) => {
27224
+ const timeout = new Promise((resolve19) => {
26610
27225
  timer = setTimeout(
26611
- () => resolve18({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
27226
+ () => resolve19({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
26612
27227
  this.timeoutMs
26613
27228
  );
26614
27229
  });
@@ -26791,7 +27406,7 @@ async function startMetricsServer(opts) {
26791
27406
  const tls = opts.tls;
26792
27407
  const useHttps = !!(tls?.cert && tls?.key);
26793
27408
  const host = opts.host ?? "127.0.0.1";
26794
- const path48 = opts.path ?? "/metrics";
27409
+ const path51 = opts.path ?? "/metrics";
26795
27410
  const healthPath = opts.healthPath ?? "/healthz";
26796
27411
  const healthRegistry = opts.healthRegistry;
26797
27412
  const listener = (req, res) => {
@@ -26801,7 +27416,7 @@ async function startMetricsServer(opts) {
26801
27416
  return;
26802
27417
  }
26803
27418
  const url = req.url.split("?")[0];
26804
- if (url === path48) {
27419
+ if (url === path51) {
26805
27420
  let body;
26806
27421
  try {
26807
27422
  body = renderPrometheus(opts.sink.snapshot());
@@ -26847,14 +27462,14 @@ async function startMetricsServer(opts) {
26847
27462
  const { createServer } = await import('http');
26848
27463
  server = createServer(listener);
26849
27464
  }
26850
- await new Promise((resolve18, reject) => {
27465
+ await new Promise((resolve19, reject) => {
26851
27466
  const onError = (err) => {
26852
27467
  server.off("listening", onListening);
26853
27468
  reject(err);
26854
27469
  };
26855
27470
  const onListening = () => {
26856
27471
  server.off("error", onError);
26857
- resolve18();
27472
+ resolve19();
26858
27473
  };
26859
27474
  server.once("error", onError);
26860
27475
  server.once("listening", onListening);
@@ -26865,9 +27480,9 @@ async function startMetricsServer(opts) {
26865
27480
  const protocol = useHttps ? "https" : "http";
26866
27481
  return {
26867
27482
  port: boundPort,
26868
- url: `${protocol}://${host}:${boundPort}${path48}`,
26869
- close: () => new Promise((resolve18, reject) => {
26870
- server.close((err) => err ? reject(err) : resolve18());
27483
+ url: `${protocol}://${host}:${boundPort}${path51}`,
27484
+ close: () => new Promise((resolve19, reject) => {
27485
+ server.close((err) => err ? reject(err) : resolve19());
26871
27486
  })
26872
27487
  };
26873
27488
  }
@@ -27804,13 +28419,13 @@ var SkillInstaller = class {
27804
28419
  context: { reason: "path_traversal", skillName: skill.name }
27805
28420
  });
27806
28421
  }
27807
- const stat15 = await fsp3.stat(srcPath);
27808
- if (stat15.size > MAX_SKILL_FILE_SIZE) {
28422
+ const stat16 = await fsp3.stat(srcPath);
28423
+ if (stat16.size > MAX_SKILL_FILE_SIZE) {
27809
28424
  throw new FsError({
27810
- message: `Skill file "${file}" is too large (${(stat15.size / 1024).toFixed(1)}KB). Max: ${MAX_SKILL_FILE_SIZE / 1024}KB`,
28425
+ message: `Skill file "${file}" is too large (${(stat16.size / 1024).toFixed(1)}KB). Max: ${MAX_SKILL_FILE_SIZE / 1024}KB`,
27811
28426
  code: ERROR_CODES.FS_WRITE_FAILED,
27812
28427
  path: srcPath,
27813
- context: { skillName: skill.name, fileSize: stat15.size, maxSize: MAX_SKILL_FILE_SIZE }
28428
+ context: { skillName: skill.name, fileSize: stat16.size, maxSize: MAX_SKILL_FILE_SIZE }
27814
28429
  });
27815
28430
  }
27816
28431
  await fsp3.mkdir(path3.dirname(destPath), { recursive: true });
@@ -28068,6 +28683,12 @@ var GraphMemoryBackend = class {
28068
28683
  edges = [];
28069
28684
  loadedScope = null;
28070
28685
  loaded = false;
28686
+ /**
28687
+ * Promise that resolves when the current in-flight _saveGraph completes.
28688
+ * Tests call flush() to await this before deleting the backend or its temp dir.
28689
+ * Each save operation chains onto the previous one so concurrent saves are serialised.
28690
+ */
28691
+ _saveDone = Promise.resolve();
28071
28692
  constructor(opts) {
28072
28693
  this.file = new FileMemoryBackend({ paths: opts.paths });
28073
28694
  this.graphFile = opts.graphPath ?? `${opts.paths.projectDir}/memory-graph.json`;
@@ -28094,7 +28715,8 @@ var GraphMemoryBackend = class {
28094
28715
  tags: entry.tags,
28095
28716
  priority: entry.priority
28096
28717
  });
28097
- for (const [, other] of this.nodes) {
28718
+ const recentNodes = [...this.nodes.values()].slice(-100);
28719
+ for (const other of recentNodes) {
28098
28720
  if (other.id === nodeId) continue;
28099
28721
  const sim = wordOverlap(entry.text, other.entry.text);
28100
28722
  const tagSim = sharedTags(entry.tags ?? [], other.tags ?? []);
@@ -28110,7 +28732,8 @@ var GraphMemoryBackend = class {
28110
28732
  }
28111
28733
  }
28112
28734
  }
28113
- await this.saveGraph(scope);
28735
+ this._saveDone = this._saveGraph(scope);
28736
+ await this._saveDone;
28114
28737
  }
28115
28738
  async forget(scope, query, filePath) {
28116
28739
  const removed = await this.file.forget(scope, query, filePath);
@@ -28125,7 +28748,8 @@ var GraphMemoryBackend = class {
28125
28748
  }
28126
28749
  for (const id of toRemove) this.nodes.delete(id);
28127
28750
  this.edges = this.edges.filter((e) => !toRemove.includes(e.from) && !toRemove.includes(e.to));
28128
- await this.saveGraph(scope);
28751
+ this._saveDone = this._saveGraph(scope);
28752
+ await this._saveDone;
28129
28753
  }
28130
28754
  return removed;
28131
28755
  }
@@ -28237,7 +28861,8 @@ var GraphMemoryBackend = class {
28237
28861
  this.loadedScope = scope;
28238
28862
  this.loaded = true;
28239
28863
  }
28240
- async saveGraph(scope) {
28864
+ /** Fire-and-forget graph persistence. Named _saveGraph to signal it must not be awaited. */
28865
+ async _saveGraph(scope) {
28241
28866
  this.loadedScope = scope;
28242
28867
  this.loaded = true;
28243
28868
  try {
@@ -28245,16 +28870,21 @@ var GraphMemoryBackend = class {
28245
28870
  nodes: [...this.nodes.entries()],
28246
28871
  edges: this.edges
28247
28872
  };
28248
- await fsp3.mkdir(
28249
- this.graphFile.substring(0, this.graphFile.lastIndexOf("/")),
28250
- { recursive: true }
28251
- );
28873
+ const dir = this.graphFile.substring(0, this.graphFile.lastIndexOf("/"));
28874
+ await fsp3.mkdir(dir, { recursive: true });
28252
28875
  const tmp = `${this.graphFile}.tmp`;
28253
28876
  await fsp3.writeFile(tmp, JSON.stringify(data));
28254
28877
  await fsp3.rename(tmp, this.graphFile);
28255
28878
  } catch {
28256
28879
  }
28257
28880
  }
28881
+ /**
28882
+ * Wait for all in-flight _saveGraph operations to complete.
28883
+ * Call this before deleting the backend or its temp directory.
28884
+ */
28885
+ async flush() {
28886
+ await this._saveDone;
28887
+ }
28258
28888
  };
28259
28889
  function wordOverlap(a, b) {
28260
28890
  const wordsA = new Set(a.toLowerCase().split(/\s+/).filter((w) => w.length > 2));
@@ -28355,84 +28985,89 @@ var SessionMemoryConsolidator = class {
28355
28985
  this.minIterations = opts.minIterations ?? 2;
28356
28986
  this.maxExistingEntries = opts.maxExistingEntries ?? 15;
28357
28987
  }
28358
- afterRun = async (ctx, result) => {
28988
+ afterRun = (ctx, result) => {
28359
28989
  if (result.status !== "done") return;
28360
28990
  if (!result.finalText || result.finalText.trim().length < 20) return;
28361
28991
  if (result.iterations < this.minIterations) return;
28362
28992
  const provider = this.provider ?? ctx.provider;
28363
28993
  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++;
28994
+ const _finalText = result.finalText;
28995
+ const _iterations = result.iterations;
28996
+ const _model = this.model ?? ctx.model;
28997
+ void (async () => {
28998
+ try {
28999
+ const existingEntries = await this.memoryStore.list("project-memory", this.maxExistingEntries);
29000
+ const prompt = buildConsolidationPrompt(
29001
+ _finalText,
29002
+ _iterations,
29003
+ existingEntries
29004
+ );
29005
+ const signal = AbortSignal.timeout(15e3);
29006
+ const response = await provider.complete(
29007
+ {
29008
+ model: _model,
29009
+ system: [{ type: "text", text: prompt }],
29010
+ messages: [
29011
+ { role: "user", content: "Review the session and return memory operations as JSON." }
29012
+ ],
29013
+ maxTokens: 500
29014
+ },
29015
+ { signal }
29016
+ );
29017
+ const text = response.content.filter((b) => b.type === "text").map((b) => b.text).join("").trim();
29018
+ if (!text) return;
29019
+ const jsonMatch = text.match(/\{[\s\S]*\}/);
29020
+ if (!jsonMatch) return;
29021
+ const parsed = JSON.parse(jsonMatch[0]);
29022
+ if (!Array.isArray(parsed.operations) || parsed.operations.length === 0) return;
29023
+ let added = 0;
29024
+ let edited = 0;
29025
+ let deleted = 0;
29026
+ for (const op of parsed.operations) {
29027
+ switch (op.action) {
29028
+ case "add": {
29029
+ if (op.text?.trim()) {
29030
+ await this.memoryStore.remember(op.text.trim(), void 0, {
29031
+ type: op.type,
29032
+ tags: op.tags,
29033
+ priority: op.priority
29034
+ });
29035
+ added++;
29036
+ }
29037
+ break;
28402
29038
  }
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++;
29039
+ case "edit": {
29040
+ if (op.query && op.text?.trim()) {
29041
+ await this.memoryStore.forget(op.query);
29042
+ await this.memoryStore.remember(op.text.trim(), void 0, {
29043
+ type: op.type,
29044
+ tags: op.tags,
29045
+ priority: op.priority
29046
+ });
29047
+ edited++;
29048
+ }
29049
+ break;
28414
29050
  }
28415
- break;
28416
- }
28417
- case "delete": {
28418
- if (op.query) {
28419
- const n = await this.memoryStore.forget(op.query);
28420
- deleted += n;
29051
+ case "delete": {
29052
+ if (op.query) {
29053
+ const n = await this.memoryStore.forget(op.query);
29054
+ deleted += n;
29055
+ }
29056
+ break;
28421
29057
  }
28422
- break;
28423
29058
  }
28424
29059
  }
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(", ")}
29060
+ if (added > 0 || edited > 0 || deleted > 0) {
29061
+ const parts = [];
29062
+ if (added) parts.push(`${added} added`);
29063
+ if (edited) parts.push(`${edited} edited`);
29064
+ if (deleted) parts.push(`${deleted} deleted`);
29065
+ process.stderr.write(`[memory] Session consolidation: ${parts.join(", ")}
28432
29066
  `);
29067
+ }
29068
+ } catch {
28433
29069
  }
28434
- } catch {
28435
- }
29070
+ })();
28436
29071
  };
28437
29072
  };
28438
29073
  function sessionScopedPath(dir, sessionId, suffix) {
@@ -29059,15 +29694,15 @@ var SessionRecovery = class {
29059
29694
  async detectStale(sessionId) {
29060
29695
  const fp = this.filePath(sessionId);
29061
29696
  const TAIL_SIZE = 8192;
29062
- let stat15;
29697
+ let stat16;
29063
29698
  try {
29064
- stat15 = await fsp3.stat(fp);
29699
+ stat16 = await fsp3.stat(fp);
29065
29700
  } catch (err) {
29066
29701
  if (err.code === "ENOENT") return null;
29067
29702
  return null;
29068
29703
  }
29069
- if (stat15.size === 0) return null;
29070
- const position = Math.max(0, stat15.size - TAIL_SIZE);
29704
+ if (stat16.size === 0) return null;
29705
+ const position = Math.max(0, stat16.size - TAIL_SIZE);
29071
29706
  const buf = Buffer.alloc(TAIL_SIZE);
29072
29707
  let fh;
29073
29708
  try {
@@ -29545,6 +30180,9 @@ var AgentStatusTracker = class {
29545
30180
  leaderName;
29546
30181
  // Live agent map: agentId → AgentEntry
29547
30182
  agents = /* @__PURE__ */ new Map();
30183
+ // Last full agent list flushed (leader + subagents). Lets external consumers
30184
+ // read the current state synchronously without re-deriving it.
30185
+ lastAgents = [];
29548
30186
  // Leader tracking
29549
30187
  leaderStatus = "idle";
29550
30188
  leaderCurrentTool;
@@ -29556,6 +30194,7 @@ var AgentStatusTracker = class {
29556
30194
  leaderCtxPct;
29557
30195
  leaderModel;
29558
30196
  leaderPartialText = "";
30197
+ leaderStartedAt;
29559
30198
  unsubscribers = [];
29560
30199
  onUpdate;
29561
30200
  sweepTimer = null;
@@ -29566,9 +30205,17 @@ var AgentStatusTracker = class {
29566
30205
  this.leaderName = opts.leaderName ?? "leader";
29567
30206
  this.onUpdate = opts.onUpdate;
29568
30207
  }
30208
+ /** Current full agent list (leader + subagents) as of the last flush. */
30209
+ getAgents() {
30210
+ return this.lastAgents.length > 0 ? [...this.lastAgents] : [];
30211
+ }
29569
30212
  start() {
29570
30213
  this.unsubscribers.push(
29571
- this.events.onPattern("agent.run.started", () => {
30214
+ this.events.onPattern("agent.run.started", (_event, payload) => {
30215
+ const p = payload;
30216
+ this.markLeaderStarted(p?.at);
30217
+ this.captureLeaderContext(p?.ctx);
30218
+ if (p?.model) this.leaderModel = p.model;
29572
30219
  this.leaderStatus = "running";
29573
30220
  this.leaderIterations++;
29574
30221
  this.flush();
@@ -29576,25 +30223,36 @@ var AgentStatusTracker = class {
29576
30223
  );
29577
30224
  this.unsubscribers.push(
29578
30225
  this.events.onPattern("iteration.started", (_e, payload) => {
29579
- const ctx = payload?.ctx;
29580
- if (!ctx) return;
29581
- if (ctx.model) this.leaderModel = ctx.model;
29582
- if (typeof ctx.tokenCount === "number" && typeof ctx.maxContext === "number" && ctx.maxContext > 0) {
29583
- this.leaderCtxPct = Math.round(ctx.tokenCount / ctx.maxContext * 100);
30226
+ const p = payload;
30227
+ const ctx = p?.ctx;
30228
+ this.markLeaderStarted();
30229
+ this.leaderStatus = "running";
30230
+ if (typeof p?.index === "number") {
30231
+ this.leaderIterations = Math.max(this.leaderIterations, p.index + 1);
29584
30232
  }
30233
+ if (!ctx) {
30234
+ this.flush();
30235
+ return;
30236
+ }
30237
+ this.captureLeaderContext(ctx);
29585
30238
  this.flush();
29586
30239
  })
29587
30240
  );
29588
30241
  this.unsubscribers.push(
29589
- this.events.onPattern("agent.run.completed", () => {
29590
- this.leaderStatus = "idle";
30242
+ this.events.onPattern("agent.run.completed", (_event, payload) => {
30243
+ const p = payload;
30244
+ this.captureLeaderContext(p?.ctx);
30245
+ this.leaderStatus = p?.status === "failed" ? "error" : "idle";
29591
30246
  this.leaderCurrentTool = void 0;
29592
30247
  this.leaderPartialText = "";
30248
+ if (this.leaderStatus === "idle") this.leaderStartedAt = void 0;
29593
30249
  this.flush();
29594
30250
  })
29595
30251
  );
29596
30252
  this.unsubscribers.push(
29597
- this.events.onPattern("agent.run.error", () => {
30253
+ this.events.onPattern("agent.run.error", (_event, payload) => {
30254
+ const p = payload;
30255
+ this.captureLeaderContext(p?.ctx);
29598
30256
  this.leaderStatus = "error";
29599
30257
  this.leaderCurrentTool = void 0;
29600
30258
  this.leaderPartialText = "";
@@ -29605,6 +30263,7 @@ var AgentStatusTracker = class {
29605
30263
  this.events.onPattern("tool.started", (_event, payload) => {
29606
30264
  const p = payload;
29607
30265
  if (p?.name) {
30266
+ this.markLeaderStarted();
29608
30267
  this.leaderCurrentTool = p.name;
29609
30268
  this.leaderToolCalls++;
29610
30269
  }
@@ -29620,12 +30279,14 @@ var AgentStatusTracker = class {
29620
30279
  );
29621
30280
  this.unsubscribers.push(
29622
30281
  this.events.onPattern("brain.ask_human", () => {
30282
+ this.markLeaderStarted();
29623
30283
  this.leaderStatus = "waiting_user";
29624
30284
  this.flush();
29625
30285
  })
29626
30286
  );
29627
30287
  this.unsubscribers.push(
29628
30288
  this.events.onPattern("llm.stream_started", () => {
30289
+ this.markLeaderStarted();
29629
30290
  this.leaderStatus = "streaming";
29630
30291
  this.leaderPartialText = "";
29631
30292
  this.flush();
@@ -29633,14 +30294,42 @@ var AgentStatusTracker = class {
29633
30294
  );
29634
30295
  this.unsubscribers.push(
29635
30296
  this.events.onPattern("provider.text_delta", (_e, payload) => {
29636
- const text = payload?.text;
30297
+ const p = payload;
30298
+ const text = p?.text;
29637
30299
  if (!text) return;
30300
+ this.markLeaderStarted();
30301
+ this.captureLeaderContext(p?.ctx);
29638
30302
  this.leaderStatus = "streaming";
29639
30303
  const next = this.leaderPartialText + text;
29640
30304
  this.leaderPartialText = next.length > PARTIAL_TEXT_CAP ? next.slice(next.length - PARTIAL_TEXT_CAP) : next;
29641
30305
  this.schedulePartialFlush();
29642
30306
  })
29643
30307
  );
30308
+ this.unsubscribers.push(
30309
+ this.events.onPattern("provider.response", (_e, payload) => {
30310
+ const p = payload;
30311
+ this.captureLeaderContext(p?.ctx);
30312
+ this.flush();
30313
+ })
30314
+ );
30315
+ this.unsubscribers.push(
30316
+ this.events.onPattern("provider.fallback", (_e, payload) => {
30317
+ const p = payload;
30318
+ if (p?.to?.model) {
30319
+ this.leaderModel = p.to.providerId ? `${p.to.providerId}/${p.to.model}` : p.to.model;
30320
+ this.flush();
30321
+ }
30322
+ })
30323
+ );
30324
+ this.unsubscribers.push(
30325
+ this.events.onPattern("ctx.pct", (_e, payload) => {
30326
+ const p = payload;
30327
+ if (typeof p?.load === "number" && Number.isFinite(p.load)) {
30328
+ this.leaderCtxPct = Math.round(p.load * 100);
30329
+ this.flush();
30330
+ }
30331
+ })
30332
+ );
29644
30333
  this.unsubscribers.push(
29645
30334
  this.events.onPattern("token.accounted", (_e, payload) => {
29646
30335
  const p = payload;
@@ -29654,7 +30343,8 @@ var AgentStatusTracker = class {
29654
30343
  const touch = (id) => {
29655
30344
  let entry = this.agents.get(id);
29656
30345
  if (!entry) {
29657
- entry = { id, name: id, status: "idle", iterations: 0, toolCalls: 0, lastActivityAt: (/* @__PURE__ */ new Date()).toISOString() };
30346
+ const now = (/* @__PURE__ */ new Date()).toISOString();
30347
+ entry = { id, name: id, status: "idle", iterations: 0, toolCalls: 0, startedAt: now, lastActivityAt: now };
29658
30348
  this.agents.set(id, entry);
29659
30349
  }
29660
30350
  entry.lastActivityAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -29667,6 +30357,7 @@ var AgentStatusTracker = class {
29667
30357
  const entry = touch(p.subagentId);
29668
30358
  entry.name = p.name?.trim() || entry.name;
29669
30359
  if (p.model) entry.model = p.model;
30360
+ if (!entry.startedAt) entry.startedAt = (/* @__PURE__ */ new Date()).toISOString();
29670
30361
  entry.status = "running";
29671
30362
  this.flush();
29672
30363
  })
@@ -29686,6 +30377,7 @@ var AgentStatusTracker = class {
29686
30377
  if (!p?.subagentId) return;
29687
30378
  const entry = touch(p.subagentId);
29688
30379
  entry.status = "running";
30380
+ if (!entry.startedAt) entry.startedAt = (/* @__PURE__ */ new Date()).toISOString();
29689
30381
  entry.iterations++;
29690
30382
  this.flush();
29691
30383
  })
@@ -29696,6 +30388,7 @@ var AgentStatusTracker = class {
29696
30388
  if (!p?.subagentId) return;
29697
30389
  const entry = touch(p.subagentId);
29698
30390
  entry.status = "running";
30391
+ if (!entry.startedAt) entry.startedAt = (/* @__PURE__ */ new Date()).toISOString();
29699
30392
  entry.currentTool = p.name;
29700
30393
  entry.toolCalls++;
29701
30394
  this.flush();
@@ -29707,6 +30400,7 @@ var AgentStatusTracker = class {
29707
30400
  if (!p?.subagentId) return;
29708
30401
  const entry = touch(p.subagentId);
29709
30402
  entry.status = "running";
30403
+ if (!entry.startedAt) entry.startedAt = (/* @__PURE__ */ new Date()).toISOString();
29710
30404
  if (typeof p.iteration === "number") entry.iterations = p.iteration;
29711
30405
  if (typeof p.toolCalls === "number") entry.toolCalls = p.toolCalls;
29712
30406
  if (typeof p.costUsd === "number") entry.costUsd = p.costUsd;
@@ -29794,6 +30488,7 @@ var AgentStatusTracker = class {
29794
30488
  const leaderEntry = {
29795
30489
  id: "leader",
29796
30490
  name: this.leaderName,
30491
+ startedAt: this.leaderStartedAt,
29797
30492
  status: this.leaderStatus,
29798
30493
  currentTool: this.leaderCurrentTool,
29799
30494
  iterations: this.leaderIterations,
@@ -29807,6 +30502,11 @@ var AgentStatusTracker = class {
29807
30502
  lastActivityAt: (/* @__PURE__ */ new Date()).toISOString()
29808
30503
  };
29809
30504
  const allAgents = [leaderEntry, ...this.agents.values()];
30505
+ this.lastAgents = allAgents;
30506
+ try {
30507
+ this.events.emit("session.agents_updated", { agents: allAgents });
30508
+ } catch {
30509
+ }
29810
30510
  this.registry.updateAgents(allAgents).then(() => {
29811
30511
  try {
29812
30512
  this.onUpdate?.();
@@ -29814,6 +30514,23 @@ var AgentStatusTracker = class {
29814
30514
  }
29815
30515
  }).catch(() => void 0);
29816
30516
  }
30517
+ markLeaderStarted(startedAt) {
30518
+ if (this.leaderStartedAt && (this.leaderStatus === "running" || this.leaderStatus === "streaming" || this.leaderStatus === "waiting_user")) {
30519
+ return;
30520
+ }
30521
+ this.leaderStartedAt = startedAt ?? (/* @__PURE__ */ new Date()).toISOString();
30522
+ }
30523
+ captureLeaderContext(ctx) {
30524
+ if (typeof ctx !== "object" || ctx === null) return;
30525
+ const c = ctx;
30526
+ if (typeof c.model === "string" && c.model.length > 0) this.leaderModel = c.model;
30527
+ const metaLimit = c.meta?.["effectiveMaxContext"];
30528
+ const providerMax = c.provider?.capabilities?.maxContext;
30529
+ const maxContext = typeof metaLimit === "number" && metaLimit > 0 ? metaLimit : typeof providerMax === "number" && providerMax > 0 ? providerMax : void 0;
30530
+ if (typeof c.lastRequestTokens === "number" && c.lastRequestTokens > 0 && maxContext !== void 0) {
30531
+ this.leaderCtxPct = Math.round(c.lastRequestTokens / maxContext * 100);
30532
+ }
30533
+ }
29817
30534
  };
29818
30535
  var INSTANCES_FILE = "webui-instances.json";
29819
30536
  var DISCOVERY_TTL_MS = 2500;
@@ -30466,8 +31183,8 @@ var CloudSync = class {
30466
31183
  const localPath = this.categoryToPath(cat);
30467
31184
  if (!localPath) continue;
30468
31185
  try {
30469
- const stat15 = await fsp3.stat(localPath);
30470
- if (stat15.isDirectory()) {
31186
+ const stat16 = await fsp3.stat(localPath);
31187
+ if (stat16.isDirectory()) {
30471
31188
  const files = await this.walkDir(localPath, localPath);
30472
31189
  for (const file of files) {
30473
31190
  const content = await fsp3.readFile(file, "utf8");
@@ -30492,8 +31209,8 @@ var CloudSync = class {
30492
31209
  const localPath = this.categoryToPath(cat);
30493
31210
  if (!localPath) continue;
30494
31211
  try {
30495
- const stat15 = await fsp3.stat(localPath);
30496
- if (stat15.isDirectory()) {
31212
+ const stat16 = await fsp3.stat(localPath);
31213
+ if (stat16.isDirectory()) {
30497
31214
  const files = await this.walkDir(localPath, localPath);
30498
31215
  for (const file of files) {
30499
31216
  const content = await fsp3.readFile(file);
@@ -30684,7 +31401,13 @@ function parseHqFrame(raw) {
30684
31401
  }
30685
31402
  }
30686
31403
  }
30687
- var KNOWN_HQ_EVENT_PAYLOAD_TYPES = /* @__PURE__ */ new Set(["mailbox.snapshot", "mailbox.event"]);
31404
+ var KNOWN_HQ_EVENT_PAYLOAD_TYPES = /* @__PURE__ */ new Set([
31405
+ "mailbox.snapshot",
31406
+ "mailbox.event",
31407
+ "session.snapshot",
31408
+ "session.transcript",
31409
+ "session.ended"
31410
+ ]);
30688
31411
  function isHqMailboxMessageSummary(x) {
30689
31412
  if (typeof x !== "object" || x === null) return false;
30690
31413
  const v = x;
@@ -30731,6 +31454,52 @@ function isHqMailboxEventPayload(x) {
30731
31454
  if (v.agent !== void 0 && !isHqMailboxAgentSummary(v.agent)) return false;
30732
31455
  return true;
30733
31456
  }
31457
+ var HQ_SESSION_AGENT_STATUSES = /* @__PURE__ */ new Set([
31458
+ "idle",
31459
+ "running",
31460
+ "streaming",
31461
+ "waiting_user",
31462
+ "error"
31463
+ ]);
31464
+ function isHqSessionAgentSummary(x) {
31465
+ if (typeof x !== "object" || x === null) return false;
31466
+ const v = x;
31467
+ 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";
31468
+ }
31469
+ var HQ_SESSION_STATUSES = /* @__PURE__ */ new Set(["active", "idle", "closing", "stale"]);
31470
+ function isHqSessionSnapshotPayload(x) {
31471
+ if (typeof x !== "object" || x === null) return false;
31472
+ const v = x;
31473
+ 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)) {
31474
+ return false;
31475
+ }
31476
+ for (const agent of v.agents) {
31477
+ if (!isHqSessionAgentSummary(agent)) return false;
31478
+ }
31479
+ return true;
31480
+ }
31481
+ var HQ_TRANSCRIPT_ROLES = /* @__PURE__ */ new Set(["user", "assistant", "tool", "system", "error"]);
31482
+ function isHqTranscriptEntry(x) {
31483
+ if (typeof x !== "object" || x === null) return false;
31484
+ const v = x;
31485
+ return typeof v.ts === "string" && typeof v.role === "string" && HQ_TRANSCRIPT_ROLES.has(v.role) && typeof v.text === "string";
31486
+ }
31487
+ function isHqTranscriptAppendPayload(x) {
31488
+ if (typeof x !== "object" || x === null) return false;
31489
+ const v = x;
31490
+ if (typeof v.sessionId !== "string" || typeof v.fromSeq !== "number" || !Array.isArray(v.entries)) {
31491
+ return false;
31492
+ }
31493
+ for (const entry of v.entries) {
31494
+ if (!isHqTranscriptEntry(entry)) return false;
31495
+ }
31496
+ return true;
31497
+ }
31498
+ function isHqSessionEndedPayload(x) {
31499
+ if (typeof x !== "object" || x === null) return false;
31500
+ const v = x;
31501
+ return typeof v.sessionId === "string" && typeof v.endedAt === "string";
31502
+ }
30734
31503
  function parseHqEventPayload(eventType, payload) {
30735
31504
  if (!KNOWN_HQ_EVENT_PAYLOAD_TYPES.has(eventType)) {
30736
31505
  return { ok: true, payload };
@@ -30740,6 +31509,12 @@ function parseHqEventPayload(eventType, payload) {
30740
31509
  return isHqMailboxSnapshotPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
30741
31510
  case "mailbox.event":
30742
31511
  return isHqMailboxEventPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
31512
+ case "session.snapshot":
31513
+ return isHqSessionSnapshotPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
31514
+ case "session.transcript":
31515
+ return isHqTranscriptAppendPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
31516
+ case "session.ended":
31517
+ return isHqSessionEndedPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
30743
31518
  default: {
30744
31519
  const _exhaustive = eventType;
30745
31520
  return _exhaustive;
@@ -31210,6 +31985,41 @@ var HqPublisher = class {
31210
31985
  ...input.timestamp !== void 0 ? { timestamp: input.timestamp } : {}
31211
31986
  });
31212
31987
  }
31988
+ /** The client identity this publisher announced (clientId, kind, machineId, …). */
31989
+ get identity() {
31990
+ return this.options.client;
31991
+ }
31992
+ /** The project identity this publisher is bound to. */
31993
+ get project() {
31994
+ return this.options.project;
31995
+ }
31996
+ /** Publish a live session/terminal snapshot (state + agents). */
31997
+ publishSessionSnapshot(payload, opts) {
31998
+ return this.publishEvent({
31999
+ type: "session.snapshot",
32000
+ payload,
32001
+ sessionId: payload.sessionId,
32002
+ ...opts?.timestamp !== void 0 ? { timestamp: opts.timestamp } : {}
32003
+ });
32004
+ }
32005
+ /** Publish an incremental batch of transcript turns for a session. */
32006
+ publishTranscriptAppend(payload, opts) {
32007
+ return this.publishEvent({
32008
+ type: "session.transcript",
32009
+ payload,
32010
+ sessionId: payload.sessionId,
32011
+ ...opts?.timestamp !== void 0 ? { timestamp: opts.timestamp } : {}
32012
+ });
32013
+ }
32014
+ /** Mark a session/terminal as ended. */
32015
+ publishSessionEnded(payload, opts) {
32016
+ return this.publishEvent({
32017
+ type: "session.ended",
32018
+ payload,
32019
+ sessionId: payload.sessionId,
32020
+ ...opts?.timestamp !== void 0 ? { timestamp: opts.timestamp } : {}
32021
+ });
32022
+ }
31213
32023
  pollCommands() {
31214
32024
  this.sendFrame({
31215
32025
  type: "client.command_poll",
@@ -31395,7 +32205,7 @@ var GlobalMailbox = class {
31395
32205
  /**
31396
32206
  * @param projectDir — `~/.wrongstack/projects/<slug>/`
31397
32207
  * @param events — optional EventBus for real-time TUI/WebUI notifications
31398
- * @param hqPublisher — optional HQ publisher for cross-project telemetry
32208
+ * @param hqPublisher — optional HQ publisher, or getter, for cross-project telemetry
31399
32209
  */
31400
32210
  constructor(projectDir, events, hqPublisher) {
31401
32211
  this.messagePath = path3.join(projectDir, MAILBOX_FILE);
@@ -31407,15 +32217,19 @@ var GlobalMailbox = class {
31407
32217
  get hqMailboxId() {
31408
32218
  return `${path3.basename(path3.dirname(this.messagePath))}:mailbox`;
31409
32219
  }
32220
+ get hqPublisher() {
32221
+ return typeof this._hqPublisher === "function" ? this._hqPublisher() : this._hqPublisher;
32222
+ }
31410
32223
  publishHqMailboxEvent(input) {
31411
32224
  try {
31412
- this._hqPublisher?.publishMailboxEvent(input);
32225
+ this.hqPublisher?.publishMailboxEvent(input);
31413
32226
  } catch {
31414
32227
  }
31415
32228
  }
31416
32229
  publishHqMailboxSnapshot() {
31417
- if (this._hqPublisher === void 0) return;
31418
- void this._hqPublisher.publishMailboxSnapshot(this, { mailboxId: this.hqMailboxId }).catch(() => {
32230
+ const publisher = this.hqPublisher;
32231
+ if (publisher === void 0) return;
32232
+ void publisher.publishMailboxSnapshot(this, { mailboxId: this.hqMailboxId }).catch(() => {
31419
32233
  });
31420
32234
  }
31421
32235
  // ── Messages ────────────────────────────────────────────────────────────
@@ -31777,30 +32591,69 @@ var GlobalMailbox = class {
31777
32591
  async _readMessages() {
31778
32592
  try {
31779
32593
  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;
32594
+ return this._parseLines(raw);
31799
32595
  } catch (err) {
31800
32596
  if (err.code === "ENOENT") return [];
31801
32597
  throw err;
31802
32598
  }
31803
32599
  }
32600
+ /**
32601
+ * Read only newly-appended bytes from the file and append them to the
32602
+ * in-memory cache. This avoids re-reading and re-parsing the entire file
32603
+ * when another process appended messages since our last read.
32604
+ *
32605
+ * Only safe when the file grew in size (messages are append-only). When
32606
+ * the file was rewritten (ack/purge changed existing content), callers
32607
+ * must fall back to {@link _readMessages}.
32608
+ *
32609
+ * @returns The (now up-to-date) message cache.
32610
+ */
32611
+ async _readNewMessagesOnly(fd, oldSize, newSize) {
32612
+ const tailLen = newSize - oldSize;
32613
+ const buf = Buffer.alloc(tailLen);
32614
+ await fd.read(buf, 0, tailLen, oldSize);
32615
+ const tail = buf.toString("utf8");
32616
+ for (const line of tail.split(LINE_SEPARATOR)) {
32617
+ if (!line.trim()) continue;
32618
+ try {
32619
+ const parsed = JSON.parse(line);
32620
+ if (!parsed["readBy"]) {
32621
+ const readBy = {};
32622
+ if (parsed["read"] && parsed["readAt"]) {
32623
+ readBy[parsed["to"]] = parsed["readAt"];
32624
+ }
32625
+ parsed["readBy"] = readBy;
32626
+ delete parsed["read"];
32627
+ delete parsed["readAt"];
32628
+ }
32629
+ this._messageCache.push(parsed);
32630
+ } catch {
32631
+ }
32632
+ }
32633
+ return this._messageCache;
32634
+ }
32635
+ /** Parse a JSONL string into MailboxMessage[], including migration. */
32636
+ _parseLines(raw) {
32637
+ const lines = raw.split(LINE_SEPARATOR).filter((l) => l.trim().length > 0);
32638
+ const messages = [];
32639
+ for (const line of lines) {
32640
+ try {
32641
+ const parsed = JSON.parse(line);
32642
+ if (!parsed["readBy"]) {
32643
+ const readBy = {};
32644
+ if (parsed["read"] && parsed["readAt"]) {
32645
+ readBy[parsed["to"]] = parsed["readAt"];
32646
+ }
32647
+ parsed["readBy"] = readBy;
32648
+ delete parsed["read"];
32649
+ delete parsed["readAt"];
32650
+ }
32651
+ messages.push(parsed);
32652
+ } catch {
32653
+ }
32654
+ }
32655
+ return messages;
32656
+ }
31804
32657
  /**
31805
32658
  * Read messages, then adopt the result as the in-memory cache. Use this
31806
32659
  * from writers that just took the file lock — the read reflects the
@@ -31820,6 +32673,10 @@ var GlobalMailbox = class {
31820
32673
  * stat matches the cached mtime+size we return the cached array — no
31821
32674
  * file read and no JSON.parse — collapsing the per-iteration query
31822
32675
  * cost on the mailbox-loop hot path.
32676
+ *
32677
+ * When the file only grew (new messages appended by another process),
32678
+ * we read and parse just the tail bytes instead of the entire file.
32679
+ * This avoids re-parsing the full 10K-message history on every check.
31823
32680
  */
31824
32681
  async _readMessagesCached() {
31825
32682
  try {
@@ -31827,6 +32684,17 @@ var GlobalMailbox = class {
31827
32684
  if (this._messageCache !== null && this._messageCacheMtime === st.mtimeMs && this._messageCacheSize === st.size) {
31828
32685
  return this._messageCache;
31829
32686
  }
32687
+ if (this._messageCache !== null && this._messageCacheSize >= 0 && st.size > this._messageCacheSize) {
32688
+ const fd = await fsp3.open(this.messagePath, "r");
32689
+ try {
32690
+ const updated = await this._readNewMessagesOnly(fd, this._messageCacheSize, st.size);
32691
+ this._messageCacheMtime = st.mtimeMs;
32692
+ this._messageCacheSize = st.size;
32693
+ return updated;
32694
+ } finally {
32695
+ await fd.close();
32696
+ }
32697
+ }
31830
32698
  const all = await this._readMessages();
31831
32699
  this._setMessageCache(all, st.mtimeMs, st.size);
31832
32700
  return all;
@@ -32191,7 +33059,7 @@ function resolveHqConfig(options = {}) {
32191
33059
  };
32192
33060
  }
32193
33061
  function stableMachineId() {
32194
- return createHash("sha256").update(`${hostname()}:${process.pid}`).digest("hex").slice(0, 12);
33062
+ return createHash("sha256").update(hostname()).digest("hex").slice(0, 12);
32195
33063
  }
32196
33064
  function deriveProjectId(projectRoot) {
32197
33065
  return createHash("sha256").update(projectRoot).digest("hex").slice(0, 12);
@@ -32233,6 +33101,381 @@ function createHqPublisherFromEnv(options) {
32233
33101
  function createGlobalMailbox(options) {
32234
33102
  return new GlobalMailbox(options.projectDir, options.events, options.hqPublisher);
32235
33103
  }
33104
+
33105
+ // src/hq/agent-bridge.ts
33106
+ function startAgentMonitorEventBridge(opts) {
33107
+ const { events, clientId, projectId, publish } = opts;
33108
+ const seq = { current: 0 };
33109
+ function nextSeq() {
33110
+ seq.current += 1;
33111
+ return seq.current;
33112
+ }
33113
+ function buildEnvelope(type, payload) {
33114
+ return createHqEventEnvelope({
33115
+ id: `${Date.now().toString(36)}-${nextSeq()}`,
33116
+ type,
33117
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
33118
+ clientId,
33119
+ projectId,
33120
+ seq: nextSeq(),
33121
+ payload
33122
+ });
33123
+ }
33124
+ const offMessage = events.on("agent.timeline.message", (payload) => {
33125
+ if (!publish) return;
33126
+ const msgPayload = {
33127
+ subagentId: payload.subagentId,
33128
+ agentName: payload.agentName,
33129
+ content: payload.content,
33130
+ kind: payload.kind,
33131
+ iteration: payload.iteration,
33132
+ ts: payload.ts
33133
+ };
33134
+ if (payload.toolName !== void 0) msgPayload.toolName = payload.toolName;
33135
+ if (payload.costUsd !== void 0) msgPayload.costUsd = payload.costUsd;
33136
+ publish(buildEnvelope("agent.message", msgPayload));
33137
+ });
33138
+ const offStatus = events.on("agent.status_changed", (payload) => {
33139
+ if (!publish) return;
33140
+ const statusPayload = {
33141
+ subagentId: payload.subagentId,
33142
+ agentName: payload.agentName,
33143
+ status: payload.status,
33144
+ ts: payload.ts
33145
+ };
33146
+ if (payload.summary !== void 0) statusPayload.summary = payload.summary;
33147
+ if (payload.task !== void 0) statusPayload.task = payload.task;
33148
+ publish(buildEnvelope("agent.status", statusPayload));
33149
+ });
33150
+ return () => {
33151
+ offMessage();
33152
+ offStatus();
33153
+ };
33154
+ }
33155
+
33156
+ // src/hq/transcript-mapper.ts
33157
+ function blocksToText(content) {
33158
+ if (typeof content === "string") return content;
33159
+ if (Array.isArray(content)) {
33160
+ return content.filter(
33161
+ (b) => !!b && typeof b === "object" && b.type === "text" && typeof b.text === "string"
33162
+ ).map((b) => b.text).join("\n");
33163
+ }
33164
+ return "";
33165
+ }
33166
+ function asString(v) {
33167
+ if (typeof v === "string") return v;
33168
+ try {
33169
+ return JSON.stringify(v, null, 2);
33170
+ } catch {
33171
+ return String(v);
33172
+ }
33173
+ }
33174
+ function argsEntry(ts, name, input, id) {
33175
+ return {
33176
+ ts,
33177
+ role: "tool",
33178
+ tool: String(name ?? "tool"),
33179
+ toolInput: input !== void 0 && input !== null ? asString(input) : "{}",
33180
+ text: "",
33181
+ ...typeof id === "string" ? { toolUseId: id } : {}
33182
+ };
33183
+ }
33184
+ function mapSessionEventToEntries(ev) {
33185
+ const ts = typeof ev["ts"] === "string" ? ev["ts"] : "";
33186
+ const make = (role, text, extra) => ({
33187
+ ts,
33188
+ role,
33189
+ text,
33190
+ ...extra
33191
+ });
33192
+ switch (ev["type"]) {
33193
+ case "user_input": {
33194
+ const t2 = blocksToText(ev["content"]);
33195
+ return t2.trim() ? [make("user", t2)] : [];
33196
+ }
33197
+ case "llm_response": {
33198
+ const out = [];
33199
+ const t2 = blocksToText(ev["content"]);
33200
+ if (t2.trim()) out.push(make("assistant", t2));
33201
+ const content = ev["content"];
33202
+ if (Array.isArray(content)) {
33203
+ for (const b of content) {
33204
+ if (b && typeof b === "object" && b.type === "tool_use") {
33205
+ const block = b;
33206
+ out.push(argsEntry(ts, block.name, block.input, block.id));
33207
+ }
33208
+ }
33209
+ }
33210
+ return out;
33211
+ }
33212
+ case "tool_use":
33213
+ return [argsEntry(ts, ev["name"], ev["input"], ev["id"])];
33214
+ case "tool_call_start":
33215
+ return [argsEntry(ts, ev["name"], ev["input"] ?? ev["args"], ev["id"])];
33216
+ case "tool_result": {
33217
+ const isError = ev["isError"] === true;
33218
+ const content = ev["content"] ?? ev["output"];
33219
+ const outStr = content !== void 0 && content !== null ? asString(content) : "";
33220
+ return [
33221
+ make(isError ? "error" : "tool", outStr, {
33222
+ tool: "\u21B3 result",
33223
+ ...isError ? { isError: true } : {},
33224
+ ...typeof ev["id"] === "string" ? { toolUseId: ev["id"] } : {}
33225
+ })
33226
+ ];
33227
+ }
33228
+ case "tool_call_end": {
33229
+ const isError = ev["isError"] === true;
33230
+ const content = ev["output"] ?? ev["content"];
33231
+ const outStr = content !== void 0 && content !== null ? asString(content) : "";
33232
+ if (!outStr.trim() && !isError && typeof ev["durationMs"] !== "number") return [];
33233
+ return [
33234
+ make(isError ? "error" : "tool", outStr, {
33235
+ tool: typeof ev["name"] === "string" ? String(ev["name"]) : "\u21B3 result",
33236
+ ...typeof ev["durationMs"] === "number" ? { durationMs: ev["durationMs"] } : {},
33237
+ ...isError ? { isError: true } : {},
33238
+ ...typeof ev["id"] === "string" ? { toolUseId: ev["id"] } : {}
33239
+ })
33240
+ ];
33241
+ }
33242
+ case "error":
33243
+ case "provider_error":
33244
+ return [make("error", String(ev["message"] ?? "error"))];
33245
+ case "agent_spawned":
33246
+ return [make("system", `spawned ${String(ev["role"] ?? "agent")}`)];
33247
+ case "task_completed":
33248
+ return [make("system", `task done: ${String(ev["title"] ?? "")}`)];
33249
+ case "task_failed":
33250
+ return [make("system", `task failed: ${String(ev["title"] ?? "")}`)];
33251
+ default:
33252
+ return [];
33253
+ }
33254
+ }
33255
+ function isResultEntry(e) {
33256
+ return (e.role === "tool" || e.role === "error") && e.toolUseId !== void 0 && e.toolInput === void 0;
33257
+ }
33258
+ function mergeToolResults(flat) {
33259
+ const out = [];
33260
+ const argsById = /* @__PURE__ */ new Map();
33261
+ for (const src of flat) {
33262
+ const e = { ...src };
33263
+ if (isResultEntry(e) && e.toolUseId !== void 0) {
33264
+ const tgt = argsById.get(e.toolUseId);
33265
+ if (tgt) {
33266
+ tgt.text = e.text || "";
33267
+ if (e.durationMs !== void 0) tgt.durationMs = e.durationMs;
33268
+ if (e.isError) {
33269
+ tgt.role = "error";
33270
+ tgt.isError = true;
33271
+ }
33272
+ continue;
33273
+ }
33274
+ }
33275
+ out.push(e);
33276
+ if (e.role === "tool" && e.toolUseId !== void 0 && e.toolInput !== void 0) {
33277
+ argsById.set(e.toolUseId, e);
33278
+ }
33279
+ }
33280
+ return out;
33281
+ }
33282
+ function buildTranscriptFromEvents(events) {
33283
+ const flat = [];
33284
+ for (const ev of events) {
33285
+ for (const e of mapSessionEventToEntries(ev)) flat.push(e);
33286
+ }
33287
+ return mergeToolResults(flat);
33288
+ }
33289
+
33290
+ // src/hq/session-bridge.ts
33291
+ var VALID_AGENT_STATUS = /* @__PURE__ */ new Set([
33292
+ "idle",
33293
+ "running",
33294
+ "streaming",
33295
+ "waiting_user",
33296
+ "error"
33297
+ ]);
33298
+ function toAgentSummary(a) {
33299
+ const status = VALID_AGENT_STATUS.has(a.status) ? a.status : "idle";
33300
+ return {
33301
+ id: a.id,
33302
+ name: a.name,
33303
+ status,
33304
+ iterations: a.iterations,
33305
+ toolCalls: a.toolCalls,
33306
+ lastActivityAt: a.lastActivityAt,
33307
+ ...a.startedAt !== void 0 ? { startedAt: a.startedAt } : {},
33308
+ ...a.currentTool !== void 0 ? { currentTool: a.currentTool } : {},
33309
+ ...a.costUsd !== void 0 ? { costUsd: a.costUsd } : {},
33310
+ ...a.tokensIn !== void 0 ? { tokensIn: a.tokensIn } : {},
33311
+ ...a.tokensOut !== void 0 ? { tokensOut: a.tokensOut } : {},
33312
+ ...a.ctxPct !== void 0 ? { ctxPct: a.ctxPct } : {},
33313
+ ...a.model !== void 0 ? { model: a.model } : {},
33314
+ ...a.partialText !== void 0 ? { partialText: a.partialText } : {}
33315
+ };
33316
+ }
33317
+ function deriveSessionStatus(agents) {
33318
+ return agents.some(
33319
+ (a) => a.status === "running" || a.status === "streaming" || a.status === "waiting_user"
33320
+ ) ? "active" : "idle";
33321
+ }
33322
+ function startSessionTelemetryBridge(opts) {
33323
+ const now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
33324
+ const publisher = opts.publisher;
33325
+ const identity = publisher.identity;
33326
+ const project = publisher.project;
33327
+ const startedAt = opts.startedAt ?? now();
33328
+ const wpaths = resolveWstackPaths({
33329
+ projectRoot: opts.projectRoot,
33330
+ ...opts.globalRoot !== void 0 ? { globalRoot: opts.globalRoot } : {}
33331
+ });
33332
+ const sessionFile = path3.join(wpaths.projectSessions, `${opts.sessionId}.jsonl`);
33333
+ let agents = (opts.initialAgents ?? []).map(toAgentSummary);
33334
+ let lastActivityAt = agents.reduce(
33335
+ (latest, agent) => agent.lastActivityAt > latest ? agent.lastActivityAt : latest,
33336
+ startedAt
33337
+ );
33338
+ let lastSnapshotHash = "";
33339
+ let disposed = false;
33340
+ function buildSnapshot() {
33341
+ return {
33342
+ sessionId: opts.sessionId,
33343
+ clientKind: identity.kind,
33344
+ machineId: identity.machineId,
33345
+ projectId: project.projectId,
33346
+ projectName: opts.projectName ?? project.projectName,
33347
+ projectRoot: opts.projectRoot,
33348
+ status: deriveSessionStatus(agents),
33349
+ startedAt,
33350
+ lastActivityAt,
33351
+ agentCount: agents.length,
33352
+ agents,
33353
+ ...identity.hostname !== void 0 ? { hostname: identity.hostname } : {},
33354
+ ...identity.pid !== void 0 ? { pid: identity.pid } : {},
33355
+ ...opts.gitBranch !== void 0 ? { gitBranch: opts.gitBranch } : {}
33356
+ };
33357
+ }
33358
+ function publishSnapshot(force = false) {
33359
+ if (disposed) return;
33360
+ const snap = buildSnapshot();
33361
+ const hash = JSON.stringify({ ...snap, lastActivityAt: "" });
33362
+ if (!force && hash === lastSnapshotHash) return;
33363
+ lastSnapshotHash = hash;
33364
+ try {
33365
+ publisher.publishSessionSnapshot(snap);
33366
+ } catch {
33367
+ }
33368
+ }
33369
+ const offAgents = opts.events?.on("session.agents_updated", (payload) => {
33370
+ agents = payload.agents.map(toAgentSummary);
33371
+ lastActivityAt = now();
33372
+ publishSnapshot();
33373
+ });
33374
+ publishSnapshot(true);
33375
+ let offset = 0;
33376
+ let partial = "";
33377
+ let seqEmitted = 0;
33378
+ let tailing = false;
33379
+ let watcher = null;
33380
+ let watchPending = false;
33381
+ function setupWatcher() {
33382
+ if (disposed || watcher) return;
33383
+ try {
33384
+ const nextWatcher = fs3.watch(sessionFile, () => {
33385
+ if (watchPending || disposed) return;
33386
+ watchPending = true;
33387
+ setTimeout(() => {
33388
+ watchPending = false;
33389
+ void tail();
33390
+ }, 25);
33391
+ });
33392
+ nextWatcher.on("error", () => {
33393
+ try {
33394
+ nextWatcher.close();
33395
+ } catch {
33396
+ }
33397
+ if (watcher === nextWatcher) watcher = null;
33398
+ });
33399
+ watcher = nextWatcher;
33400
+ } catch {
33401
+ watcher = null;
33402
+ }
33403
+ }
33404
+ async function tail() {
33405
+ if (disposed || tailing) return;
33406
+ tailing = true;
33407
+ try {
33408
+ const stat16 = await fsp3.stat(sessionFile).catch(() => null);
33409
+ if (disposed) return;
33410
+ if (!stat16) return;
33411
+ setupWatcher();
33412
+ if (stat16.size <= offset) return;
33413
+ const fd = await fsp3.open(sessionFile, "r");
33414
+ try {
33415
+ if (disposed) return;
33416
+ const len = stat16.size - offset;
33417
+ const buf = Buffer.allocUnsafe(len);
33418
+ await fd.read(buf, 0, len, offset);
33419
+ offset = stat16.size;
33420
+ partial += buf.toString("utf8");
33421
+ const lines = partial.split("\n");
33422
+ partial = lines.pop() ?? "";
33423
+ const entries = [];
33424
+ for (const line of lines) {
33425
+ const trimmed = line.trim();
33426
+ if (!trimmed) continue;
33427
+ let obj;
33428
+ try {
33429
+ obj = JSON.parse(trimmed);
33430
+ } catch {
33431
+ continue;
33432
+ }
33433
+ for (const entry of mapSessionEventToEntries(obj)) entries.push(entry);
33434
+ }
33435
+ if (entries.length > 0) {
33436
+ try {
33437
+ publisher.publishTranscriptAppend({
33438
+ sessionId: opts.sessionId,
33439
+ fromSeq: seqEmitted,
33440
+ entries
33441
+ });
33442
+ } catch {
33443
+ }
33444
+ seqEmitted += entries.length;
33445
+ lastActivityAt = now();
33446
+ }
33447
+ } finally {
33448
+ await fd.close();
33449
+ }
33450
+ } catch {
33451
+ } finally {
33452
+ tailing = false;
33453
+ }
33454
+ }
33455
+ const snapshotTimer = setInterval(() => publishSnapshot(true), opts.snapshotIntervalMs ?? 2500);
33456
+ const tailTimer = setInterval(() => void tail(), opts.transcriptIntervalMs ?? 500);
33457
+ snapshotTimer.unref?.();
33458
+ tailTimer.unref?.();
33459
+ void tail();
33460
+ return () => {
33461
+ if (disposed) return;
33462
+ disposed = true;
33463
+ offAgents?.();
33464
+ if (watcher) {
33465
+ try {
33466
+ watcher.close();
33467
+ } catch {
33468
+ }
33469
+ watcher = null;
33470
+ }
33471
+ clearInterval(snapshotTimer);
33472
+ clearInterval(tailTimer);
33473
+ try {
33474
+ publisher.publishSessionEnded({ sessionId: opts.sessionId, endedAt: now() });
33475
+ } catch {
33476
+ }
33477
+ };
33478
+ }
32236
33479
  var MATCHERS = {
32237
33480
  pnpmWorkspace: (files) => files.includes("pnpm-workspace.yaml"),
32238
33481
  gradlew: (_files, dirs) => dirs.includes("gradlew"),
@@ -33110,8 +34353,8 @@ var ReportGenerator = class {
33110
34353
  try {
33111
34354
  await stat(this.options.outputDir);
33112
34355
  } catch {
33113
- const { mkdir: mkdir23 } = await import('fs/promises');
33114
- await mkdir23(this.options.outputDir, { recursive: true });
34356
+ const { mkdir: mkdir24 } = await import('fs/promises');
34357
+ await mkdir24(this.options.outputDir, { recursive: true });
33115
34358
  }
33116
34359
  }
33117
34360
  generateMarkdown(result) {
@@ -33399,7 +34642,7 @@ var SecurityScannerOrchestrator = class {
33399
34642
  message: errAsErr.message,
33400
34643
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
33401
34644
  }));
33402
- await new Promise((resolve18) => setTimeout(resolve18, delay));
34645
+ await new Promise((resolve19) => setTimeout(resolve19, delay));
33403
34646
  return this.completeWithRetry(provider, request, abortController, attempt + 1);
33404
34647
  }
33405
34648
  }
@@ -34532,12 +35775,12 @@ var BrainDecisionQueue = class {
34532
35775
  options: request.options,
34533
35776
  rationale: "Decision escalated to human authority."
34534
35777
  };
34535
- const pending = new Promise((resolve18) => {
34536
- const entry = { request, resolve: resolve18 };
35778
+ const pending = new Promise((resolve19) => {
35779
+ const entry = { request, resolve: resolve19 };
34537
35780
  if (this.opts.timeoutMs && this.opts.timeoutMs > 0) {
34538
35781
  entry.timer = setTimeout(() => {
34539
35782
  this.pending.delete(request.id);
34540
- resolve18({ type: "deny", reason: "Brain human decision timed out." });
35783
+ resolve19({ type: "deny", reason: "Brain human decision timed out." });
34541
35784
  }, this.opts.timeoutMs);
34542
35785
  }
34543
35786
  this.pending.set(request.id, entry);
@@ -34639,6 +35882,10 @@ var DefaultMailbox = class {
34639
35882
  _messageCache = null;
34640
35883
  _messageCacheMtime = -1;
34641
35884
  _messageCacheSize = -1;
35885
+ /** Primary index: recipient → Set of messages (points into _messageCache). */
35886
+ _byTo = /* @__PURE__ */ new Map();
35887
+ /** Secondary index: sender → Set of messages (points into _messageCache). */
35888
+ _byFrom = /* @__PURE__ */ new Map();
34642
35889
  constructor(sessionDir) {
34643
35890
  this.filePath = path3.join(sessionDir, MAILBOX_FILE2);
34644
35891
  }
@@ -34674,12 +35921,30 @@ var DefaultMailbox = class {
34674
35921
  }
34675
35922
  // ── Query ─────────────────────────────────────────────────────────────
34676
35923
  async query(q) {
34677
- const all = await this._readAllCached();
35924
+ const needFullScan = q.unreadBy !== void 0 || q.since !== void 0;
35925
+ let candidates;
35926
+ if (needFullScan) {
35927
+ candidates = await this._readAllCached();
35928
+ } else {
35929
+ await this._readAllCached();
35930
+ if (q.to !== void 0) {
35931
+ const direct = this._byTo.get(q.to);
35932
+ const broadcast = this._byTo.get("*");
35933
+ const combined = /* @__PURE__ */ new Map();
35934
+ if (direct) for (const m of direct) combined.set(m.id, m);
35935
+ if (broadcast) for (const m of broadcast) combined.set(m.id, m);
35936
+ candidates = Array.from(combined.values());
35937
+ } else if (q.from !== void 0) {
35938
+ candidates = Array.from(this._byFrom.get(q.from) ?? []);
35939
+ } else {
35940
+ candidates = await this._readAllCached();
35941
+ }
35942
+ }
34678
35943
  const limit = q.limit ?? 50;
34679
35944
  const order = q.minPriority !== void 0 ? { low: 0, normal: 1, high: 2 } : null;
34680
35945
  const minPriorityRank = order && q.minPriority !== void 0 ? order[q.minPriority] : 0;
34681
35946
  const filtered = [];
34682
- for (const msg of all) {
35947
+ for (const msg of candidates) {
34683
35948
  if (q.to !== void 0 && msg.to !== q.to && msg.to !== "*") continue;
34684
35949
  if (q.from !== void 0 && msg.from !== q.from) continue;
34685
35950
  if (q.unreadBy !== void 0 && q.unreadBy in msg.readBy) continue;
@@ -34780,6 +36045,8 @@ var DefaultMailbox = class {
34780
36045
  this._messageCache = null;
34781
36046
  this._messageCacheMtime = -1;
34782
36047
  this._messageCacheSize = -1;
36048
+ this._byTo.clear();
36049
+ this._byFrom.clear();
34783
36050
  }
34784
36051
  async clearAll() {
34785
36052
  await withFileLock(this.filePath, async () => {
@@ -34838,36 +36105,81 @@ var DefaultMailbox = class {
34838
36105
  async _readAll() {
34839
36106
  try {
34840
36107
  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;
36108
+ return this._parseLines(raw);
34860
36109
  } catch (err) {
34861
36110
  if (err.code === "ENOENT") return [];
34862
36111
  throw err;
34863
36112
  }
34864
36113
  }
36114
+ /**
36115
+ * Read only newly-appended bytes from the file and append them to the
36116
+ * in-memory cache, avoiding a full re-read when the file only grew.
36117
+ */
36118
+ async _readNewMessagesOnly(fd, oldSize, newSize) {
36119
+ const tailLen = newSize - oldSize;
36120
+ const buf = Buffer.alloc(tailLen);
36121
+ await fd.read(buf, 0, tailLen, oldSize);
36122
+ const tail = buf.toString("utf8");
36123
+ for (const line of tail.split(LINE_SEPARATOR2)) {
36124
+ if (!line.trim()) continue;
36125
+ try {
36126
+ const parsed = JSON.parse(line);
36127
+ if (!parsed["readBy"]) {
36128
+ const readBy = {};
36129
+ if (parsed["read"] && parsed["readAt"]) {
36130
+ readBy[parsed["to"] ?? "unknown"] = parsed["readAt"];
36131
+ }
36132
+ parsed["readBy"] = readBy;
36133
+ delete parsed["read"];
36134
+ delete parsed["readAt"];
36135
+ }
36136
+ const msg = parsed;
36137
+ this._messageCache.push(msg);
36138
+ this._indexMsg(msg);
36139
+ } catch {
36140
+ }
36141
+ }
36142
+ return this._messageCache;
36143
+ }
36144
+ /** Parse a JSONL string into MailboxMessage[], including migration. */
36145
+ _parseLines(raw) {
36146
+ const lines = raw.split(LINE_SEPARATOR2).filter((l) => l.trim().length > 0);
36147
+ const messages = [];
36148
+ for (const line of lines) {
36149
+ try {
36150
+ const parsed = JSON.parse(line);
36151
+ if (!parsed["readBy"]) {
36152
+ const readBy = {};
36153
+ if (parsed["read"] && parsed["readAt"]) {
36154
+ readBy[parsed["to"] ?? "unknown"] = parsed["readAt"];
36155
+ }
36156
+ parsed["readBy"] = readBy;
36157
+ delete parsed["read"];
36158
+ delete parsed["readAt"];
36159
+ }
36160
+ messages.push(parsed);
36161
+ } catch {
36162
+ }
36163
+ }
36164
+ return messages;
36165
+ }
34865
36166
  async _readAllCached() {
34866
36167
  try {
34867
36168
  const st = await fsp3.stat(this.filePath);
34868
36169
  if (this._messageCache !== null && this._messageCacheMtime === st.mtimeMs && this._messageCacheSize === st.size) {
34869
36170
  return this._messageCache;
34870
36171
  }
36172
+ if (this._messageCache !== null && this._messageCacheSize >= 0 && st.size > this._messageCacheSize) {
36173
+ const fd = await fsp3.open(this.filePath, "r");
36174
+ try {
36175
+ const updated = await this._readNewMessagesOnly(fd, this._messageCacheSize, st.size);
36176
+ this._messageCacheMtime = st.mtimeMs;
36177
+ this._messageCacheSize = st.size;
36178
+ return updated;
36179
+ } finally {
36180
+ await fd.close();
36181
+ }
36182
+ }
34871
36183
  const all = await this._readAll();
34872
36184
  this._setMessageCache(all, st.mtimeMs, st.size);
34873
36185
  return all;
@@ -34884,9 +36196,12 @@ var DefaultMailbox = class {
34884
36196
  this._messageCache = null;
34885
36197
  this._messageCacheMtime = -1;
34886
36198
  this._messageCacheSize = -1;
36199
+ this._byTo.clear();
36200
+ this._byFrom.clear();
34887
36201
  return;
34888
36202
  }
34889
36203
  this._messageCache = messages;
36204
+ this._buildIndexes(messages);
34890
36205
  if (mtime !== void 0 && size !== void 0) {
34891
36206
  this._messageCacheMtime = mtime;
34892
36207
  this._messageCacheSize = size;
@@ -34904,9 +36219,35 @@ var DefaultMailbox = class {
34904
36219
  this._messageCache = null;
34905
36220
  this._messageCacheMtime = -1;
34906
36221
  this._messageCacheSize = -1;
36222
+ this._byTo.clear();
36223
+ this._byFrom.clear();
34907
36224
  return;
34908
36225
  }
34909
36226
  this._messageCache.push(msg);
36227
+ this._indexMsg(msg);
36228
+ }
36229
+ /** Rebuild both indexes from a full message list. */
36230
+ _buildIndexes(messages) {
36231
+ this._byTo.clear();
36232
+ this._byFrom.clear();
36233
+ for (const msg of messages) {
36234
+ this._indexMsg(msg);
36235
+ }
36236
+ }
36237
+ /** Add a single message to both indexes. */
36238
+ _indexMsg(msg) {
36239
+ const toSet = this._byTo.get(msg.to);
36240
+ if (toSet) {
36241
+ toSet.add(msg);
36242
+ } else {
36243
+ this._byTo.set(msg.to, /* @__PURE__ */ new Set([msg]));
36244
+ }
36245
+ const fromSet = this._byFrom.get(msg.from);
36246
+ if (fromSet) {
36247
+ fromSet.add(msg);
36248
+ } else {
36249
+ this._byFrom.set(msg.from, /* @__PURE__ */ new Set([msg]));
36250
+ }
34910
36251
  }
34911
36252
  };
34912
36253
  var BrainMonitor = class {
@@ -38053,17 +39394,17 @@ ${input.detail}`
38053
39394
  _waitForDagProgress(timeoutMs) {
38054
39395
  const before = this._dagProgressKey();
38055
39396
  if (this.dag.isDone()) return Promise.resolve();
38056
- return new Promise((resolve18) => {
39397
+ return new Promise((resolve19) => {
38057
39398
  let off;
38058
39399
  const timer = setTimeout(() => {
38059
39400
  off?.();
38060
- resolve18();
39401
+ resolve19();
38061
39402
  }, timeoutMs);
38062
39403
  off = this.dag.onEvent(() => {
38063
39404
  if (this._dagProgressKey() === before) return;
38064
39405
  clearTimeout(timer);
38065
39406
  off?.();
38066
- resolve18();
39407
+ resolve19();
38067
39408
  });
38068
39409
  });
38069
39410
  }
@@ -38337,6 +39678,442 @@ ${input.detail}`
38337
39678
  this.onCoordinatorEvent?.(event);
38338
39679
  }
38339
39680
  };
39681
+ var AgentMonitorService = class {
39682
+ _fleetBus;
39683
+ _events;
39684
+ _transcriptsDir;
39685
+ _maxEntries;
39686
+ _streamEnabled;
39687
+ _onEntry;
39688
+ /** Per-subagent virtual sessions. */
39689
+ _sessions = /* @__PURE__ */ new Map();
39690
+ /** Disposers for FleetBus subscriptions, keyed by subagentId. */
39691
+ _subscriptions = /* @__PURE__ */ new Map();
39692
+ /** Generic fleet-wide subscription disposer. */
39693
+ _fleetDisposer;
39694
+ /** Track whether service is running. */
39695
+ _started = false;
39696
+ constructor(opts) {
39697
+ this._fleetBus = opts.fleetBus;
39698
+ this._events = opts.events;
39699
+ this._transcriptsDir = opts.transcriptsDir;
39700
+ this._maxEntries = opts.maxEntriesPerAgent ?? 500;
39701
+ this._streamEnabled = opts.streamEnabled ?? false;
39702
+ this._onEntry = opts.onEntry;
39703
+ }
39704
+ // ── Public API ────────────────────────────────────────────────────
39705
+ /** Set the FleetBus to listen on. Must be called before `start()`. */
39706
+ setFleetBus(bus) {
39707
+ this._fleetBus = bus;
39708
+ }
39709
+ get streamEnabled() {
39710
+ return this._streamEnabled;
39711
+ }
39712
+ /** Enable/disable streaming agent conversations to the main chat timeline. */
39713
+ setStreamEnabled(enabled) {
39714
+ this._streamEnabled = enabled;
39715
+ }
39716
+ /** Get a snapshot of all known agent sessions. */
39717
+ getAllSessions() {
39718
+ return Array.from(this._sessions.values());
39719
+ }
39720
+ /** Get a specific agent's virtual session, or undefined. */
39721
+ getSession(subagentId) {
39722
+ return this._sessions.get(subagentId);
39723
+ }
39724
+ /** Get transcript entries for a specific agent, newest first. */
39725
+ getTranscript(subagentId, limit = 50) {
39726
+ const session = this._sessions.get(subagentId);
39727
+ if (!session) return [];
39728
+ return session.transcript.slice(-limit).reverse();
39729
+ }
39730
+ /** Set a callback for each new timeline entry (HQ bridge). */
39731
+ setOnEntry(handler) {
39732
+ this._onEntry = handler;
39733
+ }
39734
+ // ── Lifecycle ──────────────────────────────────────────────────────
39735
+ /** Start listening to FleetBus events. */
39736
+ start() {
39737
+ if (this._started) return;
39738
+ if (!this._fleetBus) {
39739
+ this._started = true;
39740
+ return;
39741
+ }
39742
+ this._started = true;
39743
+ this._fleetDisposer = this._fleetBus.onAny((event) => {
39744
+ this._routeEvent(event.subagentId, event.type, event.payload);
39745
+ });
39746
+ }
39747
+ /** Stop listening and clean up all subscriptions. */
39748
+ stop() {
39749
+ if (!this._started) return;
39750
+ this._started = false;
39751
+ if (this._fleetDisposer) {
39752
+ this._fleetDisposer();
39753
+ this._fleetDisposer = void 0;
39754
+ }
39755
+ for (const disposer of this._subscriptions.values()) {
39756
+ disposer();
39757
+ }
39758
+ this._subscriptions.clear();
39759
+ }
39760
+ /** Ensure a subagent is being tracked. Called when a subagent spawns. */
39761
+ trackSubagent(subagentId, agentName, task) {
39762
+ if (this._sessions.has(subagentId)) return;
39763
+ const now = (/* @__PURE__ */ new Date()).toISOString();
39764
+ const session = {
39765
+ subagentId,
39766
+ agentName,
39767
+ createdAt: now,
39768
+ status: "spawned",
39769
+ task,
39770
+ transcript: []
39771
+ };
39772
+ this._sessions.set(subagentId, session);
39773
+ this._addEntry(subagentId, {
39774
+ id: this._uid(),
39775
+ subagentId,
39776
+ agentName,
39777
+ ts: now,
39778
+ kind: "system",
39779
+ content: task ? `\u{1F3AF} Spawned: ${task}` : "\u{1F916} Agent spawned",
39780
+ iteration: 0
39781
+ });
39782
+ this._events.emit("agent.status_changed", {
39783
+ subagentId,
39784
+ agentName,
39785
+ status: "spawned",
39786
+ ts: now,
39787
+ summary: task,
39788
+ task
39789
+ });
39790
+ }
39791
+ /** Mark a subagent as completed/failed/etc. Called on subagent finish. */
39792
+ completeSubagent(subagentId, status, summary) {
39793
+ const session = this._sessions.get(subagentId);
39794
+ if (!session) return;
39795
+ const now = (/* @__PURE__ */ new Date()).toISOString();
39796
+ session.status = status;
39797
+ this._addEntry(subagentId, {
39798
+ id: this._uid(),
39799
+ subagentId,
39800
+ agentName: session.agentName,
39801
+ ts: now,
39802
+ kind: "status",
39803
+ content: summary ?? `Agent ${status}`,
39804
+ iteration: 999
39805
+ });
39806
+ this._events.emit("agent.status_changed", {
39807
+ subagentId,
39808
+ agentName: session.agentName,
39809
+ status,
39810
+ ts: now,
39811
+ summary,
39812
+ task: session.task
39813
+ });
39814
+ }
39815
+ // ── Internal ───────────────────────────────────────────────────────
39816
+ _routeEvent(subagentId, type, payload) {
39817
+ const session = this._sessions.get(subagentId);
39818
+ if (!session) return;
39819
+ switch (type) {
39820
+ case "provider.text_delta": {
39821
+ const text = payload.text;
39822
+ if (!text || text.length === 0) return;
39823
+ const iteration = payload.iteration ?? 0;
39824
+ this._addEntry(subagentId, {
39825
+ id: this._uid(),
39826
+ subagentId,
39827
+ agentName: session.agentName,
39828
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
39829
+ kind: "text",
39830
+ content: text,
39831
+ iteration
39832
+ });
39833
+ break;
39834
+ }
39835
+ case "provider.thinking_delta": {
39836
+ const text = payload.text;
39837
+ if (!text || text.length === 0) return;
39838
+ const iteration = payload.iteration ?? 0;
39839
+ this._addEntry(subagentId, {
39840
+ id: this._uid(),
39841
+ subagentId,
39842
+ agentName: session.agentName,
39843
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
39844
+ kind: "text",
39845
+ content: `\u{1F9E0} ${text}`,
39846
+ iteration
39847
+ });
39848
+ break;
39849
+ }
39850
+ case "tool.started": {
39851
+ const name = payload.name;
39852
+ if (!name) return;
39853
+ this._addEntry(subagentId, {
39854
+ id: this._uid(),
39855
+ subagentId,
39856
+ agentName: session.agentName,
39857
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
39858
+ kind: "tool_use",
39859
+ content: `\u{1F527} ${name}()`,
39860
+ iteration: payload.iteration ?? 0,
39861
+ toolName: name
39862
+ });
39863
+ break;
39864
+ }
39865
+ case "tool.executed": {
39866
+ const name = payload.name;
39867
+ const ok = payload.ok;
39868
+ const durationMs = payload.durationMs;
39869
+ if (!name) return;
39870
+ const statusIcon2 = ok ? "\u2705" : "\u274C";
39871
+ const duration = durationMs !== void 0 ? ` (${durationMs}ms)` : "";
39872
+ this._addEntry(subagentId, {
39873
+ id: this._uid(),
39874
+ subagentId,
39875
+ agentName: session.agentName,
39876
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
39877
+ kind: "tool_result",
39878
+ content: `${statusIcon2} ${name}${duration}`,
39879
+ iteration: payload.iteration ?? 0,
39880
+ toolName: name,
39881
+ toolOk: ok
39882
+ });
39883
+ break;
39884
+ }
39885
+ case "iteration.completed": {
39886
+ const index = payload.index ?? 0;
39887
+ if (index > 0 && index % 5 === 0) {
39888
+ this._addEntry(subagentId, {
39889
+ id: this._uid(),
39890
+ subagentId,
39891
+ agentName: session.agentName,
39892
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
39893
+ kind: "status",
39894
+ content: `\u{1F504} Iteration ${index}`,
39895
+ iteration: index
39896
+ });
39897
+ }
39898
+ break;
39899
+ }
39900
+ }
39901
+ }
39902
+ _addEntry(subagentId, entry) {
39903
+ const session = this._sessions.get(subagentId);
39904
+ if (!session) return;
39905
+ session.transcript.push(entry);
39906
+ if (session.transcript.length > this._maxEntries) {
39907
+ session.transcript.splice(0, session.transcript.length - this._maxEntries);
39908
+ }
39909
+ this._appendToFile(subagentId, entry).catch(() => {
39910
+ });
39911
+ this._events.emit("agent.timeline.message", {
39912
+ subagentId: entry.subagentId,
39913
+ agentName: entry.agentName,
39914
+ content: entry.content,
39915
+ kind: entry.kind === "tool_result" ? "tool_use" : entry.kind === "system" ? "status" : entry.kind,
39916
+ iteration: entry.iteration,
39917
+ ts: entry.ts,
39918
+ toolName: entry.toolName,
39919
+ costUsd: entry.costUsd
39920
+ });
39921
+ this._onEntry?.(entry);
39922
+ }
39923
+ async _appendToFile(subagentId, entry) {
39924
+ const dir = path3.join(this._transcriptsDir, subagentId);
39925
+ await fsp3.mkdir(dir, { recursive: true });
39926
+ const filePath = path3.join(dir, "transcript.jsonl");
39927
+ const line = JSON.stringify(entry) + "\n";
39928
+ await fsp3.appendFile(filePath, line, { encoding: "utf8" });
39929
+ }
39930
+ _uid() {
39931
+ return `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
39932
+ }
39933
+ };
39934
+ function createAgentMonitorService(opts) {
39935
+ return new AgentMonitorService(opts);
39936
+ }
39937
+
39938
+ // src/coordination/adaptive-concurrency.ts
39939
+ var DEFAULT_CONFIG = Object.freeze({
39940
+ enabled: false,
39941
+ minConcurrent: 1,
39942
+ maxConcurrent: 16,
39943
+ decreaseFactor: 0.5,
39944
+ successThreshold: 10,
39945
+ recoveryIntervalMs: 3e4
39946
+ });
39947
+ var AdaptiveConcurrencyController = class {
39948
+ config;
39949
+ state;
39950
+ disposers = [];
39951
+ stateChangeHandlers = [];
39952
+ constructor(fleetBus, setMaxConcurrent, config = {}, onStateChange) {
39953
+ this.config = {
39954
+ enabled: config.enabled ?? DEFAULT_CONFIG.enabled,
39955
+ minConcurrent: config.minConcurrent ?? DEFAULT_CONFIG.minConcurrent,
39956
+ maxConcurrent: config.maxConcurrent ?? DEFAULT_CONFIG.maxConcurrent,
39957
+ decreaseFactor: config.decreaseFactor ?? DEFAULT_CONFIG.decreaseFactor,
39958
+ successThreshold: config.successThreshold ?? DEFAULT_CONFIG.successThreshold,
39959
+ recoveryIntervalMs: config.recoveryIntervalMs ?? DEFAULT_CONFIG.recoveryIntervalMs
39960
+ };
39961
+ this.state = {
39962
+ current: this.config.maxConcurrent,
39963
+ min: this.config.minConcurrent,
39964
+ max: this.config.maxConcurrent,
39965
+ consecutiveSuccesses: 0,
39966
+ consecutiveFailures: 0,
39967
+ totalDecreases: 0,
39968
+ totalIncreases: 0,
39969
+ enabled: this.config.enabled
39970
+ };
39971
+ if (onStateChange) {
39972
+ this.stateChangeHandlers.push(onStateChange);
39973
+ }
39974
+ if (this.config.enabled) {
39975
+ setMaxConcurrent(this.state.current);
39976
+ }
39977
+ this.setupEventHandlers(fleetBus, setMaxConcurrent);
39978
+ }
39979
+ setupEventHandlers(fleetBus, setMaxConcurrent) {
39980
+ if (!this.config.enabled) return;
39981
+ const off = fleetBus.onAny((event) => {
39982
+ if (!this.config.enabled) return;
39983
+ if (event.type === "error" || event.type === "provider_error") {
39984
+ const payload = event.payload;
39985
+ if (payload?.status === 429 || payload?.code === "rate_limit_error" || payload?.kind === "rate_limit") {
39986
+ this.handleRateLimit(setMaxConcurrent);
39987
+ }
39988
+ }
39989
+ });
39990
+ this.disposers.push(off);
39991
+ }
39992
+ /**
39993
+ * Handle a rate limit (429) error - decrease concurrency
39994
+ */
39995
+ handleRateLimit(setMaxConcurrent) {
39996
+ if (this.state.current <= this.config.minConcurrent) {
39997
+ this.state.consecutiveFailures++;
39998
+ this.state.consecutiveSuccesses = 0;
39999
+ this.notifyStateChange();
40000
+ return;
40001
+ }
40002
+ const newConcurrent = Math.max(
40003
+ this.config.minConcurrent,
40004
+ Math.floor(this.state.current * this.config.decreaseFactor)
40005
+ );
40006
+ if (newConcurrent < this.state.current) {
40007
+ const previousConcurrent = this.state.current;
40008
+ this.state.current = newConcurrent;
40009
+ this.state.consecutiveFailures++;
40010
+ this.state.consecutiveSuccesses = 0;
40011
+ this.state.totalDecreases++;
40012
+ setMaxConcurrent(this.state.current);
40013
+ this.notifyStateChange();
40014
+ console.log(
40015
+ JSON.stringify({
40016
+ level: "warn",
40017
+ event: "adaptive_concurrency.decreased",
40018
+ reason: "rate_limit",
40019
+ previousConcurrent,
40020
+ newConcurrent: this.state.current,
40021
+ decreaseFactor: this.config.decreaseFactor,
40022
+ totalDecreases: this.state.totalDecreases
40023
+ })
40024
+ );
40025
+ }
40026
+ }
40027
+ /**
40028
+ * Force a decrease (e.g., manual trigger or other error types)
40029
+ */
40030
+ decrease(target) {
40031
+ if (!this.config.enabled) return;
40032
+ const newConcurrent = target ?? Math.max(
40033
+ this.config.minConcurrent,
40034
+ Math.floor(this.state.current * this.config.decreaseFactor)
40035
+ );
40036
+ if (newConcurrent < this.state.current) {
40037
+ const previousConcurrent = this.state.current;
40038
+ this.state.current = newConcurrent;
40039
+ this.state.consecutiveSuccesses = 0;
40040
+ this.state.totalDecreases++;
40041
+ this.notifyStateChange();
40042
+ console.log(
40043
+ JSON.stringify({
40044
+ level: "warn",
40045
+ event: "adaptive_concurrency.decreased",
40046
+ reason: "manual",
40047
+ previousConcurrent,
40048
+ newConcurrent: this.state.current,
40049
+ totalDecreases: this.state.totalDecreases
40050
+ })
40051
+ );
40052
+ }
40053
+ }
40054
+ /**
40055
+ * Get the current state
40056
+ */
40057
+ getState() {
40058
+ return { ...this.state };
40059
+ }
40060
+ /**
40061
+ * Update configuration at runtime
40062
+ */
40063
+ updateConfig(config) {
40064
+ if (config.enabled !== void 0) {
40065
+ this.config.enabled = config.enabled;
40066
+ }
40067
+ if (config.minConcurrent !== void 0) {
40068
+ this.config.minConcurrent = config.minConcurrent;
40069
+ }
40070
+ if (config.maxConcurrent !== void 0) {
40071
+ this.config.maxConcurrent = config.maxConcurrent;
40072
+ }
40073
+ if (config.decreaseFactor !== void 0) {
40074
+ this.config.decreaseFactor = config.decreaseFactor;
40075
+ }
40076
+ if (config.successThreshold !== void 0) {
40077
+ this.config.successThreshold = config.successThreshold;
40078
+ }
40079
+ if (config.recoveryIntervalMs !== void 0) {
40080
+ this.config.recoveryIntervalMs = config.recoveryIntervalMs;
40081
+ }
40082
+ this.state.current = Math.max(this.config.minConcurrent, Math.min(this.state.current, this.config.maxConcurrent));
40083
+ this.state.enabled = this.config.enabled;
40084
+ this.state.min = this.config.minConcurrent;
40085
+ this.state.max = this.config.maxConcurrent;
40086
+ this.notifyStateChange();
40087
+ }
40088
+ /**
40089
+ * Dispose of the controller and clean up event listeners
40090
+ */
40091
+ dispose() {
40092
+ for (const dispose of this.disposers) {
40093
+ dispose();
40094
+ }
40095
+ this.disposers.length = 0;
40096
+ this.stateChangeHandlers = [];
40097
+ }
40098
+ notifyStateChange() {
40099
+ const state = this.getState();
40100
+ for (const handler of this.stateChangeHandlers) {
40101
+ handler(state);
40102
+ }
40103
+ }
40104
+ /**
40105
+ * Register a state change handler
40106
+ */
40107
+ onStateChange(handler) {
40108
+ this.stateChangeHandlers.push(handler);
40109
+ return () => {
40110
+ const index = this.stateChangeHandlers.indexOf(handler);
40111
+ if (index !== -1) {
40112
+ this.stateChangeHandlers.splice(index, 1);
40113
+ }
40114
+ };
40115
+ }
40116
+ };
38340
40117
 
38341
40118
  // src/tools/mcp-control.ts
38342
40119
  function createMcpControlTool(opts) {
@@ -38364,11 +40141,18 @@ function createMcpControlTool(opts) {
38364
40141
  };
38365
40142
  return {
38366
40143
  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.",
40144
+ 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
40145
  category: "mcp",
38369
40146
  permission: "auto",
38370
40147
  mutating: true,
38371
- riskTier: "standard",
40148
+ // `enable`/`restart` spawn a server process that, for the stdio presets,
40149
+ // fetches and runs an npm package (`npx -y <pkg>`) — effectively remote
40150
+ // code execution. Marking the tool destructive means the YOLO
40151
+ // `confirmDestructive` safety net still prompts before it runs (plain
40152
+ // non-YOLO already confirms via the CONFIG_MUTATE dangerous-capability
40153
+ // net in the executor). Read-only actions (list/search) ride the same
40154
+ // tool but are cheap to confirm/trust once.
40155
+ riskTier: "destructive",
38372
40156
  capabilities: [ToolCapabilities.CONFIG_MUTATE],
38373
40157
  inputSchema,
38374
40158
  async execute(raw) {
@@ -38890,13 +40674,13 @@ function createAgentToolHandler(a) {
38890
40674
  }
38891
40675
  }
38892
40676
  function waitForConfirm(info) {
38893
- return new Promise((resolve18) => {
40677
+ return new Promise((resolve19) => {
38894
40678
  a.events.emit("tool.confirm_needed", {
38895
40679
  tool: info.tool,
38896
40680
  input: info.input,
38897
40681
  toolUseId: info.toolUseId,
38898
40682
  suggestedPattern: info.suggestedPattern,
38899
- resolve: resolve18
40683
+ resolve: resolve19
38900
40684
  });
38901
40685
  });
38902
40686
  }
@@ -39433,12 +41217,12 @@ function attachMailboxCheckerInner(a, source) {
39433
41217
  // src/core/iteration-limit.ts
39434
41218
  function requestLimitExtension(opts) {
39435
41219
  const { events, currentIterations, currentLimit, autoExtend, timeoutMs = 3e4 } = opts;
39436
- return new Promise((resolve18) => {
41220
+ return new Promise((resolve19) => {
39437
41221
  let resolved = false;
39438
41222
  const timerFired = () => {
39439
41223
  if (!resolved) {
39440
41224
  resolved = true;
39441
- resolve18(0);
41225
+ resolve19(0);
39442
41226
  }
39443
41227
  };
39444
41228
  const timer = setTimeout(timerFired, timeoutMs);
@@ -39447,14 +41231,14 @@ function requestLimitExtension(opts) {
39447
41231
  if (!resolved) {
39448
41232
  resolved = true;
39449
41233
  clearTimeout(timer);
39450
- resolve18(0);
41234
+ resolve19(0);
39451
41235
  }
39452
41236
  };
39453
41237
  const grant = (extra) => {
39454
41238
  if (!resolved) {
39455
41239
  resolved = true;
39456
41240
  clearTimeout(timer);
39457
- resolve18(Math.max(0, extra));
41241
+ resolve19(Math.max(0, extra));
39458
41242
  }
39459
41243
  };
39460
41244
  events.emit("iteration.limit_reached", {
@@ -39468,7 +41252,7 @@ function requestLimitExtension(opts) {
39468
41252
  if (!resolved) {
39469
41253
  resolved = true;
39470
41254
  clearTimeout(timer);
39471
- resolve18(100);
41255
+ resolve19(100);
39472
41256
  }
39473
41257
  });
39474
41258
  }
@@ -39489,25 +41273,29 @@ function createAgentLoopHandler(a, handlers) {
39489
41273
  const checkMailbox = attachMailboxChecker(a);
39490
41274
  async function compactContextIfNeeded() {
39491
41275
  const msgCount = a.ctx.messages.length;
39492
- if (_lastCompactionMsgCount === msgCount && _lastCompactionWasNoop && _maxContext > 0) {
41276
+ const maxContext = currentMaxContext();
41277
+ if (_lastCompactionMsgCount === msgCount && _lastCompactionWasNoop && _lastCompactionMaxContext === maxContext && maxContext > 0) {
39493
41278
  return;
39494
41279
  }
39495
41280
  await a.pipelines.contextWindow.run(a.ctx);
39496
41281
  _lastCompactionMsgCount = msgCount;
41282
+ _lastCompactionMaxContext = maxContext;
39497
41283
  const stashed = a.ctx.lastRequestTokens;
39498
41284
  const tokens = typeof stashed === "number" && stashed > 0 ? stashed : 0;
39499
- const load = _maxContext > 0 ? tokens / _maxContext : 0;
41285
+ const load = maxContext > 0 ? tokens / maxContext : 0;
39500
41286
  _lastCompactionWasNoop = tokens > 0 && load < 0.5;
39501
41287
  }
39502
41288
  const calibrationKey = (model = a.ctx.model) => `${a.ctx.provider?.id ?? "unknown"}/${model}`;
39503
41289
  function emitContextPct() {
39504
41290
  const msgCount = a.ctx.messages.length;
39505
41291
  const toolCount = (a.ctx.tools ?? []).length;
39506
- if (msgCount === _lastEmittedMsgCount && toolCount === _lastEmittedToolCount && _maxContext > 0) {
41292
+ const maxContext = currentMaxContext();
41293
+ if (msgCount === _lastEmittedMsgCount && toolCount === _lastEmittedToolCount && maxContext === _lastEmittedMaxContext && maxContext > 0) {
39507
41294
  return;
39508
41295
  }
39509
41296
  _lastEmittedMsgCount = msgCount;
39510
41297
  _lastEmittedToolCount = toolCount;
41298
+ _lastEmittedMaxContext = maxContext;
39511
41299
  if (msgCount !== _lastPreFlightMsgCount) {
39512
41300
  a.ctx.lastRequestTokens = estimateRequestTokens(
39513
41301
  a.ctx.messages,
@@ -39517,11 +41305,6 @@ function createAgentLoopHandler(a, handlers) {
39517
41305
  _lastPreFlightMsgCount = msgCount;
39518
41306
  a.ctx.meta["lastRequestTokensAt"] = { msgCount, toolCount };
39519
41307
  }
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
41308
  let total;
39526
41309
  const stashed = a.ctx.lastRequestTokens;
39527
41310
  if (typeof stashed === "number" && stashed > 0) {
@@ -39536,13 +41319,19 @@ function createAgentLoopHandler(a, handlers) {
39536
41319
  );
39537
41320
  total = est.total;
39538
41321
  }
39539
- a.events.emit("ctx.pct", { load: total / _maxContext, tokens: total, maxContext: _maxContext });
41322
+ a.events.emit("ctx.pct", { load: total / maxContext, tokens: total, maxContext });
41323
+ }
41324
+ function currentMaxContext() {
41325
+ const metaLimit = a.ctx.meta?.["effectiveMaxContext"];
41326
+ const providerMax = a.ctx.provider.capabilities.maxContext;
41327
+ return typeof metaLimit === "number" && metaLimit > 0 ? metaLimit : typeof providerMax === "number" && providerMax > 0 ? providerMax : 2e5;
39540
41328
  }
39541
- let _maxContext = 0;
39542
41329
  let _lastEmittedMsgCount = -1;
39543
41330
  let _lastEmittedToolCount = -1;
41331
+ let _lastEmittedMaxContext = -1;
39544
41332
  let _lastPreFlightMsgCount = -1;
39545
41333
  let _lastCompactionMsgCount = -1;
41334
+ let _lastCompactionMaxContext = -1;
39546
41335
  let _lastCompactionWasNoop = false;
39547
41336
  function foldBlockIntoConversation(block) {
39548
41337
  const messages = a.ctx.messages;
@@ -39612,7 +41401,8 @@ function createAgentLoopHandler(a, handlers) {
39612
41401
  ts: (/* @__PURE__ */ new Date()).toISOString(),
39613
41402
  content: inputPayload.content
39614
41403
  });
39615
- await a.ctx.session.flush();
41404
+ void a.ctx.session.flush().catch(() => {
41405
+ });
39616
41406
  const promptIndex = a.ctx.messages.filter((m) => m.role === "user").length - 1;
39617
41407
  const preview = inputPayload.text.slice(0, 80) + (inputPayload.text.length > 80 ? "\u2026" : "");
39618
41408
  await a.ctx.session.writeCheckpoint(promptIndex, preview);
@@ -39993,12 +41783,26 @@ var Agent = class {
39993
41783
  const { blocks, text } = normalizeInput(userInput);
39994
41784
  const inputPayload = { content: blocks, text, ctx: this.ctx };
39995
41785
  await this.extensions.runBeforeRun(this.ctx, inputPayload);
41786
+ const runStartedAt = Date.now();
41787
+ const runStartedIso = new Date(runStartedAt).toISOString();
39996
41788
  try {
41789
+ this.events.emit("agent.run.started", {
41790
+ ctx: this.ctx,
41791
+ model: opts.model ?? this.ctx.model,
41792
+ at: runStartedIso
41793
+ });
39997
41794
  const autonomousContinue = opts.autonomousContinue ?? this.autonomousContinue;
39998
41795
  const result = await this._loopHandler.runInner(inputPayload, opts, controller, autonomousContinue);
39999
41796
  span?.setAttribute("agent.status", result.status);
40000
41797
  span?.setAttribute("agent.iterations", result.iterations);
40001
41798
  await this.extensions.runAfterRun(this.ctx, result);
41799
+ this.events.emit("agent.run.completed", {
41800
+ ctx: this.ctx,
41801
+ status: result.status,
41802
+ iterations: result.iterations,
41803
+ at: (/* @__PURE__ */ new Date()).toISOString(),
41804
+ durationMs: Date.now() - runStartedAt
41805
+ });
40002
41806
  return result;
40003
41807
  } catch (err) {
40004
41808
  const wse = err instanceof AgentError ? err : toWrongStackError(err);
@@ -40013,6 +41817,21 @@ var Agent = class {
40013
41817
  abortReason: signal.aborted ? signalAbortReason(signal) : void 0
40014
41818
  };
40015
41819
  await this.extensions.runAfterRun(this.ctx, result);
41820
+ if (result.status === "failed") {
41821
+ this.events.emit("agent.run.error", {
41822
+ ctx: this.ctx,
41823
+ err: safeError,
41824
+ at: (/* @__PURE__ */ new Date()).toISOString(),
41825
+ durationMs: Date.now() - runStartedAt
41826
+ });
41827
+ }
41828
+ this.events.emit("agent.run.completed", {
41829
+ ctx: this.ctx,
41830
+ status: result.status,
41831
+ iterations: result.iterations,
41832
+ at: (/* @__PURE__ */ new Date()).toISOString(),
41833
+ durationMs: Date.now() - runStartedAt
41834
+ });
40016
41835
  return result;
40017
41836
  } finally {
40018
41837
  span?.end();
@@ -40170,13 +41989,13 @@ async function runShellHook(spec, input, logger) {
40170
41989
  logger?.warn?.(`hook rejected: command not in allowlist: ${spec.command}`);
40171
41990
  return null;
40172
41991
  }
40173
- return await new Promise((resolve18) => {
41992
+ return await new Promise((resolve19) => {
40174
41993
  let settled = false;
40175
41994
  const done = (v) => {
40176
41995
  if (settled) return;
40177
41996
  settled = true;
40178
41997
  clearTimeout(timer);
40179
- resolve18(v);
41998
+ resolve19(v);
40180
41999
  };
40181
42000
  let child;
40182
42001
  try {
@@ -40189,7 +42008,7 @@ async function runShellHook(spec, input, logger) {
40189
42008
  });
40190
42009
  } catch (err2) {
40191
42010
  logger?.warn?.(`hook spawn failed: ${toErrorMessage(err2)}`);
40192
- return resolve18(null);
42011
+ return resolve19(null);
40193
42012
  }
40194
42013
  const timer = setTimeout(() => {
40195
42014
  logger?.warn?.(`hook command timed out after ${timeoutMs}ms: ${spec.command}`);
@@ -40365,14 +42184,20 @@ var HookRunner = class {
40365
42184
  };
40366
42185
 
40367
42186
  // src/execution/model-runtime.ts
40368
- function resolveModelRuntime(settings, reasoning) {
42187
+ function resolveModelRuntime(settings, reasoning, capabilities) {
40369
42188
  const warnings = [];
40370
42189
  if (!settings) {
40371
- return { reasoning: void 0, cache: void 0, warnings };
42190
+ return { reasoning: void 0, cache: void 0, parameters: void 0, warnings };
40372
42191
  }
40373
42192
  const reasoningField = resolveReasoningForRequest(settings, reasoning, warnings);
40374
42193
  const cacheField = resolveCacheForRequest(settings);
40375
- return { reasoning: reasoningField, cache: cacheField, warnings };
42194
+ const paramsField = resolveParametersForRequest(settings.parameters, capabilities);
42195
+ return {
42196
+ reasoning: reasoningField,
42197
+ cache: cacheField,
42198
+ parameters: paramsField,
42199
+ warnings
42200
+ };
40376
42201
  }
40377
42202
  function resolveReasoningForRequest(settings, rc, warnings) {
40378
42203
  const cfg = settings.reasoning;
@@ -40430,11 +42255,38 @@ function resolveCacheForRequest(settings, _warnings) {
40430
42255
  const out = { ttl };
40431
42256
  return out;
40432
42257
  }
42258
+ function resolveParametersForRequest(params, caps, _warnings) {
42259
+ if (!params) return void 0;
42260
+ const out = {};
42261
+ if (params.topK !== void 0 && caps?.topK !== false) {
42262
+ out.topK = params.topK;
42263
+ }
42264
+ if (params.frequencyPenalty !== void 0 && caps?.frequencyPenalty !== false) {
42265
+ out.frequencyPenalty = params.frequencyPenalty;
42266
+ }
42267
+ if (params.presencePenalty !== void 0 && caps?.presencePenalty !== false) {
42268
+ out.presencePenalty = params.presencePenalty;
42269
+ }
42270
+ if (params.seed !== void 0 && caps?.seed !== false) {
42271
+ out.seed = params.seed;
42272
+ }
42273
+ if (params.user !== void 0) {
42274
+ out.user = params.user;
42275
+ }
42276
+ if (params.logprobs !== void 0 && caps?.logprobs !== false) {
42277
+ out.logprobs = params.logprobs;
42278
+ if (params.topLogprobs !== void 0) {
42279
+ out.topLogprobs = params.topLogprobs;
42280
+ }
42281
+ }
42282
+ return Object.keys(out).length > 0 ? out : void 0;
42283
+ }
40433
42284
  function applyModelRuntime(req, opts) {
40434
42285
  const settings = opts.getSettings();
40435
42286
  if (!settings) return req;
40436
42287
  const rc = opts.getReasoningConfig();
40437
- const resolved = resolveModelRuntime(settings, rc);
42288
+ const caps = opts.getCapabilities?.();
42289
+ const resolved = resolveModelRuntime(settings, rc, caps);
40438
42290
  for (const w of resolved.warnings) opts.onWarning?.(w);
40439
42291
  const next = { ...req };
40440
42292
  if (resolved.reasoning !== void 0) {
@@ -40443,6 +42295,9 @@ function applyModelRuntime(req, opts) {
40443
42295
  if (resolved.cache !== void 0) {
40444
42296
  next.cache = resolved.cache;
40445
42297
  }
42298
+ if (resolved.parameters !== void 0) {
42299
+ Object.assign(next, resolved.parameters);
42300
+ }
40446
42301
  return next;
40447
42302
  }
40448
42303
  async function bootConfig(options = {}) {
@@ -40765,8 +42620,8 @@ var InputBuilder = class {
40765
42620
  async registerFile(input) {
40766
42621
  const ref = await this.store.add({ ...input, kind: "file" });
40767
42622
  this.refs.push(ref);
40768
- const path48 = ref.meta.filename ?? ref.meta.label ?? String(ref.seq);
40769
- return `[file:${path48}]`;
42623
+ const path51 = ref.meta.filename ?? ref.meta.label ?? String(ref.seq);
42624
+ return `[file:${path51}]`;
40770
42625
  }
40771
42626
  /**
40772
42627
  * Whether `appendPaste(text)` would collapse the text to a placeholder
@@ -40915,9 +42770,9 @@ var DefaultSystemPromptBuilder = class {
40915
42770
  skillBodyCache;
40916
42771
  /** Tools from last build — used for memory relevance scoring. */
40917
42772
  _lastBuildTools;
40918
- /** Cached rendered online agents string, keyed by array reference. */
42773
+ /** Cached rendered online agents string, keyed by content fingerprint. */
40919
42774
  _lastOnlineAgents;
40920
- /** Cached full buildToolUsage output — keyed by tools array + online agents refs. */
42775
+ /** Cached full buildToolUsage output — keyed by tools array ref + agents fingerprint. */
40921
42776
  _toolsUsageCache;
40922
42777
  /**
40923
42778
  * Normalizes `tokenSavingMode` to a boolean for backward-compatible boolean checks.
@@ -41053,13 +42908,13 @@ var DefaultSystemPromptBuilder = class {
41053
42908
  if (!planPath) return "";
41054
42909
  let raw;
41055
42910
  try {
41056
- const stat15 = await fsp3.stat(planPath);
41057
- if (this._planCache && this._planCache.path === planPath && this._planCache.mtimeMs === stat15.mtimeMs) {
42911
+ const stat16 = await fsp3.stat(planPath);
42912
+ if (this._planCache && this._planCache.path === planPath && this._planCache.mtimeMs === stat16.mtimeMs) {
41058
42913
  return this._planCache.text;
41059
42914
  }
41060
42915
  raw = await fsp3.readFile(planPath, "utf8");
41061
42916
  const text = this._formatPlan(raw);
41062
- this._planCache = { path: planPath, mtimeMs: stat15.mtimeMs, text };
42917
+ this._planCache = { path: planPath, mtimeMs: stat16.mtimeMs, text };
41063
42918
  return text;
41064
42919
  } catch {
41065
42920
  this._planCache = void 0;
@@ -41074,8 +42929,8 @@ var DefaultSystemPromptBuilder = class {
41074
42929
  return "";
41075
42930
  }
41076
42931
  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 "";
42932
+ const open10 = parsed.items.filter((i) => i?.status !== "done");
42933
+ if (open10.length === 0) return "";
41079
42934
  const lines = ["## Active plan"];
41080
42935
  if (parsed.title) lines.push(`*${parsed.title}*`, "");
41081
42936
  parsed.items.forEach((it, idx) => {
@@ -41090,7 +42945,8 @@ var DefaultSystemPromptBuilder = class {
41090
42945
  }
41091
42946
  buildToolUsage(tools, ctx) {
41092
42947
  if (tools.length === 0) return "## Tool usage\n\nNo tools registered.";
41093
- if (this._toolsUsageCache?.toolsRef === tools && this._toolsUsageCache?.agentsRef === ctx.onlineAgents) {
42948
+ const agentsHash = this.agentsFingerprint(ctx.onlineAgents);
42949
+ if (this._toolsUsageCache?.toolsRef === tools && this._toolsUsageCache?.agentsHash === agentsHash) {
41094
42950
  return this._toolsUsageCache.text;
41095
42951
  }
41096
42952
  const byCat = /* @__PURE__ */ new Map();
@@ -41377,7 +43233,7 @@ the server connection \u2014 only tool visibility changes.`);
41377
43233
 
41378
43234
  Use \`context_manager\` to manage context. Call \`{"action":"check"}\` to see token budget.`);
41379
43235
  } else {
41380
- const maxCtx = this.opts.modelCapabilities?.maxContextTokens ?? 128e3;
43236
+ const maxCtx = this.modelCapabilities()?.maxContextTokens ?? 128e3;
41381
43237
  const threshold = maxCtx <= 32e3 ? "50" : "70";
41382
43238
  lines.push(`
41383
43239
  ## Context management
@@ -41395,15 +43251,38 @@ summarize it, and let the tool result hold only the summary.`);
41395
43251
  }
41396
43252
  }
41397
43253
  const text = lines.join("\n");
41398
- this._toolsUsageCache = { toolsRef: tools, agentsRef: ctx.onlineAgents, text };
43254
+ this._toolsUsageCache = { toolsRef: tools, agentsHash, text };
41399
43255
  return text;
41400
43256
  }
41401
43257
  /**
41402
- * Render the online agents list, cached by array reference. The agents
43258
+ * Cheap content fingerprint of the online agents array. The mailbox
43259
+ * rebuilds the array as a fresh object on every status check, so caching
43260
+ * by reference always misses — this lets the renderOnlineAgents and
43261
+ * buildToolUsage caches detect membership changes instead.
43262
+ *
43263
+ * O(n) over agent names with no per-element string concatenation. Uses
43264
+ * FNV-1a over character codes so two different agent sets collide only
43265
+ * if they produce the identical sequence of name characters — astronomically
43266
+ * unlikely. A collision would produce a stale agent list in the prompt,
43267
+ * a cosmetic issue, not a correctness bug.
43268
+ */
43269
+ agentsFingerprint(agents) {
43270
+ if (!agents || agents.length === 0) return "0";
43271
+ let h = 2166136261;
43272
+ for (const a of agents) {
43273
+ for (let i = 0; i < a.name.length; i++) {
43274
+ h ^= a.name.charCodeAt(i);
43275
+ h = Math.imul(h, 16777619) >>> 0;
43276
+ }
43277
+ }
43278
+ return `${agents.length}:${h.toString(36)}`;
43279
+ }
43280
+ /**
43281
+ * Render the online agents list, cached by content fingerprint. The agents
41403
43282
  * 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.
43283
+ * build turn (hundreds of ms). The fingerprint detects membership changes
43284
+ * without holding the array reference the mailbox rebuilds the array as
43285
+ * a fresh object on every status check, so reference equality always misses.
41407
43286
  *
41408
43287
  * Tier behaviour:
41409
43288
  * - 'off' / 'medium' / 'aggressive' → full list with names, sessions, sources
@@ -41411,13 +43290,14 @@ summarize it, and let the tool result hold only the summary.`);
41411
43290
  */
41412
43291
  renderOnlineAgents(agents) {
41413
43292
  if (!agents || agents.length === 0) return "";
41414
- if (this._lastOnlineAgents?.ref === agents) {
43293
+ const hash = this.agentsFingerprint(agents);
43294
+ if (this._lastOnlineAgents?.hash === hash) {
41415
43295
  return this._lastOnlineAgents.text;
41416
43296
  }
41417
43297
  const totalCount = agents.length;
41418
43298
  if (this.tier === "minimal" || this.tier === "light") {
41419
43299
  const text2 = ` (${totalCount} agent${totalCount !== 1 ? "s" : ""} online)`;
41420
- this._lastOnlineAgents = { ref: agents, text: text2 };
43300
+ this._lastOnlineAgents = { hash, text: text2 };
41421
43301
  return text2;
41422
43302
  }
41423
43303
  const agentList = agents.map(
@@ -41427,11 +43307,21 @@ summarize it, and let the tool result hold only the summary.`);
41427
43307
 
41428
43308
  **Currently online (${totalCount} agent${totalCount !== 1 ? "s" : ""}):**
41429
43309
  ${agentList}`;
41430
- this._lastOnlineAgents = { ref: agents, text };
43310
+ this._lastOnlineAgents = { hash, text };
41431
43311
  return text;
41432
43312
  }
41433
43313
  async buildEnvironment(ctx) {
41434
- const cached = this.envCacheByRoot.get(ctx.projectRoot);
43314
+ const modelCapabilities = this.modelCapabilities();
43315
+ const cacheKey = [
43316
+ ctx.projectRoot,
43317
+ ctx.provider ?? "",
43318
+ ctx.model ?? "",
43319
+ modelCapabilities?.maxContextTokens ?? 0,
43320
+ modelCapabilities?.supportsTools ? 1 : 0,
43321
+ modelCapabilities?.supportsVision ? 1 : 0,
43322
+ modelCapabilities?.supportsReasoning ? 1 : 0
43323
+ ].join("\0");
43324
+ const cached = this.envCacheByRoot.get(cacheKey);
41435
43325
  if (cached) return cached;
41436
43326
  const today = this.opts.todayIso ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
41437
43327
  const platform2 = `${os6.platform()} ${os6.release()}`;
@@ -41463,15 +43353,15 @@ ${agentList}`;
41463
43353
  `- Running on: ${ctx.provider ?? "<unknown provider>"}/${ctx.model ?? "<unknown model>"}`
41464
43354
  );
41465
43355
  }
41466
- if (this.opts.modelCapabilities) {
43356
+ if (modelCapabilities) {
41467
43357
  lines.push(
41468
- `- Context window: ${this.opts.modelCapabilities.maxContextTokens.toLocaleString()} tokens max`
43358
+ `- Context window: ${modelCapabilities.maxContextTokens.toLocaleString()} tokens max`
41469
43359
  );
41470
43360
  }
41471
43361
  }
41472
- if (tier !== "aggressive" && this.opts.modelCapabilities) {
43362
+ if (tier !== "aggressive" && modelCapabilities) {
41473
43363
  lines.push(
41474
- `- Context window: ${this.opts.modelCapabilities.maxContextTokens.toLocaleString()} tokens max`
43364
+ `- Context window: ${modelCapabilities.maxContextTokens.toLocaleString()} tokens max`
41475
43365
  );
41476
43366
  }
41477
43367
  if (tier !== "aggressive" && (ctx.provider || ctx.model)) {
@@ -41493,9 +43383,13 @@ ${agentList}`;
41493
43383
  );
41494
43384
  }
41495
43385
  const text = lines.join("\n");
41496
- this.envCacheByRoot.set(ctx.projectRoot, text);
43386
+ this.envCacheByRoot.set(cacheKey, text);
41497
43387
  return text;
41498
43388
  }
43389
+ modelCapabilities() {
43390
+ const caps = this.opts.modelCapabilities;
43391
+ return typeof caps === "function" ? caps() : caps;
43392
+ }
41499
43393
  async buildMemoryAndSkills() {
41500
43394
  const parts = [];
41501
43395
  const memoryCount = this.tier === "minimal" ? 3 : this.tier === "light" ? 5 : 8;
@@ -41621,19 +43515,19 @@ ${clean.trim()}`);
41621
43515
  }
41622
43516
  async dirExists(p) {
41623
43517
  try {
41624
- const stat15 = await fsp3.stat(p);
41625
- return stat15.isDirectory();
43518
+ const stat16 = await fsp3.stat(p);
43519
+ return stat16.isDirectory();
41626
43520
  } catch {
41627
43521
  return false;
41628
43522
  }
41629
43523
  }
41630
43524
  async gitStatus(root) {
41631
- return new Promise((resolve18) => {
43525
+ return new Promise((resolve19) => {
41632
43526
  let settled = false;
41633
43527
  const finish = (s) => {
41634
43528
  if (settled) return;
41635
43529
  settled = true;
41636
- resolve18(s);
43530
+ resolve19(s);
41637
43531
  };
41638
43532
  let proc;
41639
43533
  const timer = setTimeout(() => {
@@ -42554,18 +44448,21 @@ Rules:
42554
44448
  - Be concise: one tight instruction per version (a few sentences at most). No preamble, no explanation, no quotes, no markdown headers.
42555
44449
  - If the message is already clear and complete, return it essentially unchanged.
42556
44450
 
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).
44451
+ Detect the language of the user's LATEST message and output accordingly:
44452
+
44453
+ - If that message is ALREADY in English: output exactly ONE refined version, in English. Nothing else \u2014 no "---" line, no second copy.
44454
+ - If that message is in ANY OTHER language (Turkish, Spanish, \u2026): output TWO versions separated by a line containing only "---":
44455
+ - First version: refined in the SAME LANGUAGE the user wrote in.
44456
+ - Second version: refined in ENGLISH (translate the intent into clear English while preserving all concrete details).
42560
44457
 
42561
- Output format:
44458
+ Output format for non-English input:
42562
44459
  <refined in user's language>
42563
44460
  ---
42564
44461
  <refined in English>
42565
44462
 
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.
44463
+ 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
44464
 
42568
- Output ONLY the two versions separated by "---" \u2014 nothing else.`;
44465
+ Output ONLY the refined request(s) in the format above \u2014 nothing else.`;
42569
44466
  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
44467
  function shouldEnhance(text) {
42571
44468
  const t2 = text.trim();
@@ -42578,6 +44475,24 @@ function shouldEnhance(text) {
42578
44475
  if (words.length < 3) return false;
42579
44476
  return true;
42580
44477
  }
44478
+ var EFFORT_PREFERENCE = [
44479
+ "low",
44480
+ "minimal",
44481
+ "medium",
44482
+ "high",
44483
+ "xhigh",
44484
+ "max",
44485
+ "none"
44486
+ ];
44487
+ function gatedEnhancerReasoning(rc) {
44488
+ if (!rc) return void 0;
44489
+ if (rc.effortSupported && rc.effortLevels.length > 0) {
44490
+ const lowest = EFFORT_PREFERENCE.find((e) => rc.effortLevels.includes(e)) ?? rc.effortLevels[0];
44491
+ if (lowest) return { effort: lowest };
44492
+ }
44493
+ if (rc.disableSupported) return { enabled: false };
44494
+ return void 0;
44495
+ }
42581
44496
  function normalizedEqual(a, b) {
42582
44497
  const norm = (s) => s.trim().replace(/\s+/g, " ").toLowerCase();
42583
44498
  return norm(a) === norm(b);
@@ -42601,11 +44516,17 @@ async function enhanceUserPrompt(opts) {
42601
44516
  model,
42602
44517
  system: [{ type: "text", text: ENHANCER_SYSTEM_PROMPT }],
42603
44518
  messages: [{ role: "user", content: buildRefinerInput(text, opts.history) }],
42604
- maxTokens
44519
+ maxTokens,
42605
44520
  // NOTE: deliberately NO `temperature`. The main agent loop never sets it,
42606
44521
  // and reasoning models (DeepSeek reasoner, o1/o3, …) return HTTP 400 when
42607
44522
  // `temperature` is present — which would make every refine call fail and
42608
44523
  // silently fall back to the original (no panel shown).
44524
+ //
44525
+ // A reasoning hint is forwarded ONLY when the caller supplies one (it must
44526
+ // already be gated to the model's advertised support — see
44527
+ // `gatedEnhancerReasoning`). Absent it, no reasoning field is sent, which
44528
+ // is identical to the original behavior.
44529
+ ...opts.reasoning ? { reasoning: opts.reasoning } : {}
42609
44530
  };
42610
44531
  const timer = new AbortController();
42611
44532
  const to = setTimeout(() => timer.abort(new Error("enhancer timeout")), timeoutMs);
@@ -42619,7 +44540,6 @@ async function enhanceUserPrompt(opts) {
42619
44540
  }
42620
44541
  const sepIdx = raw.indexOf("\n---\n");
42621
44542
  if (sepIdx === -1) {
42622
- opts.onError?.("model did not produce two versions");
42623
44543
  return { refined: raw, english: raw };
42624
44544
  }
42625
44545
  const refined = raw.slice(0, sepIdx).trim();
@@ -43090,7 +45010,7 @@ var PhaseOrchestrator = class {
43090
45010
  async mergeOne(phase, handle) {
43091
45011
  if (!this.worktrees) return;
43092
45012
  try {
43093
- const resolve18 = this.ctx.resolveConflict ? async (info) => {
45013
+ const resolve19 = this.ctx.resolveConflict ? async (info) => {
43094
45014
  const shouldResolve = await this.shouldAttemptConflictResolution(phase, info);
43095
45015
  if (!shouldResolve) return false;
43096
45016
  this.emit("phase.conflictResolving", {
@@ -43104,7 +45024,7 @@ var PhaseOrchestrator = class {
43104
45024
  const mergeOpts = {
43105
45025
  squash: true
43106
45026
  };
43107
- if (resolve18 !== void 0) mergeOpts.resolve = resolve18;
45027
+ if (resolve19 !== void 0) mergeOpts.resolve = resolve19;
43108
45028
  const result = await this.worktrees.merge(handle, mergeOpts);
43109
45029
  if (result.resolved) {
43110
45030
  this.emit("phase.conflictResolved", { phaseId: phase.id, name: phase.name });
@@ -43455,7 +45375,7 @@ var PhaseOrchestrator = class {
43455
45375
  }
43456
45376
  }
43457
45377
  delay(ms) {
43458
- return new Promise((resolve18) => setTimeout(resolve18, ms));
45378
+ return new Promise((resolve19) => setTimeout(resolve19, ms));
43459
45379
  }
43460
45380
  };
43461
45381
 
@@ -44516,8 +46436,8 @@ var CollaborationBus = class {
44516
46436
  if (this.isPaused()) return false;
44517
46437
  this.pausedAtMs = Date.now();
44518
46438
  this.pausedBy = byParticipant;
44519
- this.pausePromise = new Promise((resolve18) => {
44520
- this.pauseResolve = resolve18;
46439
+ this.pausePromise = new Promise((resolve19) => {
46440
+ this.pauseResolve = resolve19;
44521
46441
  });
44522
46442
  return true;
44523
46443
  }
@@ -44553,8 +46473,8 @@ var CollaborationBus = class {
44553
46473
  return true;
44554
46474
  }
44555
46475
  let timer;
44556
- const timeoutPromise = new Promise((resolve18) => {
44557
- timer = setTimeout(() => resolve18("timeout"), timeoutMs);
46476
+ const timeoutPromise = new Promise((resolve19) => {
46477
+ timer = setTimeout(() => resolve19("timeout"), timeoutMs);
44558
46478
  });
44559
46479
  const resumedPromise = this.pausePromise.then(() => "resumed").catch(() => "resumed");
44560
46480
  const winner = await Promise.race([resumedPromise, timeoutPromise]);
@@ -44573,6 +46493,24 @@ var CollaborationBus = class {
44573
46493
  // "skip the bash call, just give it the answer I typed". The
44574
46494
  // injection is matched by tool_use_id, consumed once, and discarded.
44575
46495
  injectionQueue = /* @__PURE__ */ new Map();
46496
+ onConsumed;
46497
+ /**
46498
+ * Register a listener fired when `collabInjectMiddleware` actually splices a
46499
+ * queued injection into a tool call (Phase 4 feedback loop). The webui's
46500
+ * CollaborationWebSocketHandler uses it to broadcast a
46501
+ * `collab.injection.granted` with phase `'consumed'` so observers learn the
46502
+ * injection was applied (and to which real tool). Last registration wins.
46503
+ */
46504
+ onInjectionConsumed(fn) {
46505
+ this.onConsumed = fn;
46506
+ }
46507
+ /**
46508
+ * Invoked by `collabInjectMiddleware` immediately after it replaces a tool
46509
+ * call's result with a queued injection. No-op when no listener is set.
46510
+ */
46511
+ notifyInjectionConsumed(info) {
46512
+ this.onConsumed?.(info);
46513
+ }
44576
46514
  /**
44577
46515
  * Queue a manual tool result. The next time the agent's toolCall
44578
46516
  * pipeline sees a matching `toolUse.id`, the
@@ -44835,7 +46773,7 @@ async function gitOtherWorktrees(cwd, signal) {
44835
46773
  return branches.slice(1);
44836
46774
  }
44837
46775
  function runGit(args, cwd, signal) {
44838
- return new Promise((resolve18, reject) => {
46776
+ return new Promise((resolve19, reject) => {
44839
46777
  let stdout = "";
44840
46778
  let stderr = "";
44841
46779
  const child = spawn("git", args, {
@@ -44854,7 +46792,7 @@ function runGit(args, cwd, signal) {
44854
46792
  });
44855
46793
  child.on("error", (err) => reject(err));
44856
46794
  child.on("close", (code) => {
44857
- if (code === 0) resolve18(stdout);
46795
+ if (code === 0) resolve19(stdout);
44858
46796
  else reject(new Error(stderr || `git ${args[0]} exited ${code}`));
44859
46797
  });
44860
46798
  });
@@ -44967,8 +46905,8 @@ function extractManifestPath(msg) {
44967
46905
  }
44968
46906
  return void 0;
44969
46907
  }
44970
- function isManifestFile(path48) {
44971
- const name = pathBasename(path48).toLowerCase();
46908
+ function isManifestFile(path51) {
46909
+ const name = pathBasename(path51).toLowerCase();
44972
46910
  const manifests = [
44973
46911
  "package.json",
44974
46912
  "package-lock.json",
@@ -45068,6 +47006,13 @@ function collabInjectMiddleware(bus, opts = {}) {
45068
47006
  content: typeof injected.content === "string" ? injected.content : JSON.stringify(injected.content),
45069
47007
  is_error: injected.isError
45070
47008
  };
47009
+ bus.notifyInjectionConsumed({
47010
+ toolUseId: payload.toolUse.id,
47011
+ toolName: payload.toolUse.name,
47012
+ authorId: injected.authorId,
47013
+ reason: injected.reason,
47014
+ isError: injected.isError
47015
+ });
45071
47016
  };
45072
47017
  }
45073
47018
 
@@ -45472,7 +47417,7 @@ function createGitPlugin() {
45472
47417
  }
45473
47418
  async function runGit2(args, cwd) {
45474
47419
  try {
45475
- return await new Promise((resolve18, reject) => {
47420
+ return await new Promise((resolve19, reject) => {
45476
47421
  const child = spawn("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"], signal: AbortSignal.timeout(3e4), windowsHide: true });
45477
47422
  let stdout = "";
45478
47423
  let stderr = "";
@@ -45493,7 +47438,7 @@ async function runGit2(args, cwd) {
45493
47438
  })
45494
47439
  );
45495
47440
  });
45496
- child.on("close", (code) => resolve18({ stdout, stderr, code: code ?? 0 }));
47441
+ child.on("close", (code) => resolve19({ stdout, stderr, code: code ?? 0 }));
45497
47442
  });
45498
47443
  } catch (err) {
45499
47444
  if (err instanceof WrongStackError) throw err;
@@ -46295,7 +48240,7 @@ If NOTHING worth flagging:
46295
48240
  ## \u{1F982} Chimera Review \u2014 all clear \u2705
46296
48241
  No issues found in N changed files.`;
46297
48242
  async function runGit3(args, cwd) {
46298
- return new Promise((resolve18, reject) => {
48243
+ return new Promise((resolve19, reject) => {
46299
48244
  let child;
46300
48245
  try {
46301
48246
  child = spawn("git", args, {
@@ -46316,8 +48261,8 @@ async function runGit3(args, cwd) {
46316
48261
  child.stderr?.on("data", (d) => {
46317
48262
  stderr += d;
46318
48263
  });
46319
- child.on("error", () => resolve18({ stdout, stderr, code: 1 }));
46320
- child.on("close", (code) => resolve18({ stdout, stderr, code: code ?? 0 }));
48264
+ child.on("error", () => resolve19({ stdout, stderr, code: 1 }));
48265
+ child.on("close", (code) => resolve19({ stdout, stderr, code: code ?? 0 }));
46321
48266
  });
46322
48267
  }
46323
48268
  async function isGitRepo2(cwd) {
@@ -46471,6 +48416,6 @@ function createChimeraPlugin() {
46471
48416
  };
46472
48417
  }
46473
48418
 
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 };
48419
+ 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, AdaptiveConcurrencyController, 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
48420
  //# sourceMappingURL=index.js.map
46476
48421
  //# sourceMappingURL=index.js.map