@wrongstack/core 0.1.4 → 0.1.8

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.
package/dist/index.js CHANGED
@@ -467,7 +467,8 @@ var TOKENS = {
467
467
  SkillLoader: t("SkillLoader"),
468
468
  SystemPromptBuilder: t("SystemPromptBuilder"),
469
469
  SecretScrubber: t("SecretScrubber"),
470
- ModelsRegistry: t("ModelsRegistry")
470
+ ModelsRegistry: t("ModelsRegistry"),
471
+ ModeStore: t("ModeStore")
471
472
  };
472
473
 
473
474
  // src/kernel/run-controller.ts
@@ -800,16 +801,33 @@ var DEFAULT_SPEC_TEMPLATE = {
800
801
 
801
802
  // src/types/task-graph.ts
802
803
  function computeTaskProgress(graph) {
803
- const nodes = Array.from(graph.nodes.values());
804
- const total = nodes.length;
805
- const completed = nodes.filter((n) => n.status === "completed").length;
806
- const pending = nodes.filter((n) => n.status === "pending").length;
807
- const inProgress = nodes.filter((n) => n.status === "in_progress").length;
808
- const blocked = nodes.filter((n) => n.status === "blocked").length;
809
- const failed = nodes.filter((n) => n.status === "failed").length;
810
- const review = nodes.filter((n) => n.status === "review").length;
811
- const estimatedHours = nodes.reduce((sum, n) => sum + (n.estimateHours ?? 0), 0);
812
- const actualHours = nodes.reduce((sum, n) => sum + (n.actualHours ?? 0), 0);
804
+ let completed = 0, pending = 0, inProgress = 0, blocked = 0, failed = 0, review = 0;
805
+ let estimatedHours = 0, actualHours = 0;
806
+ for (const n of graph.nodes.values()) {
807
+ switch (n.status) {
808
+ case "completed":
809
+ completed++;
810
+ break;
811
+ case "pending":
812
+ pending++;
813
+ break;
814
+ case "in_progress":
815
+ inProgress++;
816
+ break;
817
+ case "blocked":
818
+ blocked++;
819
+ break;
820
+ case "failed":
821
+ failed++;
822
+ break;
823
+ case "review":
824
+ review++;
825
+ break;
826
+ }
827
+ estimatedHours += n.estimateHours ?? 0;
828
+ actualHours += n.actualHours ?? 0;
829
+ }
830
+ const total = graph.nodes.size;
813
831
  return {
814
832
  total,
815
833
  pending,
@@ -836,16 +854,18 @@ function findCriticalPath(graph) {
836
854
  }
837
855
  function topologicalSort(graph) {
838
856
  const visited = /* @__PURE__ */ new Set();
857
+ const inStack = /* @__PURE__ */ new Set();
839
858
  const result = [];
840
859
  function visit(id) {
860
+ if (inStack.has(id)) return;
841
861
  if (visited.has(id)) return;
862
+ if (!graph.nodes.has(id)) return;
842
863
  visited.add(id);
843
- const node = graph.nodes.get(id);
844
- if (!node) return;
845
- const outgoing = graph.edges.filter((e) => e.from === id);
846
- for (const edge of outgoing) {
847
- visit(edge.to);
864
+ inStack.add(id);
865
+ for (const edge of graph.edges) {
866
+ if (edge.from === id) visit(edge.to);
848
867
  }
868
+ inStack.delete(id);
849
869
  result.push(id);
850
870
  }
851
871
  for (const rootId of graph.rootNodes) {
@@ -937,7 +957,7 @@ function sanitizeJsonString(s) {
937
957
  JSON.parse(out);
938
958
  return out;
939
959
  } catch {
940
- return out;
960
+ return null;
941
961
  }
942
962
  }
943
963
  function stripSingleLineComments(s) {
@@ -1027,7 +1047,22 @@ function stripAnsi(s) {
1027
1047
 
1028
1048
  // src/utils/glob-match.ts
1029
1049
  function escapeRegex(s) {
1030
- return s.replace(/[.+^${}()|\\/]/g, "\\$&");
1050
+ return s.replace(/[.+^${}()|\\]/g, "\\$&");
1051
+ }
1052
+ var COMPILED_GLOB_CACHE = /* @__PURE__ */ new Map();
1053
+ var CACHE_MAX_SIZE = 2e3;
1054
+ function getCachedGlob(pattern) {
1055
+ const cached = COMPILED_GLOB_CACHE.get(pattern);
1056
+ if (cached) return cached;
1057
+ if (COMPILED_GLOB_CACHE.size >= CACHE_MAX_SIZE) {
1058
+ const keys = [...COMPILED_GLOB_CACHE.keys()];
1059
+ for (let i = 0; i < Math.floor(CACHE_MAX_SIZE / 4); i++) {
1060
+ COMPILED_GLOB_CACHE.delete(keys[i]);
1061
+ }
1062
+ }
1063
+ const re = compileGlob(pattern);
1064
+ COMPILED_GLOB_CACHE.set(pattern, re);
1065
+ return re;
1031
1066
  }
1032
1067
  function compileGlob(pattern) {
1033
1068
  let i = 0;
@@ -1076,7 +1111,7 @@ function compileGlob(pattern) {
1076
1111
  return new RegExp(re);
1077
1112
  }
1078
1113
  function matchGlob(pattern, input) {
1079
- return compileGlob(pattern).test(input);
1114
+ return getCachedGlob(pattern).test(input);
1080
1115
  }
1081
1116
  function matchAny(patterns, input) {
1082
1117
  return patterns.some((p) => matchGlob(p, input));
@@ -1243,6 +1278,7 @@ function resolveWstackPaths(opts) {
1243
1278
  const projectDir = path2.join(globalRoot, "projects", hash);
1244
1279
  return {
1245
1280
  globalRoot,
1281
+ configDir: globalRoot,
1246
1282
  globalConfig: path2.join(globalRoot, "config.json"),
1247
1283
  secretsKey: path2.join(globalRoot, ".key"),
1248
1284
  globalMemory: path2.join(globalRoot, "memory.md"),
@@ -1301,7 +1337,6 @@ function createToolOutputSerializer(opts = {}) {
1301
1337
  }
1302
1338
  const half = Math.floor(available / 2);
1303
1339
  const first = text.slice(0, half);
1304
- Buffer.byteLength(first, "utf8");
1305
1340
  const second = text.slice(text.length - half);
1306
1341
  return { text: `${first}${marker}${second}`, newBudget: 0 };
1307
1342
  }
@@ -1310,21 +1345,33 @@ function createToolOutputSerializer(opts = {}) {
1310
1345
 
1311
1346
  // src/utils/token-estimate.ts
1312
1347
  var RoughTokenEstimate = (text) => Math.max(1, Math.ceil(text.length / 4));
1348
+ var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
1349
+ var ESTIMATE_CACHE_MAX_SIZE = 1e4;
1350
+ function getCachedEstimate(key, compute) {
1351
+ const existing = ESTIMATE_CACHE.get(key);
1352
+ if (existing !== void 0) return existing;
1353
+ if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
1354
+ const keys = [...ESTIMATE_CACHE.keys()];
1355
+ for (let i = 0; i < Math.floor(ESTIMATE_CACHE_MAX_SIZE / 4); i++) {
1356
+ ESTIMATE_CACHE.delete(keys[i]);
1357
+ }
1358
+ }
1359
+ const estimate = compute();
1360
+ ESTIMATE_CACHE.set(key, estimate);
1361
+ return estimate;
1362
+ }
1313
1363
  function estimateToolInputTokens(input) {
1314
1364
  if (typeof input === "string") return RoughTokenEstimate(input);
1315
- if (input !== null && typeof input === "object" && "__tokenEstimate" in input) {
1316
- return input.__tokenEstimate;
1365
+ if (input === null || typeof input !== "object") {
1366
+ return RoughTokenEstimate(String(input));
1317
1367
  }
1318
- const str = typeof input === "object" ? JSON.stringify(input) : String(input);
1319
- const estimate = RoughTokenEstimate(str);
1320
- if (input !== null && typeof input === "object" && !Array.isArray(input)) {
1321
- input.__tokenEstimate = estimate;
1322
- }
1323
- return estimate;
1368
+ const key = JSON.stringify(input);
1369
+ return getCachedEstimate(key, () => RoughTokenEstimate(key));
1324
1370
  }
1325
1371
  function estimateToolResultTokens(content) {
1326
1372
  if (typeof content === "string") return RoughTokenEstimate(content);
1327
- return RoughTokenEstimate(JSON.stringify(content));
1373
+ const key = JSON.stringify(content);
1374
+ return getCachedEstimate(key, () => RoughTokenEstimate(key));
1328
1375
  }
1329
1376
  function estimateTextTokens(text) {
1330
1377
  return RoughTokenEstimate(text);
@@ -1336,11 +1383,11 @@ function validateAgainstSchema(value, schema) {
1336
1383
  walk(value, schema, "", errors);
1337
1384
  return { ok: errors.length === 0, errors };
1338
1385
  }
1339
- function walk(value, schema, path15, errors) {
1386
+ function walk(value, schema, path17, errors) {
1340
1387
  if (schema.enum !== void 0) {
1341
1388
  if (!schema.enum.some((e) => deepEqual(e, value))) {
1342
1389
  errors.push({
1343
- path: path15 || "<root>",
1390
+ path: path17 || "<root>",
1344
1391
  message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
1345
1392
  });
1346
1393
  return;
@@ -1349,7 +1396,7 @@ function walk(value, schema, path15, errors) {
1349
1396
  if (typeof schema.type === "string") {
1350
1397
  if (!checkType(value, schema.type)) {
1351
1398
  errors.push({
1352
- path: path15 || "<root>",
1399
+ path: path17 || "<root>",
1353
1400
  message: `expected ${schema.type}, got ${describeType(value)}`
1354
1401
  });
1355
1402
  return;
@@ -1359,19 +1406,19 @@ function walk(value, schema, path15, errors) {
1359
1406
  const obj = value;
1360
1407
  for (const req of schema.required ?? []) {
1361
1408
  if (!(req in obj)) {
1362
- errors.push({ path: joinPath(path15, req), message: "required property missing" });
1409
+ errors.push({ path: joinPath(path17, req), message: "required property missing" });
1363
1410
  }
1364
1411
  }
1365
1412
  if (schema.properties) {
1366
1413
  for (const [key, subSchema] of Object.entries(schema.properties)) {
1367
1414
  if (key in obj) {
1368
- walk(obj[key], subSchema, joinPath(path15, key), errors);
1415
+ walk(obj[key], subSchema, joinPath(path17, key), errors);
1369
1416
  }
1370
1417
  }
1371
1418
  }
1372
1419
  }
1373
1420
  if (schema.type === "array" && Array.isArray(value) && schema.items) {
1374
- value.forEach((item, i) => walk(item, schema.items, `${path15}[${i}]`, errors));
1421
+ value.forEach((item, i) => walk(item, schema.items, `${path17}[${i}]`, errors));
1375
1422
  }
1376
1423
  }
1377
1424
  function checkType(value, type) {
@@ -1567,9 +1614,11 @@ var DefaultPathResolver = class {
1567
1614
  ensureInsideRoot(absPath) {
1568
1615
  const resolved = this.resolve(absPath);
1569
1616
  if (!this.isInsideRoot(resolved)) {
1570
- throw new Error(
1571
- `Path "${absPath}" resolves outside the project root (${this.projectRoot})`
1572
- );
1617
+ const display = path2.isAbsolute(absPath) ? path2.basename(absPath) : absPath;
1618
+ const err = new Error(`Path "${display}" resolves outside the project root`);
1619
+ err.fullPath = absPath;
1620
+ err.projectRoot = this.projectRoot;
1621
+ throw err;
1573
1622
  }
1574
1623
  return resolved;
1575
1624
  }
@@ -1697,7 +1746,9 @@ var DefaultSessionStore = class {
1697
1746
  try {
1698
1747
  handle = await fsp.open(file, "a", 384);
1699
1748
  } catch (err) {
1700
- throw new Error(`Failed to open session file: ${err instanceof Error ? err.message : String(err)}`);
1749
+ throw new Error(`Failed to open session file: ${err instanceof Error ? err.message : String(err)}`, {
1750
+ cause: err
1751
+ });
1701
1752
  }
1702
1753
  try {
1703
1754
  return new FileSessionWriter(id, handle, startedAt, meta, { dir: this.dir, filePath: file });
@@ -1715,7 +1766,8 @@ var DefaultSessionStore = class {
1715
1766
  handle = await fsp.open(file, "a", 384);
1716
1767
  } catch (err) {
1717
1768
  throw new Error(
1718
- `Failed to open session "${id}" for append: ${err instanceof Error ? err.message : String(err)}`
1769
+ `Failed to open session "${id}" for append: ${err instanceof Error ? err.message : String(err)}`,
1770
+ { cause: err }
1719
1771
  );
1720
1772
  }
1721
1773
  const writer = new FileSessionWriter(
@@ -1738,7 +1790,10 @@ var DefaultSessionStore = class {
1738
1790
  const events = [];
1739
1791
  for (const line of lines) {
1740
1792
  try {
1741
- events.push(JSON.parse(line));
1793
+ const parsed = JSON.parse(line);
1794
+ if (parsed !== null && typeof parsed === "object" && typeof parsed.type === "string" && typeof parsed.ts === "string") {
1795
+ events.push(parsed);
1796
+ }
1742
1797
  } catch {
1743
1798
  }
1744
1799
  }
@@ -1755,7 +1810,11 @@ var DefaultSessionStore = class {
1755
1810
  ids.map((id) => this.summaryFor(id).catch(() => null))
1756
1811
  );
1757
1812
  const out = sessions.filter((s) => s !== null);
1758
- out.sort((a, b) => a.startedAt < b.startedAt ? 1 : -1);
1813
+ out.sort((a, b) => {
1814
+ if (a.startedAt < b.startedAt) return 1;
1815
+ if (a.startedAt > b.startedAt) return -1;
1816
+ return a.id.localeCompare(b.id);
1817
+ });
1759
1818
  return out.slice(0, limit);
1760
1819
  } catch {
1761
1820
  return [];
@@ -1901,6 +1960,8 @@ var FileSessionWriter = class {
1901
1960
  filePath;
1902
1961
  initDone = false;
1903
1962
  resumed;
1963
+ appendFailCount = 0;
1964
+ lastAppendWarnAt = 0;
1904
1965
  async writeSessionStart() {
1905
1966
  if (this.initDone || this.closed) return;
1906
1967
  this.initDone = true;
@@ -1929,7 +1990,19 @@ var FileSessionWriter = class {
1929
1990
  await this.handle.appendFile(`${JSON.stringify(event)}
1930
1991
  `, "utf8");
1931
1992
  } catch (err) {
1932
- console.warn("[session] append failed:", err instanceof Error ? err.message : String(err));
1993
+ this.appendFailCount++;
1994
+ const now = Date.now();
1995
+ if (now - this.lastAppendWarnAt > 5e3) {
1996
+ const suppressed = this.appendFailCount - 1;
1997
+ const tail = suppressed > 0 ? ` (+${suppressed} suppressed)` : "";
1998
+ console.warn(
1999
+ "[session] append failed:",
2000
+ err instanceof Error ? err.message : String(err),
2001
+ tail
2002
+ );
2003
+ this.lastAppendWarnAt = now;
2004
+ this.appendFailCount = 0;
2005
+ }
1933
2006
  }
1934
2007
  }
1935
2008
  /**
@@ -2135,6 +2208,15 @@ function mergeAdjacentText(blocks) {
2135
2208
  var MAX_BYTES_TOTAL = 32e3;
2136
2209
  var DefaultMemoryStore = class {
2137
2210
  files;
2211
+ /**
2212
+ * Per-scope serialization queue. `remember` / `forget` / `consolidate` /
2213
+ * `clear` are read-modify-write against a single file; without a lock,
2214
+ * two concurrent calls on the same scope can read the same baseline and
2215
+ * the later write silently drops the earlier entry. We chain each
2216
+ * mutation onto the prior promise for the same scope so they run in
2217
+ * issue order. Different scopes still proceed in parallel.
2218
+ */
2219
+ writeChain = /* @__PURE__ */ new Map();
2138
2220
  constructor(opts) {
2139
2221
  this.files = {
2140
2222
  "project-agents": opts.paths.inProjectAgentsFile,
@@ -2142,6 +2224,18 @@ var DefaultMemoryStore = class {
2142
2224
  "user-memory": opts.paths.globalMemory
2143
2225
  };
2144
2226
  }
2227
+ async runSerialized(scope, work) {
2228
+ const prior = this.writeChain.get(scope) ?? Promise.resolve();
2229
+ const next = prior.catch(() => void 0).then(work);
2230
+ this.writeChain.set(scope, next);
2231
+ try {
2232
+ return await next;
2233
+ } finally {
2234
+ if (this.writeChain.get(scope) === next) {
2235
+ this.writeChain.delete(scope);
2236
+ }
2237
+ }
2238
+ }
2145
2239
  async readAll() {
2146
2240
  const parts = [];
2147
2241
  for (const scope of ["project-agents", "project-memory", "user-memory"]) {
@@ -2160,27 +2254,32 @@ ${body.trim()}`);
2160
2254
  }
2161
2255
  }
2162
2256
  async remember(text, scope = "project-memory") {
2163
- const file = this.files[scope];
2164
- await ensureDir(path2.dirname(file));
2165
- let existing = "";
2166
- try {
2167
- existing = await fsp.readFile(file, "utf8");
2168
- } catch {
2169
- }
2170
- const ts = (/* @__PURE__ */ new Date()).toISOString();
2171
- const id = `mem_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
2172
- const entry = `
2257
+ return this.runSerialized(scope, async () => {
2258
+ const file = this.files[scope];
2259
+ await ensureDir(path2.dirname(file));
2260
+ let existing = "";
2261
+ try {
2262
+ existing = await fsp.readFile(file, "utf8");
2263
+ } catch {
2264
+ }
2265
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
2266
+ const id = `mem_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
2267
+ const entry = `
2173
2268
  - [${ts}] ${id} ${text.replace(/\n/g, " ")}
2174
2269
  `;
2175
- const next = existing.trim() ? existing.replace(/\n+$/, "") + entry : `# WrongStack Memory
2270
+ const next = existing.trim() ? existing.replace(/\n+$/, "") + entry : `# WrongStack Memory
2176
2271
  ${entry}`;
2177
- await atomicWrite(file, next);
2178
- const buf = Buffer.byteLength(next, "utf8");
2179
- if (buf > MAX_BYTES_TOTAL) {
2180
- await this.consolidate(scope);
2181
- }
2272
+ await atomicWrite(file, next);
2273
+ const buf = Buffer.byteLength(next, "utf8");
2274
+ if (buf > MAX_BYTES_TOTAL) {
2275
+ await this.consolidateUnsafe(scope);
2276
+ }
2277
+ });
2182
2278
  }
2183
2279
  async forget(query, scope = "project-memory") {
2280
+ return this.runSerialized(scope, async () => this.forgetUnsafe(query, scope));
2281
+ }
2282
+ async forgetUnsafe(query, scope) {
2184
2283
  const file = this.files[scope];
2185
2284
  let existing;
2186
2285
  try {
@@ -2217,6 +2316,9 @@ ${entry}`;
2217
2316
  return removed;
2218
2317
  }
2219
2318
  async consolidate(scope) {
2319
+ return this.runSerialized(scope, async () => this.consolidateUnsafe(scope));
2320
+ }
2321
+ async consolidateUnsafe(scope) {
2220
2322
  const file = this.files[scope];
2221
2323
  let existing;
2222
2324
  try {
@@ -2247,12 +2349,14 @@ ${entry}`;
2247
2349
  }
2248
2350
  async clear(scope) {
2249
2351
  if (scope) {
2250
- await atomicWrite(this.files[scope], "");
2251
- } else {
2252
- for (const s of ["project-agents", "project-memory", "user-memory"]) {
2253
- await atomicWrite(this.files[s], "");
2254
- }
2352
+ await this.runSerialized(scope, async () => atomicWrite(this.files[scope], ""));
2353
+ return;
2255
2354
  }
2355
+ await Promise.all(
2356
+ ["project-agents", "project-memory", "user-memory"].map(
2357
+ (s) => this.runSerialized(s, async () => atomicWrite(this.files[s], ""))
2358
+ )
2359
+ );
2256
2360
  }
2257
2361
  };
2258
2362
  function labelOf(scope) {
@@ -2300,9 +2404,27 @@ var PATTERNS = [
2300
2404
  regex: /\b([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,})['"]?(?!\s*[A-Za-z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))/g
2301
2405
  }
2302
2406
  ];
2407
+ var SCRUB_CHUNK_BYTES = 64 * 1024;
2303
2408
  var DefaultSecretScrubber = class {
2304
2409
  scrub(text) {
2305
2410
  if (!text) return text;
2411
+ if (text.length <= SCRUB_CHUNK_BYTES) {
2412
+ return this.scrubOne(text);
2413
+ }
2414
+ const out = [];
2415
+ let i = 0;
2416
+ while (i < text.length) {
2417
+ let end = Math.min(i + SCRUB_CHUNK_BYTES, text.length);
2418
+ if (end < text.length) {
2419
+ const nl = text.lastIndexOf("\n", end);
2420
+ if (nl > i + SCRUB_CHUNK_BYTES / 2) end = nl + 1;
2421
+ }
2422
+ out.push(this.scrubOne(text.slice(i, end)));
2423
+ i = end;
2424
+ }
2425
+ return out.join("");
2426
+ }
2427
+ scrubOne(text) {
2306
2428
  let out = text;
2307
2429
  for (const p of PATTERNS) {
2308
2430
  out = out.replace(p.regex, (_match, group1, group2) => {
@@ -2402,7 +2524,17 @@ var DefaultSecretVault = class {
2402
2524
  }
2403
2525
  };
2404
2526
  function decryptConfigSecrets(cfg, vault) {
2405
- return walk2(cfg, vault, (v) => vault.decrypt(v));
2527
+ return walk2(cfg, vault, (v, key) => {
2528
+ try {
2529
+ return vault.decrypt(v);
2530
+ } catch (err) {
2531
+ console.warn(
2532
+ `[secret-vault] Failed to decrypt "${key}":`,
2533
+ err instanceof Error ? err.message : err
2534
+ );
2535
+ return "";
2536
+ }
2537
+ });
2406
2538
  }
2407
2539
  function encryptConfigSecrets(cfg, vault) {
2408
2540
  return walk2(cfg, vault, (v) => vault.encrypt(v));
@@ -2416,7 +2548,7 @@ function walk2(node, vault, transform) {
2416
2548
  const out = {};
2417
2549
  for (const [k, v] of Object.entries(node)) {
2418
2550
  if (typeof v === "string" && isSecretField(k)) {
2419
- out[k] = transform(v);
2551
+ out[k] = transform(v, k);
2420
2552
  } else if (typeof v === "object" && v !== null) {
2421
2553
  out[k] = walk2(v, vault, transform);
2422
2554
  } else {
@@ -2490,9 +2622,11 @@ function walkCount(node, vault, counter) {
2490
2622
  }
2491
2623
  return out;
2492
2624
  }
2625
+ var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
2493
2626
  function deepMerge(a, b) {
2494
2627
  const out = { ...a };
2495
2628
  for (const [k, v] of Object.entries(b)) {
2629
+ if (FORBIDDEN_PROTO_KEYS.has(k)) continue;
2496
2630
  const existing = out[k];
2497
2631
  if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
2498
2632
  out[k] = deepMerge(existing, v);
@@ -2527,7 +2661,7 @@ var DefaultPermissionPolicy = class {
2527
2661
  if (!this.loaded) await this.reload();
2528
2662
  const namespaceEntry = this.findNamespaceEntry(tool.name);
2529
2663
  const entry = this.policy[tool.name] ?? namespaceEntry;
2530
- const subject = this.subjectFor(tool.name, input);
2664
+ const subject = this.subjectFor(tool.name, input, tool.subjectKey);
2531
2665
  if (entry?.deny && subject && matchAny(entry.deny, subject)) {
2532
2666
  return { permission: "deny", source: "deny", reason: "matched deny pattern" };
2533
2667
  }
@@ -2575,16 +2709,23 @@ var DefaultPermissionPolicy = class {
2575
2709
  throw err;
2576
2710
  }
2577
2711
  }
2578
- subjectFor(toolName, input) {
2712
+ subjectFor(toolName, input, subjectKey) {
2579
2713
  if (!input || typeof input !== "object") return void 0;
2580
2714
  const obj = input;
2581
2715
  const globChars = /[*?\[\]]/g;
2582
2716
  const escapeGlob = (s) => s.replace(globChars, (c) => `\\${c}`);
2717
+ const normalizePath = (s) => escapeGlob(s.replace(/\\/g, "/"));
2718
+ if (subjectKey) {
2719
+ const v = obj[subjectKey];
2720
+ if (typeof v === "string") {
2721
+ return subjectKey === "path" || subjectKey === "file" || subjectKey === "files" ? normalizePath(v) : escapeGlob(v);
2722
+ }
2723
+ }
2583
2724
  if (toolName === "bash" && typeof obj.command === "string") {
2584
2725
  return escapeGlob(obj.command);
2585
2726
  }
2586
2727
  if (typeof obj.path === "string") {
2587
- return escapeGlob(obj.path.replace(/\\/g, "/"));
2728
+ return normalizePath(obj.path);
2588
2729
  }
2589
2730
  if (typeof obj.url === "string") {
2590
2731
  return escapeGlob(obj.url);
@@ -2605,14 +2746,15 @@ var DefaultPermissionPolicy = class {
2605
2746
  };
2606
2747
 
2607
2748
  // src/defaults/retry-policy.ts
2608
- var DefaultRetryPolicy = class {
2749
+ var DefaultRetryPolicy = class _DefaultRetryPolicy {
2750
+ static NETWORK_ERR_RE = /ECONN|ETIMEDOUT|ETIME|ENOTFOUND|EAI_AGAIN|fetch failed/i;
2609
2751
  shouldRetry(err, attempt) {
2610
2752
  if (err instanceof ProviderError) {
2611
2753
  if (!err.retryable) return false;
2612
2754
  return attempt < this.maxAttempts(err);
2613
2755
  }
2614
2756
  const msg = err.message ?? "";
2615
- const isNetwork = /ECONN|ETIMEDOUT|ETIME|ENOTFOUND|EAI_AGAIN|fetch failed/i.test(msg);
2757
+ const isNetwork = _DefaultRetryPolicy.NETWORK_ERR_RE.test(msg);
2616
2758
  if (isNetwork) return attempt < 2;
2617
2759
  return false;
2618
2760
  }
@@ -2634,55 +2776,43 @@ var DefaultRetryPolicy = class {
2634
2776
  };
2635
2777
 
2636
2778
  // src/defaults/error-handler.ts
2779
+ var CONTEXT_OVERFLOW_RE = /context|too long|tokens/i;
2780
+ var NETWORK_ERR_RE = /ECONN|ETIMEDOUT|ETIME|ENOTFOUND|EAI_AGAIN|fetch failed/i;
2637
2781
  function buildRecoveryStrategies(opts) {
2638
2782
  return [
2639
2783
  {
2640
2784
  label: "context_overflow_reduce",
2641
2785
  compactor: opts?.compactor,
2642
2786
  async attempt(err, ctx) {
2643
- if (err instanceof ProviderError && (err.status === 413 || /context|too long|tokens/i.test(err.message))) {
2644
- if (this.compactor) {
2645
- try {
2646
- const report = await this.compactor.compact(ctx, { aggressive: true });
2647
- if (report.after < report.before) {
2648
- return {
2649
- content: [{ type: "text", text: "[context compacted automatically \u2014 please retry]" }],
2650
- stopReason: "end_turn",
2651
- usage: { input: 0, output: 0 },
2652
- model: ctx.model
2653
- };
2654
- }
2655
- } catch {
2787
+ if (!(err instanceof ProviderError)) return null;
2788
+ if (err.status !== 413 && !CONTEXT_OVERFLOW_RE.test(err.message)) return null;
2789
+ if (this.compactor) {
2790
+ try {
2791
+ const report = await this.compactor.compact(ctx, { aggressive: true });
2792
+ if (report.after < report.before) {
2793
+ return { action: "retry", reason: "context_compacted" };
2656
2794
  }
2795
+ } catch {
2657
2796
  }
2658
- return null;
2659
2797
  }
2660
2798
  return null;
2661
2799
  }
2662
2800
  },
2663
2801
  {
2664
2802
  label: "rate_limit_backoff",
2665
- async attempt(err, ctx) {
2666
- if (err instanceof ProviderError && err.status === 429) {
2667
- const delayMs = err.body?.retryAfterMs ?? 5e3;
2668
- const delay = Math.max(1e3, Math.min(delayMs, 6e4));
2669
- await new Promise((r) => setTimeout(r, delay));
2670
- return {
2671
- content: [{ type: "text", text: "[rate limit backoff applied \u2014 please retry]" }],
2672
- stopReason: "end_turn",
2673
- usage: { input: 0, output: 0 },
2674
- model: ctx.model
2675
- };
2676
- }
2677
- return null;
2803
+ async attempt(err) {
2804
+ if (!(err instanceof ProviderError) || err.status !== 429) return null;
2805
+ const delayMs = err.body?.retryAfterMs ?? 5e3;
2806
+ const delay = Math.max(1e3, Math.min(delayMs, 6e4));
2807
+ await new Promise((r) => setTimeout(r, delay));
2808
+ return { action: "retry", reason: "rate_limit_backoff" };
2678
2809
  }
2679
2810
  },
2680
2811
  {
2681
2812
  label: "downgrade_model",
2682
2813
  async attempt(err, ctx) {
2683
- if (err instanceof ProviderError && (err.status === 429 || err.status === 529 || err.status >= 500)) {
2684
- return null;
2685
- }
2814
+ if (!(err instanceof ProviderError)) return null;
2815
+ if (err.status !== 429 && err.status !== 529 && err.status < 500) return null;
2686
2816
  return null;
2687
2817
  }
2688
2818
  }
@@ -2705,12 +2835,12 @@ var DefaultErrorHandler = class {
2705
2835
  if (err.status === 429) return { kind: "rate_limit", retryable: true };
2706
2836
  if (err.status === 529) return { kind: "overloaded", retryable: true };
2707
2837
  if (err.status >= 500) return { kind: "server", retryable: true };
2708
- if (err.status === 413 || /context|too long|tokens/i.test(err.message)) {
2838
+ if (err.status === 413 || CONTEXT_OVERFLOW_RE.test(err.message)) {
2709
2839
  return { kind: "context_overflow", retryable: false };
2710
2840
  }
2711
2841
  if (err.status >= 400) return { kind: "client", retryable: false };
2712
2842
  }
2713
- if (err instanceof Error && /ECONN|ETIMEDOUT|ETIME|ENOTFOUND|EAI_AGAIN|fetch failed/i.test(err.message)) {
2843
+ if (err instanceof Error && NETWORK_ERR_RE.test(err.message)) {
2714
2844
  return { kind: "network", retryable: true };
2715
2845
  }
2716
2846
  return { kind: "unknown", retryable: false };
@@ -2774,13 +2904,28 @@ var DefaultSkillLoader = class {
2774
2904
  async manifestText() {
2775
2905
  const skills = await this.list();
2776
2906
  if (skills.length === 0) return "";
2907
+ const entries = await this.listEntries();
2777
2908
  const lines = ["## Available skills"];
2778
- for (const s of skills) {
2779
- lines.push(`- **${s.name}** \u2014 ${s.description.replace(/\n/g, " ").trim()}`);
2780
- lines.push(` Path: ${s.path}`);
2909
+ for (const e of entries) {
2910
+ const scopeTag = e.scope.length > 0 ? ` \u2014 ${e.scope.slice(0, 3).join(", ")}` : "";
2911
+ lines.push(`- **${e.name}**${scopeTag}`);
2912
+ lines.push(` Use when: ${e.trigger}`);
2781
2913
  }
2782
2914
  return lines.join("\n");
2783
2915
  }
2916
+ async listEntries() {
2917
+ const skills = await this.list();
2918
+ const entries = [];
2919
+ for (const s of skills) {
2920
+ try {
2921
+ const raw = await fsp.readFile(s.path, "utf8");
2922
+ const { trigger, scope } = parseDescription(raw);
2923
+ entries.push({ name: s.name, trigger, scope, source: s.source, path: s.path });
2924
+ } catch {
2925
+ }
2926
+ }
2927
+ return entries;
2928
+ }
2784
2929
  async readBody(name) {
2785
2930
  const m = await this.find(name);
2786
2931
  if (!m) throw new Error(`Skill "${name}" not found`);
@@ -2823,6 +2968,19 @@ function parseFrontmatter(raw) {
2823
2968
  flush();
2824
2969
  return out;
2825
2970
  }
2971
+ function parseDescription(raw) {
2972
+ const fm = parseFrontmatter(raw);
2973
+ const desc = fm.description ?? "";
2974
+ const firstSentenceEnd = desc.indexOf(". ");
2975
+ const trigger = firstSentenceEnd !== -1 ? desc.slice(0, firstSentenceEnd + 1).trim() : desc.trim().split("\n")[0] ?? "";
2976
+ const scope = [];
2977
+ const coversMatch = /(?:covers|for|including)\s+([^.]+)/i.exec(desc);
2978
+ if (coversMatch) {
2979
+ const items = coversMatch[1].replace(/[·•]/g, ",").split(",").map((s) => s.trim()).filter(Boolean);
2980
+ scope.push(...items);
2981
+ }
2982
+ return { trigger, scope };
2983
+ }
2826
2984
  var BEHAVIOR_DEFAULTS = {
2827
2985
  version: 1,
2828
2986
  context: {
@@ -2871,11 +3029,13 @@ var ENV_MAP = {
2871
3029
  function isPrimitiveArray(a) {
2872
3030
  return a.every((v) => v === null || typeof v !== "object");
2873
3031
  }
3032
+ var FORBIDDEN_PROTO_KEYS2 = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
2874
3033
  function deepMerge2(base, patch) {
2875
3034
  if (typeof base !== "object" || base === null) return patch ?? base;
2876
3035
  if (typeof patch !== "object" || patch === null) return base;
2877
3036
  const out = { ...base };
2878
3037
  for (const [k, v] of Object.entries(patch)) {
3038
+ if (FORBIDDEN_PROTO_KEYS2.has(k)) continue;
2879
3039
  const existing = out[k];
2880
3040
  if (Array.isArray(v)) {
2881
3041
  if (Array.isArray(existing) && isPrimitiveArray(v) && isPrimitiveArray(existing)) {
@@ -2943,8 +3103,12 @@ var DefaultConfigLoader = class {
2943
3103
  if (cfg.providers) {
2944
3104
  for (const pcfg of Object.values(cfg.providers)) {
2945
3105
  if (!pcfg || typeof pcfg !== "object") continue;
2946
- const keys = pcfg.apiKeys;
2947
- if (!Array.isArray(keys) || keys.length === 0) continue;
3106
+ const rawKeys = pcfg.apiKeys;
3107
+ if (!Array.isArray(rawKeys) || rawKeys.length === 0) continue;
3108
+ const keys = rawKeys.filter(
3109
+ (k) => !!k && typeof k === "object" && typeof k.label === "string" && typeof k.apiKey === "string"
3110
+ );
3111
+ if (keys.length === 0) continue;
2948
3112
  const existing = pcfg.apiKey;
2949
3113
  if (existing && existing.length > 0) continue;
2950
3114
  const activeLabel = pcfg.activeKey;
@@ -2955,23 +3119,42 @@ var DefaultConfigLoader = class {
2955
3119
  }
2956
3120
  }
2957
3121
  this.validateBehavior(cfg);
2958
- if (this.strict) this.validateIdentity(cfg);
3122
+ if (this.strict) {
3123
+ this.validateIdentity(cfg);
3124
+ }
2959
3125
  return Object.freeze(cfg);
2960
3126
  }
2961
3127
  async readJson(file) {
3128
+ let raw;
2962
3129
  try {
2963
- const raw = await fsp.readFile(file, "utf8");
2964
- const parsed = safeParse(raw);
2965
- if (parsed.ok && parsed.value) return parsed.value;
2966
- } catch {
3130
+ raw = await fsp.readFile(file, "utf8");
3131
+ } catch (err) {
3132
+ if (err.code !== "ENOENT") {
3133
+ console.warn(`[config] Failed to read "${file}":`, err);
3134
+ }
3135
+ return {};
3136
+ }
3137
+ const parsed = safeParse(raw);
3138
+ if (!parsed.ok || !parsed.value) {
3139
+ console.warn(
3140
+ `[config] Failed to parse "${file}": invalid JSON. Falling back to defaults for this layer.`
3141
+ );
3142
+ return {};
2967
3143
  }
2968
- return {};
3144
+ return parsed.value;
2969
3145
  }
2970
3146
  validateBehavior(cfg) {
2971
3147
  if (cfg.version === void 0) throw new Error("Config: missing version field");
2972
3148
  if (cfg.version !== 1) throw new Error(`Config: unsupported version ${cfg.version}`);
2973
3149
  const c = cfg.context;
2974
3150
  if (!c) throw new Error("Config: missing context section");
3151
+ const fields = ["warnThreshold", "softThreshold", "hardThreshold"];
3152
+ for (const f of fields) {
3153
+ const v = c[f];
3154
+ if (typeof v !== "number" || !Number.isFinite(v)) {
3155
+ throw new Error(`Config: context.${String(f)} must be a finite number (got ${typeof v})`);
3156
+ }
3157
+ }
2975
3158
  if (c.warnThreshold >= c.softThreshold || c.softThreshold >= c.hardThreshold) {
2976
3159
  throw new Error("Config: context thresholds must satisfy warn < soft < hard");
2977
3160
  }
@@ -3019,7 +3202,8 @@ var DefaultConfigStore = class {
3019
3202
  for (const w of this.watchers) {
3020
3203
  try {
3021
3204
  w(next, prev);
3022
- } catch {
3205
+ } catch (err) {
3206
+ console.error("[config-store] watcher threw:", err);
3023
3207
  }
3024
3208
  }
3025
3209
  return next;
@@ -3129,9 +3313,18 @@ var HybridCompactor = class {
3129
3313
  }
3130
3314
  }
3131
3315
  let saved = 0;
3132
- for (let i = 0; i < preserveStart; i++) {
3316
+ let changed = false;
3317
+ const nextMessages = new Array(messages.length);
3318
+ for (let i = 0; i < messages.length; i++) {
3133
3319
  const msg = messages[i];
3134
- if (!msg || !Array.isArray(msg.content)) continue;
3320
+ if (i >= preserveStart) {
3321
+ nextMessages[i] = msg;
3322
+ continue;
3323
+ }
3324
+ if (!msg || !Array.isArray(msg.content)) {
3325
+ nextMessages[i] = msg;
3326
+ continue;
3327
+ }
3135
3328
  const newContent = msg.content.map((b) => {
3136
3329
  if (b.type !== "tool_result") return b;
3137
3330
  const tokens = estimateToolResultTokens(b.content);
@@ -3145,8 +3338,14 @@ var HybridCompactor = class {
3145
3338
  };
3146
3339
  return elided;
3147
3340
  });
3148
- messages[i] = { ...msg, content: newContent };
3341
+ if (newContent.length === msg.content.length && newContent.every((b, idx) => b === msg.content[idx])) {
3342
+ nextMessages[i] = msg;
3343
+ } else {
3344
+ nextMessages[i] = { ...msg, content: newContent };
3345
+ changed = true;
3346
+ }
3149
3347
  }
3348
+ if (changed) ctx.state.replaceMessages(nextMessages);
3150
3349
  return saved;
3151
3350
  }
3152
3351
  collapseAncientTurns(ctx) {
@@ -3180,10 +3379,10 @@ var HybridCompactor = class {
3180
3379
  let total = 0;
3181
3380
  for (const m of messages) {
3182
3381
  if (typeof m.content === "string") {
3183
- total += estimateTextTokens(m.content);
3382
+ total += this.estimator(m.content);
3184
3383
  } else {
3185
3384
  for (const b of m.content) {
3186
- if (b.type === "text") total += estimateTextTokens(b.text);
3385
+ if (b.type === "text") total += this.estimator(b.text);
3187
3386
  else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
3188
3387
  else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
3189
3388
  }
@@ -3223,7 +3422,7 @@ var IntelligentCompactor = class {
3223
3422
  const beforeTokens = this.estimateTokens(ctx.messages);
3224
3423
  const reductions = [];
3225
3424
  const load = beforeTokens / this.maxContext;
3226
- const aggressive = opts.aggressive ?? load >= this.softThreshold;
3425
+ const aggressive = load >= this.hardThreshold ? true : opts.aggressive ?? load >= this.softThreshold;
3227
3426
  const saved1 = this.eliseOldToolResults(ctx);
3228
3427
  if (saved1 > 0) reductions.push({ phase: "elision", saved: saved1 });
3229
3428
  if (aggressive) {
@@ -3332,9 +3531,18 @@ var IntelligentCompactor = class {
3332
3531
  }
3333
3532
  }
3334
3533
  let saved = 0;
3335
- for (let i = 0; i < preserveStart; i++) {
3534
+ let changed = false;
3535
+ const nextMessages = new Array(messages.length);
3536
+ for (let i = 0; i < messages.length; i++) {
3336
3537
  const msg = messages[i];
3337
- if (!msg || !Array.isArray(msg.content)) continue;
3538
+ if (i >= preserveStart) {
3539
+ nextMessages[i] = msg;
3540
+ continue;
3541
+ }
3542
+ if (!msg || !Array.isArray(msg.content)) {
3543
+ nextMessages[i] = msg;
3544
+ continue;
3545
+ }
3338
3546
  const newContent = msg.content.map((b) => {
3339
3547
  if (b.type !== "tool_result") return b;
3340
3548
  const tokens = estimateToolResultTokens(b.content);
@@ -3347,8 +3555,14 @@ var IntelligentCompactor = class {
3347
3555
  is_error: b.is_error
3348
3556
  };
3349
3557
  });
3350
- messages[i] = { ...msg, content: newContent };
3558
+ if (newContent.length === msg.content.length && newContent.every((b, idx) => b === msg.content[idx])) {
3559
+ nextMessages[i] = msg;
3560
+ } else {
3561
+ nextMessages[i] = { ...msg, content: newContent };
3562
+ changed = true;
3563
+ }
3351
3564
  }
3565
+ if (changed) ctx.state.replaceMessages(nextMessages);
3352
3566
  return saved;
3353
3567
  }
3354
3568
  hasTextContent(m) {
@@ -3501,7 +3715,7 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
3501
3715
  const jsonEnd = raw.lastIndexOf("}");
3502
3716
  if (jsonStart === -1 || jsonEnd === -1) {
3503
3717
  return this.fallbackSelect(
3504
- Array.from({ length: messageCount }, (_, i) => ({ role: "user", content: "" })),
3718
+ Array.from({ length: messageCount }, () => ({ role: "user", content: "" })),
3505
3719
  this.maxContextTokens
3506
3720
  );
3507
3721
  }
@@ -3510,7 +3724,7 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
3510
3724
  parsed = JSON.parse(raw.slice(jsonStart, jsonEnd + 1));
3511
3725
  } catch {
3512
3726
  return this.fallbackSelect(
3513
- Array.from({ length: messageCount }, (_, i) => ({ role: "user", content: "" })),
3727
+ Array.from({ length: messageCount }, () => ({ role: "user", content: "" })),
3514
3728
  this.maxContextTokens
3515
3729
  );
3516
3730
  }
@@ -3563,7 +3777,7 @@ var SelectiveCompactor = class {
3563
3777
  const savedElision = this.eliseOldToolResults(ctx);
3564
3778
  if (savedElision > 0) reductions.push({ phase: "elision", saved: savedElision });
3565
3779
  const afterPhase1 = this.estimateTokens(ctx.messages);
3566
- const targetBudget = this.computeTargetBudget(load, opts.aggressive ?? false);
3780
+ const targetBudget = this.computeTargetBudget(load);
3567
3781
  if (afterPhase1 > targetBudget) {
3568
3782
  const savedSelective = await this.runSelector(ctx, targetBudget);
3569
3783
  if (savedSelective > 0) reductions.push({ phase: "selective", saved: savedSelective });
@@ -3581,7 +3795,7 @@ var SelectiveCompactor = class {
3581
3795
  try {
3582
3796
  result = await this.selector.select(ctx.messages, targetBudget);
3583
3797
  } catch {
3584
- return this.aggressiveRecencyTrim(ctx, targetBudget);
3798
+ return this.aggressiveRecencyTrim(ctx);
3585
3799
  }
3586
3800
  await this.executePlan(ctx, result);
3587
3801
  const after = this.estimateTokens(ctx.messages);
@@ -3636,9 +3850,8 @@ Summarize the following message range:`;
3636
3850
  * Fallback when selector fails: aggressively trim from the oldest end
3637
3851
  * until we hit targetBudget.
3638
3852
  */
3639
- aggressiveRecencyTrim(ctx, targetBudget) {
3853
+ aggressiveRecencyTrim(ctx) {
3640
3854
  const messages = ctx.messages;
3641
- this.estimateTokens(messages);
3642
3855
  const preserveIdx = Math.max(0, messages.length - this.preserveK * 2);
3643
3856
  if (preserveIdx <= 0) return 0;
3644
3857
  let boundary = preserveIdx;
@@ -3659,7 +3872,7 @@ Summarize the following message range:`;
3659
3872
  ctx.state.replaceMessages([summaryMsg, ...tail]);
3660
3873
  return Math.max(0, removedTokens - this.estimateTokens([summaryMsg]));
3661
3874
  }
3662
- computeTargetBudget(load, aggressive) {
3875
+ computeTargetBudget(load) {
3663
3876
  if (load >= this.hardThreshold) {
3664
3877
  return Math.floor(this.maxContext * 0.5);
3665
3878
  }
@@ -3681,9 +3894,18 @@ Summarize the following message range:`;
3681
3894
  }
3682
3895
  }
3683
3896
  let saved = 0;
3684
- for (let i = 0; i < preserveStart; i++) {
3897
+ let changed = false;
3898
+ const nextMessages = new Array(messages.length);
3899
+ for (let i = 0; i < messages.length; i++) {
3685
3900
  const msg = messages[i];
3686
- if (!msg || !Array.isArray(msg.content)) continue;
3901
+ if (i >= preserveStart) {
3902
+ nextMessages[i] = msg;
3903
+ continue;
3904
+ }
3905
+ if (!msg || !Array.isArray(msg.content)) {
3906
+ nextMessages[i] = msg;
3907
+ continue;
3908
+ }
3687
3909
  const newContent = msg.content.map((b) => {
3688
3910
  if (b.type !== "tool_result") return b;
3689
3911
  const text = typeof b.content === "string" ? b.content : JSON.stringify(b.content);
@@ -3697,8 +3919,14 @@ Summarize the following message range:`;
3697
3919
  is_error: b.is_error
3698
3920
  };
3699
3921
  });
3700
- messages[i] = { ...msg, content: newContent };
3922
+ if (newContent.every((b, idx) => b === msg.content[idx])) {
3923
+ nextMessages[i] = msg;
3924
+ } else {
3925
+ nextMessages[i] = { ...msg, content: newContent };
3926
+ changed = true;
3927
+ }
3701
3928
  }
3929
+ if (changed) ctx.state.replaceMessages(nextMessages);
3702
3930
  return saved;
3703
3931
  }
3704
3932
  hasTextContent(m) {
@@ -3734,46 +3962,78 @@ var AutoCompactionMiddleware = class {
3734
3962
  name = "AutoCompaction";
3735
3963
  compactor;
3736
3964
  warnThreshold;
3737
- // fraction of maxContext (0-1)
3738
3965
  softThreshold;
3739
3966
  hardThreshold;
3740
3967
  maxContext;
3741
3968
  estimator;
3742
3969
  aggressiveOn;
3970
+ events;
3971
+ failureMode;
3743
3972
  /**
3744
- * @param compactor Compactor to use for compaction
3745
- * @param maxContext Provider's max context window in tokens
3746
- * @param estimator Token estimation function (ctx → token count)
3747
- * @param thresholds Threshold fractions (0-1) of maxContext
3748
- * @param aggressiveOn Which threshold triggers aggressive (full LLM summarization)
3973
+ * @param compactor Compactor to use for compaction.
3974
+ * @param maxContext Provider's max context window in tokens.
3975
+ * @param estimator Token estimation function.
3976
+ * @param thresholds Threshold fractions (0-1) of maxContext.
3977
+ * @param opts Optional behavior. By default, failures at the
3978
+ * hard threshold throw AGENT_CONTEXT_OVERFLOW so
3979
+ * the agent does not continue into a likely
3980
+ * provider context overflow. Warn/soft failures
3981
+ * still emit compaction.failed and continue.
3749
3982
  */
3750
- constructor(compactor, maxContext, estimator, thresholds, aggressiveOn = "soft") {
3983
+ constructor(compactor, maxContext, estimator, thresholds, optsOrAggressiveOn = {}, events) {
3984
+ const opts = typeof optsOrAggressiveOn === "string" ? { aggressiveOn: optsOrAggressiveOn, events } : optsOrAggressiveOn;
3751
3985
  this.compactor = compactor;
3752
3986
  this.maxContext = maxContext;
3753
3987
  this.estimator = estimator;
3754
3988
  this.warnThreshold = thresholds.warn;
3755
3989
  this.softThreshold = thresholds.soft;
3756
3990
  this.hardThreshold = thresholds.hard;
3757
- this.aggressiveOn = aggressiveOn;
3991
+ this.aggressiveOn = opts.aggressiveOn ?? "soft";
3992
+ this.events = opts.events;
3993
+ this.failureMode = opts.failureMode ?? "throw_on_hard";
3758
3994
  }
3759
3995
  handler() {
3760
3996
  return async (ctx, next) => {
3761
3997
  const tokens = this.estimator(ctx);
3762
3998
  const load = tokens / this.maxContext;
3763
3999
  if (load >= this.hardThreshold) {
3764
- await this.compact(ctx, true);
4000
+ await this.compact(ctx, true, { level: "hard", tokens, load });
3765
4001
  } else if (load >= this.softThreshold) {
3766
- await this.compact(ctx, this.aggressiveOn !== "hard");
4002
+ await this.compact(ctx, this.aggressiveOn !== "hard", { level: "soft", tokens, load });
3767
4003
  } else if (load >= this.warnThreshold) {
3768
- await this.compact(ctx, false);
4004
+ await this.compact(ctx, false, { level: "warn", tokens, load });
3769
4005
  }
3770
4006
  return next(ctx);
3771
4007
  };
3772
4008
  }
3773
- async compact(ctx, aggressive) {
4009
+ async compact(ctx, aggressive, pressure) {
3774
4010
  try {
3775
4011
  await this.compactor.compact(ctx, { aggressive });
3776
- } catch {
4012
+ } catch (err) {
4013
+ const error = err instanceof Error ? err : new Error(String(err));
4014
+ const fatal = this.failureMode === "throw" || this.failureMode === "throw_on_hard" && pressure.level === "hard";
4015
+ this.events?.emit("compaction.failed", {
4016
+ err: error,
4017
+ aggressive,
4018
+ level: pressure.level,
4019
+ tokens: pressure.tokens,
4020
+ maxContext: this.maxContext,
4021
+ load: pressure.load,
4022
+ fatal
4023
+ });
4024
+ if (fatal) {
4025
+ throw new AgentError({
4026
+ message: `Auto-compaction failed at ${pressure.level} threshold`,
4027
+ code: "AGENT_CONTEXT_OVERFLOW",
4028
+ recoverable: true,
4029
+ context: {
4030
+ level: pressure.level,
4031
+ tokens: pressure.tokens,
4032
+ maxContext: this.maxContext
4033
+ },
4034
+ cause: err
4035
+ });
4036
+ }
3777
4037
  }
3778
4038
  }
3779
4039
  };
@@ -4150,8 +4410,9 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
4150
4410
  const context = {
4151
4411
  subagentId: id,
4152
4412
  tasks: [],
4153
- // parentBridge: wired by the caller via setSubagentBridge() once the
4154
- // bidirectional bridge is created. Reads gated by hasParentBridge().
4413
+ // Wired later by the caller via setSubagentBridge() once the
4414
+ // bidirectional bridge is created. Readers must null-check / use
4415
+ // hasParentBridge() — the type now reflects this.
4155
4416
  parentBridge: null,
4156
4417
  doneCondition: this.config.doneCondition,
4157
4418
  maxConcurrent: this.config.maxConcurrent ?? 4
@@ -4196,9 +4457,7 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
4196
4457
  this.emit("subagent.stopped", { subagentId, reason: "stopped by coordinator" });
4197
4458
  }
4198
4459
  async stopAll() {
4199
- for (const id of this.subagents.keys()) {
4200
- await this.stop(id);
4201
- }
4460
+ await Promise.allSettled([...this.subagents.keys()].map((id) => this.stop(id)));
4202
4461
  }
4203
4462
  getStatus() {
4204
4463
  return {
@@ -4234,7 +4493,17 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
4234
4493
  if (!subagentId) return;
4235
4494
  const task = this.pendingTasks.shift();
4236
4495
  if (!task) return;
4237
- void this.runDispatched(subagentId, task);
4496
+ this.runDispatched(subagentId, task).catch((err) => {
4497
+ this.recordCompletion({
4498
+ subagentId,
4499
+ taskId: task.id,
4500
+ status: "failed",
4501
+ error: err instanceof Error ? err.message : String(err),
4502
+ iterations: 0,
4503
+ toolCalls: 0,
4504
+ durationMs: 0
4505
+ });
4506
+ });
4238
4507
  }
4239
4508
  }
4240
4509
  canDispatch() {
@@ -4254,7 +4523,6 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
4254
4523
  subagent.currentTask = task.id;
4255
4524
  task.subagentId = subagentId;
4256
4525
  subagent.context.tasks.push(task);
4257
- this.inFlight++;
4258
4526
  this.emit("task.assigned", { task, subagentId });
4259
4527
  const budget = new SubagentBudget({
4260
4528
  maxIterations: subagent.config.maxIterations ?? this.config.defaultBudget?.maxIterations,
@@ -4264,6 +4532,10 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
4264
4532
  timeoutMs: task.timeoutMs ?? subagent.config.timeoutMs ?? this.config.defaultBudget?.timeoutMs
4265
4533
  });
4266
4534
  subagent.activeBudget = budget;
4535
+ if (!this.runner) {
4536
+ return;
4537
+ }
4538
+ this.inFlight++;
4267
4539
  const startTime = Date.now();
4268
4540
  const runCtx = {
4269
4541
  subagentId,
@@ -4273,9 +4545,6 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
4273
4545
  bridge: subagent.context.parentBridge || null
4274
4546
  };
4275
4547
  let result;
4276
- if (!this.runner) {
4277
- return;
4278
- }
4279
4548
  budget.start();
4280
4549
  try {
4281
4550
  const outcome = await this.executeWithTimeout(this.runner, task, runCtx, budget);
@@ -4290,13 +4559,14 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
4290
4559
  };
4291
4560
  } catch (err) {
4292
4561
  const status = err instanceof BudgetExceededError && err.kind === "timeout" ? "timeout" : subagent.abortController.signal.aborted ? "stopped" : "failed";
4562
+ const usage = budget.usage();
4293
4563
  result = {
4294
4564
  subagentId,
4295
4565
  taskId: task.id,
4296
4566
  status,
4297
4567
  error: err instanceof Error ? err.message : String(err),
4298
- iterations: budget.usage().iterations,
4299
- toolCalls: budget.usage().toolCalls,
4568
+ iterations: usage.iterations,
4569
+ toolCalls: usage.toolCalls,
4300
4570
  durationMs: Date.now() - startTime
4301
4571
  };
4302
4572
  }
@@ -4321,11 +4591,24 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
4321
4591
  recordCompletion(result) {
4322
4592
  this.completedResults.push(result);
4323
4593
  this.totalIterations += result.iterations;
4324
- this.inFlight = Math.max(0, this.inFlight - 1);
4594
+ if (this.inFlight > 0) {
4595
+ this.inFlight--;
4596
+ } else if (this.runner) {
4597
+ this.emit("warning", {
4598
+ type: "inFlight_underflow",
4599
+ taskId: result.taskId,
4600
+ subagentId: result.subagentId
4601
+ });
4602
+ return;
4603
+ }
4325
4604
  const subagent = this.subagents.get(result.subagentId);
4326
4605
  if (subagent && subagent.status !== "stopped") {
4327
- subagent.status = result.status === "failed" || result.status === "timeout" ? "error" : "idle";
4606
+ const failed = result.status === "failed" || result.status === "timeout";
4607
+ subagent.status = failed ? "error" : "idle";
4328
4608
  subagent.currentTask = void 0;
4609
+ if (subagent.abortController.signal.aborted) {
4610
+ subagent.abortController = new AbortController();
4611
+ }
4329
4612
  if (subagent.status === "error") {
4330
4613
  queueMicrotask(() => {
4331
4614
  if (subagent.status === "error") subagent.status = "idle";
@@ -4427,6 +4710,182 @@ function defaultFormatTaskInput(task) {
4427
4710
  return task.description ?? "";
4428
4711
  }
4429
4712
 
4713
+ // src/defaults/fleet-bus.ts
4714
+ var FleetBus = class {
4715
+ byId = /* @__PURE__ */ new Map();
4716
+ byType = /* @__PURE__ */ new Map();
4717
+ any = /* @__PURE__ */ new Set();
4718
+ /**
4719
+ * Hook a subagent's EventBus into the fleet. EventBus is strongly
4720
+ * typed and doesn't expose an `onAny` hook, so we subscribe to the
4721
+ * canonical set of event types a subagent emits during a run. New
4722
+ * event types added to the kernel must be added here too — but the
4723
+ * cost is a tiny single line per type, and the explicit list keeps
4724
+ * the wire format clear.
4725
+ *
4726
+ * Returns a disposer that detaches every subscription; call on
4727
+ * subagent teardown so the listeners don't outlive the run.
4728
+ */
4729
+ attach(subagentId, bus, taskId) {
4730
+ const FORWARDED_TYPES = [
4731
+ "tool.started",
4732
+ "tool.executed",
4733
+ "tool.progress",
4734
+ "tool.confirm_needed",
4735
+ "iteration.started",
4736
+ "iteration.completed",
4737
+ "provider.text_delta",
4738
+ "provider.response",
4739
+ "provider.retry",
4740
+ "provider.error",
4741
+ "session.started",
4742
+ "session.ended",
4743
+ "token.threshold"
4744
+ ];
4745
+ const offs = [];
4746
+ for (const t2 of FORWARDED_TYPES) {
4747
+ offs.push(
4748
+ bus.on(t2, (payload) => {
4749
+ this.emit({ subagentId, taskId, ts: Date.now(), type: t2, payload });
4750
+ })
4751
+ );
4752
+ }
4753
+ return () => {
4754
+ for (const off of offs) off();
4755
+ };
4756
+ }
4757
+ /** Subscribe to every event from one subagent. */
4758
+ subscribe(subagentId, handler) {
4759
+ let set = this.byId.get(subagentId);
4760
+ if (!set) {
4761
+ set = /* @__PURE__ */ new Set();
4762
+ this.byId.set(subagentId, set);
4763
+ }
4764
+ set.add(handler);
4765
+ return () => {
4766
+ set.delete(handler);
4767
+ };
4768
+ }
4769
+ /** Subscribe to one event type across all subagents. */
4770
+ filter(type, handler) {
4771
+ let set = this.byType.get(type);
4772
+ if (!set) {
4773
+ set = /* @__PURE__ */ new Set();
4774
+ this.byType.set(type, set);
4775
+ }
4776
+ set.add(handler);
4777
+ return () => {
4778
+ set.delete(handler);
4779
+ };
4780
+ }
4781
+ /** Subscribe to literally everything. The fleet roll-up uses this. */
4782
+ onAny(handler) {
4783
+ this.any.add(handler);
4784
+ return () => {
4785
+ this.any.delete(handler);
4786
+ };
4787
+ }
4788
+ emit(event) {
4789
+ const byId = this.byId.get(event.subagentId);
4790
+ if (byId) for (const h of byId) {
4791
+ try {
4792
+ h(event);
4793
+ } catch {
4794
+ }
4795
+ }
4796
+ const byType = this.byType.get(event.type);
4797
+ if (byType) for (const h of byType) {
4798
+ try {
4799
+ h(event);
4800
+ } catch {
4801
+ }
4802
+ }
4803
+ for (const h of this.any) {
4804
+ try {
4805
+ h(event);
4806
+ } catch {
4807
+ }
4808
+ }
4809
+ }
4810
+ };
4811
+ var FleetUsageAggregator = class {
4812
+ constructor(bus, priceLookup, metaLookup) {
4813
+ this.bus = bus;
4814
+ this.priceLookup = priceLookup;
4815
+ this.metaLookup = metaLookup;
4816
+ bus.filter("provider.response", (e) => this.onProviderResponse(e));
4817
+ bus.filter("tool.executed", (e) => this.onToolExecuted(e));
4818
+ bus.filter("iteration.started", (e) => this.onIterationStarted(e));
4819
+ }
4820
+ bus;
4821
+ priceLookup;
4822
+ metaLookup;
4823
+ perSubagent = /* @__PURE__ */ new Map();
4824
+ total = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
4825
+ /** Live snapshot — safe to call from a tool's execute() body. */
4826
+ snapshot() {
4827
+ return {
4828
+ total: { ...this.total },
4829
+ perSubagent: Object.fromEntries(
4830
+ Array.from(this.perSubagent.entries()).map(([k, v]) => [k, { ...v }])
4831
+ )
4832
+ };
4833
+ }
4834
+ ensure(subagentId) {
4835
+ let snap = this.perSubagent.get(subagentId);
4836
+ if (!snap) {
4837
+ const meta = this.metaLookup?.(subagentId);
4838
+ snap = {
4839
+ subagentId,
4840
+ provider: meta?.provider,
4841
+ model: meta?.model,
4842
+ input: 0,
4843
+ output: 0,
4844
+ cacheRead: 0,
4845
+ cacheWrite: 0,
4846
+ cost: 0,
4847
+ toolCalls: 0,
4848
+ iterations: 0,
4849
+ startedAt: Date.now(),
4850
+ lastEventAt: Date.now()
4851
+ };
4852
+ this.perSubagent.set(subagentId, snap);
4853
+ }
4854
+ return snap;
4855
+ }
4856
+ onProviderResponse(e) {
4857
+ const snap = this.ensure(e.subagentId);
4858
+ const p = e.payload;
4859
+ const usage = p?.usage;
4860
+ if (!usage) return;
4861
+ snap.input += usage.input ?? 0;
4862
+ snap.output += usage.output ?? 0;
4863
+ snap.cacheRead += usage.cacheRead ?? 0;
4864
+ snap.cacheWrite += usage.cacheWrite ?? 0;
4865
+ this.total.input += usage.input ?? 0;
4866
+ this.total.output += usage.output ?? 0;
4867
+ this.total.cacheRead += usage.cacheRead ?? 0;
4868
+ this.total.cacheWrite += usage.cacheWrite ?? 0;
4869
+ const price = this.priceLookup?.(e.subagentId);
4870
+ if (price) {
4871
+ const delta = (usage.input ?? 0) / 1e6 * (price.input ?? 0) + (usage.output ?? 0) / 1e6 * (price.output ?? 0) + (usage.cacheRead ?? 0) / 1e6 * (price.cacheRead ?? 0) + (usage.cacheWrite ?? 0) / 1e6 * (price.cacheWrite ?? 0);
4872
+ snap.cost += delta;
4873
+ this.total.cost += delta;
4874
+ }
4875
+ snap.lastEventAt = e.ts;
4876
+ }
4877
+ onToolExecuted(e) {
4878
+ const snap = this.ensure(e.subagentId);
4879
+ snap.toolCalls += 1;
4880
+ snap.lastEventAt = e.ts;
4881
+ }
4882
+ onIterationStarted(e) {
4883
+ const snap = this.ensure(e.subagentId);
4884
+ snap.iterations += 1;
4885
+ snap.lastEventAt = e.ts;
4886
+ }
4887
+ };
4888
+
4430
4889
  // src/defaults/transport/in-memory-transport.ts
4431
4890
  var InMemoryBridgeTransport = class {
4432
4891
  subs = /* @__PURE__ */ new Map();
@@ -4513,6 +4972,11 @@ var InMemoryAgentBridge = class {
4513
4972
  if (this.stopped) throw new Error("Bridge is stopped");
4514
4973
  const timeout = timeoutMs ?? this.timeoutMs;
4515
4974
  const correlationId = msg.id;
4975
+ if (this.pendingRequests.has(correlationId)) {
4976
+ throw new Error(
4977
+ `Bridge request id "${correlationId}" collides with an in-flight request \u2014 caller is reusing message ids`
4978
+ );
4979
+ }
4516
4980
  return new Promise((resolve4, reject) => {
4517
4981
  const timer = setTimeout(() => {
4518
4982
  this.pendingRequests.delete(correlationId);
@@ -4549,12 +5013,686 @@ function createMessage(type, from, payload, to) {
4549
5013
  };
4550
5014
  }
4551
5015
 
5016
+ // src/defaults/director-prompts.ts
5017
+ var DEFAULT_DIRECTOR_PREAMBLE = `You are the Director of a multi-agent fleet. You orchestrate worker
5018
+ subagents by spawning them, assigning tasks, awaiting completions, and
5019
+ rolling up their outputs into your next decision.
5020
+
5021
+ Core fleet tools available to you:
5022
+ - spawn_subagent \u2014 create a worker with a chosen provider / model / role
5023
+ - assign_task \u2014 hand a piece of work to a specific subagent
5024
+ - await_tasks \u2014 block until named task ids complete (parallel-safe)
5025
+ - ask_subagent \u2014 synchronously query a running subagent via the bridge
5026
+ - roll_up \u2014 aggregate finished tasks into a markdown/json summary
5027
+ - terminate_subagent \u2014 abort a stuck worker (use sparingly)
5028
+ - fleet_status \u2014 snapshot of all subagents and pending tasks
5029
+ - fleet_usage \u2014 token + cost breakdown per subagent and total
5030
+
5031
+ Working rules:
5032
+ 1. Decompose first. Before spawning, decide which sub-tasks are
5033
+ independent and can run in parallel. Sequential work doesn't need a
5034
+ subagent \u2014 do it yourself.
5035
+ 2. Match worker to job. Cheap/fast model for triage, capable model for
5036
+ synthesis. Different providers per sibling is allowed and encouraged.
5037
+ 3. Always pair an assign with an await. Don't fire-and-forget; you owe
5038
+ the user a single coherent answer at the end.
5039
+ 4. Roll up before deciding. After await_tasks resolves, call roll_up so
5040
+ the results are folded back into your context in a compact form.
5041
+ 5. Budget is real. Check fleet_usage periodically. If a subagent is
5042
+ thrashing, terminate it rather than letting cost climb silently.
5043
+ 6. Never claim a subagent's work as your own without verifying it. If a
5044
+ result looks wrong, ask_subagent for clarification before passing it
5045
+ to the user.`;
5046
+ var DEFAULT_SUBAGENT_BASELINE = `You are a subagent operating under a Director. You were spawned to handle
5047
+ a specific slice of a larger plan \u2014 do that slice well and report back.
5048
+
5049
+ Bridge contract:
5050
+ - You have a parent (the Director). You may call \`request\` on the
5051
+ parent bridge to ask a clarifying question. Use this sparingly; the
5052
+ parent is also working.
5053
+ - You MAY NOT request the parent's system prompt, tool list, or other
5054
+ subagents' context. Those are not yours to read.
5055
+ - Your final task output is what the Director sees. Be concise,
5056
+ structured, and self-contained \u2014 assume the Director will paste your
5057
+ output into its own context.`;
5058
+ function composeDirectorPrompt(parts = {}) {
5059
+ const sections = [];
5060
+ const preamble = parts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
5061
+ if (preamble && preamble.trim().length > 0) sections.push(preamble.trim());
5062
+ if (parts.rosterSummary && parts.rosterSummary.trim().length > 0) {
5063
+ sections.push(`Available roles you can spawn:
5064
+ ${parts.rosterSummary.trim()}`);
5065
+ }
5066
+ if (parts.basePrompt && parts.basePrompt.trim().length > 0) {
5067
+ sections.push(parts.basePrompt.trim());
5068
+ }
5069
+ return sections.join("\n\n");
5070
+ }
5071
+ function composeSubagentPrompt(parts = {}) {
5072
+ const sections = [];
5073
+ const baseline = parts.baseline ?? DEFAULT_SUBAGENT_BASELINE;
5074
+ if (baseline && baseline.trim().length > 0) sections.push(baseline.trim());
5075
+ if (parts.role && parts.role.trim().length > 0) {
5076
+ sections.push(`Role:
5077
+ ${parts.role.trim()}`);
5078
+ }
5079
+ if (parts.task && parts.task.trim().length > 0) {
5080
+ sections.push(`Task:
5081
+ ${parts.task.trim()}`);
5082
+ }
5083
+ if (parts.override && parts.override.trim().length > 0) {
5084
+ sections.push(parts.override.trim());
5085
+ }
5086
+ return sections.join("\n\n");
5087
+ }
5088
+ function rosterSummaryFromConfigs(roster) {
5089
+ const lines = [];
5090
+ for (const [roleId, cfg] of Object.entries(roster)) {
5091
+ const tag = cfg.provider && cfg.model ? ` (${cfg.provider}/${cfg.model})` : "";
5092
+ const headline = cfg.prompt ? (cfg.prompt.split("\n").find((l) => l.trim().length > 0) ?? "").trim().slice(0, 80) : "";
5093
+ const tail = headline ? ` \u2014 ${headline}` : "";
5094
+ lines.push(`- ${roleId}: ${cfg.name}${tag}${tail}`);
5095
+ }
5096
+ return lines.join("\n");
5097
+ }
5098
+
5099
+ // src/defaults/director.ts
5100
+ var Director = class {
5101
+ id;
5102
+ fleet;
5103
+ usage;
5104
+ /**
5105
+ * Director-side bridge endpoint. Subagents are wired to the same
5106
+ * in-memory transport so the director can `ask()` them synchronously
5107
+ * and they can `send()` progress back. Exposed so external code (e.g.
5108
+ * the TUI) can subscribe to inbound messages.
5109
+ */
5110
+ bridge;
5111
+ transport;
5112
+ coordinator;
5113
+ /** Resolves with the matching `TaskResult` the first time the
5114
+ * coordinator emits `task.completed` for a given task id. Each entry
5115
+ * is created lazily on first poll/await and cleared once consumed. */
5116
+ taskWaiters = /* @__PURE__ */ new Map();
5117
+ /** Cache of completed results in case the consumer asks AFTER the
5118
+ * coordinator already fired the event — `awaitTasks(['t-1'])` after
5119
+ * t-1 finished should resolve immediately, not hang. */
5120
+ completed = /* @__PURE__ */ new Map();
5121
+ /** Per-subagent provider/model metadata, captured at spawn time so the
5122
+ * FleetUsageAggregator's metaLookup can surface readable rows. */
5123
+ subagentMeta = /* @__PURE__ */ new Map();
5124
+ priceLookups = /* @__PURE__ */ new Map();
5125
+ /** Bridge endpoints we created per subagent (so we can `stop()` them
5126
+ * on shutdown and free transport subscriptions). */
5127
+ subagentBridges = /* @__PURE__ */ new Map();
5128
+ /** Tracks per-spawn config + assigned task ids for manifest writing. */
5129
+ manifestEntries = /* @__PURE__ */ new Map();
5130
+ manifestPath;
5131
+ roster;
5132
+ directorPreamble;
5133
+ subagentBaseline;
5134
+ constructor(opts) {
5135
+ this.id = opts.config.coordinatorId || randomUUID();
5136
+ this.manifestPath = opts.manifestPath;
5137
+ this.roster = opts.roster;
5138
+ this.directorPreamble = opts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
5139
+ this.subagentBaseline = opts.subagentBaseline ?? DEFAULT_SUBAGENT_BASELINE;
5140
+ this.transport = new InMemoryBridgeTransport();
5141
+ this.bridge = new InMemoryAgentBridge(
5142
+ { agentId: this.id, coordinatorId: this.id },
5143
+ this.transport
5144
+ );
5145
+ this.fleet = new FleetBus();
5146
+ this.usage = new FleetUsageAggregator(
5147
+ this.fleet,
5148
+ (id) => this.priceLookups.get(id),
5149
+ (id) => this.subagentMeta.get(id)
5150
+ );
5151
+ this.coordinator = new DefaultMultiAgentCoordinator(
5152
+ { ...opts.config, coordinatorId: this.id },
5153
+ { runner: opts.runner }
5154
+ );
5155
+ this.coordinator.on("task.completed", (payload) => {
5156
+ const r = payload.result;
5157
+ this.completed.set(r.taskId, r);
5158
+ const waiter = this.taskWaiters.get(r.taskId);
5159
+ if (waiter) {
5160
+ waiter.resolve(r);
5161
+ this.taskWaiters.delete(r.taskId);
5162
+ }
5163
+ });
5164
+ }
5165
+ /**
5166
+ * Spawn a subagent. Identical to the coordinator's `spawn()` but
5167
+ * captures provider/model metadata for the usage aggregator and
5168
+ * lets the FleetBus attach to the runner's EventBus when the task
5169
+ * actually runs (see `attachSubagentBus`).
5170
+ *
5171
+ * Caller-supplied `priceLookup` is optional but recommended — without
5172
+ * it the `cost` column in `usage.snapshot()` stays at 0.
5173
+ */
5174
+ async spawn(config, priceLookup) {
5175
+ const result = await this.coordinator.spawn(config);
5176
+ this.subagentMeta.set(result.subagentId, {
5177
+ provider: config.provider,
5178
+ model: config.model
5179
+ });
5180
+ if (priceLookup) this.priceLookups.set(result.subagentId, priceLookup);
5181
+ const subagentBridge = new InMemoryAgentBridge(
5182
+ { agentId: result.subagentId, coordinatorId: this.id },
5183
+ this.transport
5184
+ );
5185
+ this.coordinator.setSubagentBridge(result.subagentId, subagentBridge);
5186
+ this.subagentBridges.set(result.subagentId, subagentBridge);
5187
+ this.manifestEntries.set(result.subagentId, {
5188
+ subagentId: result.subagentId,
5189
+ name: config.name,
5190
+ role: config.role,
5191
+ provider: config.provider,
5192
+ model: config.model,
5193
+ taskIds: []
5194
+ });
5195
+ return result.subagentId;
5196
+ }
5197
+ /**
5198
+ * Synchronously ask a subagent something via the bridge. Sends a
5199
+ * `task` message addressed to the subagent and awaits a matching
5200
+ * reply (matched by message id). Subagent runners that handle these
5201
+ * requests subscribe to `ctx.bridge` and reply with a message whose
5202
+ * `id` equals the incoming request's id (see `InMemoryAgentBridge`'s
5203
+ * `request<T>` implementation).
5204
+ *
5205
+ * Returns the response payload directly (the bridge wrapper is
5206
+ * unwrapped for ergonomics). Times out after `timeoutMs` (default
5207
+ * matches the bridge's own default of 30s) — surface those rejections
5208
+ * to the caller as actionable errors instead of letting tools hang.
5209
+ */
5210
+ async ask(subagentId, payload, timeoutMs) {
5211
+ if (!this.subagentBridges.has(subagentId)) {
5212
+ throw new Error(
5213
+ `ask: unknown subagent "${subagentId}" (spawn() it first; current fleet: ${Array.from(this.subagentBridges.keys()).join(", ") || "(empty)"})`
5214
+ );
5215
+ }
5216
+ const msg = {
5217
+ id: randomUUID(),
5218
+ type: "task",
5219
+ from: this.id,
5220
+ to: subagentId,
5221
+ payload,
5222
+ timestamp: Date.now(),
5223
+ priority: "normal"
5224
+ };
5225
+ const reply = await this.bridge.request(msg, timeoutMs);
5226
+ return reply.payload;
5227
+ }
5228
+ /**
5229
+ * Read completed task results and format them as a structured text
5230
+ * block the director's LLM can paste into its own context. The
5231
+ * Director keeps every completed `TaskResult` in `completed` so this
5232
+ * is a pure read — no bridge round-trip, cheap to call.
5233
+ *
5234
+ * The returned string is intentionally markdown-flavored: headers per
5235
+ * subagent, a one-line meta row (iter / tools / ms), and the task's
5236
+ * result text. Pass `style: 'json'` for a programmatic shape instead
5237
+ * (useful when the director model is doing structured-output work).
5238
+ */
5239
+ rollUp(taskIds, style = "markdown") {
5240
+ const rows = taskIds.map((id) => this.completed.get(id)).filter(
5241
+ (r) => !!r
5242
+ );
5243
+ if (style === "json") {
5244
+ return JSON.stringify(
5245
+ rows.map((r) => ({
5246
+ taskId: r.taskId,
5247
+ subagentId: r.subagentId,
5248
+ status: r.status,
5249
+ iterations: r.iterations,
5250
+ toolCalls: r.toolCalls,
5251
+ durationMs: r.durationMs,
5252
+ result: r.result,
5253
+ error: r.error
5254
+ })),
5255
+ null,
5256
+ 2
5257
+ );
5258
+ }
5259
+ if (rows.length === 0) {
5260
+ return "_No completed tasks for the requested ids \u2014 try waiting first._";
5261
+ }
5262
+ const lines = [];
5263
+ for (const r of rows) {
5264
+ const meta = this.subagentMeta.get(r.subagentId);
5265
+ const tag = meta?.provider && meta?.model ? ` \xB7 ${meta.provider}/${meta.model}` : "";
5266
+ lines.push(`### ${r.subagentId}${tag}`);
5267
+ lines.push(
5268
+ `_${r.status} \u2014 ${r.iterations} iter \xB7 ${r.toolCalls} tools \xB7 ${r.durationMs}ms_`
5269
+ );
5270
+ lines.push("");
5271
+ if (r.error) lines.push(`**Error:** ${r.error}`);
5272
+ else if (typeof r.result === "string") lines.push(r.result);
5273
+ else if (r.result !== void 0) lines.push("```json\n" + JSON.stringify(r.result, null, 2) + "\n```");
5274
+ else lines.push("_(no output)_");
5275
+ lines.push("");
5276
+ }
5277
+ return lines.join("\n").trimEnd();
5278
+ }
5279
+ /**
5280
+ * Write the fleet manifest to `manifestPath`. Returns the path written
5281
+ * or null when no path was configured. Captures every spawn + its
5282
+ * assigned tasks — paired with per-subagent JSONLs, this is enough to
5283
+ * replay an entire director run.
5284
+ */
5285
+ async writeManifest() {
5286
+ if (!this.manifestPath) return null;
5287
+ const manifest = {
5288
+ directorRunId: this.id,
5289
+ writtenAt: (/* @__PURE__ */ new Date()).toISOString(),
5290
+ children: Array.from(this.manifestEntries.values()).map((e) => ({
5291
+ ...e,
5292
+ // Surface final status from `completed` when available — manifest
5293
+ // becomes much more useful for replay when it carries the
5294
+ // success/failure state.
5295
+ results: e.taskIds.map((tid) => {
5296
+ const r = this.completed.get(tid);
5297
+ return r ? {
5298
+ taskId: tid,
5299
+ status: r.status,
5300
+ iterations: r.iterations,
5301
+ toolCalls: r.toolCalls,
5302
+ durationMs: r.durationMs
5303
+ } : { taskId: tid, status: "pending" };
5304
+ })
5305
+ })),
5306
+ usage: this.usage.snapshot()
5307
+ };
5308
+ await fsp.mkdir(path2.dirname(this.manifestPath), { recursive: true });
5309
+ await fsp.writeFile(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
5310
+ return this.manifestPath;
5311
+ }
5312
+ /**
5313
+ * Tear down the director: stop every subagent, close every bridge
5314
+ * endpoint, and (when configured) write the final manifest. Idempotent
5315
+ * — calling shutdown twice is a no-op on the second invocation.
5316
+ */
5317
+ async shutdown() {
5318
+ await this.coordinator.stopAll();
5319
+ for (const b of this.subagentBridges.values()) {
5320
+ await b.stop().catch(() => void 0);
5321
+ }
5322
+ this.subagentBridges.clear();
5323
+ await this.bridge.stop().catch(() => void 0);
5324
+ if (this.manifestPath) await this.writeManifest().catch(() => void 0);
5325
+ }
5326
+ /**
5327
+ * Hand a task to the coordinator. Returns the assigned task id so
5328
+ * callers can wait on it via `awaitTasks([id])`. The coordinator's
5329
+ * concurrency limit applies — the task may queue before running.
5330
+ */
5331
+ async assign(task) {
5332
+ const taskWithId = task.id ? task : { ...task, id: randomUUID() };
5333
+ if (task.subagentId) {
5334
+ const entry = this.manifestEntries.get(task.subagentId);
5335
+ if (entry) entry.taskIds.push(taskWithId.id);
5336
+ }
5337
+ await this.coordinator.assign(taskWithId);
5338
+ return taskWithId.id;
5339
+ }
5340
+ /**
5341
+ * Block until every task id resolves. Returns results in the same
5342
+ * order as the input. If any task hasn't completed by the time this
5343
+ * is called, the promise hangs until it does — pair with a timeout
5344
+ * at the caller if that's a concern. Resolves immediately for ids
5345
+ * whose results were already cached.
5346
+ */
5347
+ awaitTasks(taskIds) {
5348
+ return Promise.all(taskIds.map((id) => {
5349
+ const cached = this.completed.get(id);
5350
+ if (cached) return cached;
5351
+ const existing = this.taskWaiters.get(id);
5352
+ if (existing) return existing.promise;
5353
+ let resolve4;
5354
+ const promise = new Promise((res) => {
5355
+ resolve4 = res;
5356
+ });
5357
+ this.taskWaiters.set(id, { promise, resolve: resolve4 });
5358
+ return promise;
5359
+ }));
5360
+ }
5361
+ async terminate(subagentId) {
5362
+ await this.coordinator.stop(subagentId);
5363
+ }
5364
+ async terminateAll() {
5365
+ await this.coordinator.stopAll();
5366
+ }
5367
+ status() {
5368
+ return this.coordinator.getStatus();
5369
+ }
5370
+ /**
5371
+ * Subscribe to coordinator events. Currently only `task.completed` is
5372
+ * exposed (the others are internal lifecycle). Returns an unsubscribe
5373
+ * function. External callers (e.g. the CLI's `MultiAgentHost`) use this
5374
+ * to drive their own pending/results tracking without poking the
5375
+ * coordinator directly.
5376
+ */
5377
+ on(event, handler) {
5378
+ this.coordinator.on(event, handler);
5379
+ return () => {
5380
+ this.coordinator.off(event, handler);
5381
+ };
5382
+ }
5383
+ /**
5384
+ * Snapshot of every task that has resolved (success, failed, timeout,
5385
+ * stopped) since the director started. Returned in completion order
5386
+ * via the internal map's iteration order. Used by `/fleet status` to
5387
+ * paint the completed table without reaching into private state.
5388
+ */
5389
+ completedResults() {
5390
+ return Array.from(this.completed.values());
5391
+ }
5392
+ snapshot() {
5393
+ return this.usage.snapshot();
5394
+ }
5395
+ /**
5396
+ * Compose the leader/director-agent system prompt: fleet preamble +
5397
+ * (optional) roster summary + user base prompt. Pass the result to your
5398
+ * leader Agent's `ctx.systemPrompt` when constructing it.
5399
+ *
5400
+ * `basePrompt` defaults to `config.leaderSystemPrompt` so callers can
5401
+ * use the no-arg form when the multi-agent config already carries it.
5402
+ */
5403
+ leaderSystemPrompt(basePrompt) {
5404
+ return composeDirectorPrompt({
5405
+ basePrompt: basePrompt ?? this.coordinator.config.leaderSystemPrompt,
5406
+ directorPreamble: this.directorPreamble,
5407
+ rosterSummary: this.roster ? rosterSummaryFromConfigs(this.roster) : void 0
5408
+ });
5409
+ }
5410
+ /**
5411
+ * Compose a subagent's system prompt for a given `SubagentConfig`:
5412
+ * baseline + role + task + per-spawn override. Returned by value — does
5413
+ * not mutate the config. Factories (the user-supplied `AgentFactory`)
5414
+ * should call this when building each subagent's Agent so the bridge
5415
+ * contract, role context, and override are all surfaced.
5416
+ *
5417
+ * When `taskBrief` is omitted the Task section is dropped. Pass the
5418
+ * actual task description here to reinforce it in the system prompt
5419
+ * (the runner already passes it as user input — duplicating in the
5420
+ * system prompt is optional but improves anchoring on small models).
5421
+ */
5422
+ subagentSystemPrompt(config, taskBrief) {
5423
+ return composeSubagentPrompt({
5424
+ baseline: this.subagentBaseline,
5425
+ role: config.prompt,
5426
+ task: taskBrief,
5427
+ override: config.systemPromptOverride
5428
+ });
5429
+ }
5430
+ /**
5431
+ * Build the tool set the LLM-driven director uses to orchestrate.
5432
+ * Returns an array of `Tool` definitions; register these on the
5433
+ * director's `Agent` to expose `spawn_subagent`, `assign_task`, etc.
5434
+ * Each tool's `execute()` delegates straight to the matching method
5435
+ * above.
5436
+ *
5437
+ * Tools all carry `permission: 'auto'` — the *user* has already
5438
+ * approved running the director when they kicked off the run, so
5439
+ * gating individual orchestration calls behind a confirm prompt
5440
+ * would just be noise. The actual subagent tools they spawn are
5441
+ * still permission-checked normally.
5442
+ */
5443
+ tools(roster) {
5444
+ const t2 = [
5445
+ makeSpawnTool(this, roster),
5446
+ makeAssignTool(this),
5447
+ makeAwaitTasksTool(this),
5448
+ makeAskTool(this),
5449
+ makeRollUpTool(this),
5450
+ makeTerminateTool(this),
5451
+ makeFleetStatusTool(this),
5452
+ makeFleetUsageTool(this)
5453
+ ];
5454
+ return t2;
5455
+ }
5456
+ };
5457
+ function makeSpawnTool(director, roster) {
5458
+ const inputSchema = {
5459
+ type: "object",
5460
+ properties: {
5461
+ role: { type: "string", description: "Roster role id (preferred). When set, the spawn uses the matching config from the roster and ignores other fields." },
5462
+ name: { type: "string", description: "Display name for the subagent. Required when not using roster." },
5463
+ provider: { type: "string", description: 'Provider id (e.g. "anthropic", "openai"). Defaults to the leader provider when omitted.' },
5464
+ model: { type: "string", description: "Model id within the provider. Defaults to the leader model when omitted." },
5465
+ systemPromptOverride: { type: "string", description: "Extra prompt text appended after the role-base prompt." },
5466
+ maxIterations: { type: "number" },
5467
+ maxToolCalls: { type: "number" },
5468
+ maxCostUsd: { type: "number" }
5469
+ },
5470
+ required: []
5471
+ };
5472
+ return {
5473
+ name: "spawn_subagent",
5474
+ description: "Create a new subagent under this director. Returns the subagent id. Use this when you need a worker with a specific provider, model, or role to handle a piece of the plan.",
5475
+ usageHint: "Either pass `role` (matches the roster) OR pass `name` + optional `provider`/`model`. Returns `{ subagentId }`.",
5476
+ permission: "auto",
5477
+ mutating: false,
5478
+ inputSchema,
5479
+ async execute(input) {
5480
+ const i = input ?? {};
5481
+ const role = typeof i.role === "string" ? i.role : void 0;
5482
+ const base = role && roster ? roster[role] : void 0;
5483
+ if (role && !base) {
5484
+ return { error: `unknown role "${role}". roster has: ${roster ? Object.keys(roster).join(", ") : "(empty)"}` };
5485
+ }
5486
+ const cfg = {
5487
+ ...base ?? { name: i.name ?? "subagent" }
5488
+ };
5489
+ if (typeof i.name === "string") cfg.name = i.name;
5490
+ if (typeof i.provider === "string") cfg.provider = i.provider;
5491
+ if (typeof i.model === "string") cfg.model = i.model;
5492
+ if (typeof i.systemPromptOverride === "string") cfg.systemPromptOverride = i.systemPromptOverride;
5493
+ if (typeof i.maxIterations === "number") cfg.maxIterations = i.maxIterations;
5494
+ if (typeof i.maxToolCalls === "number") cfg.maxToolCalls = i.maxToolCalls;
5495
+ if (typeof i.maxCostUsd === "number") cfg.maxCostUsd = i.maxCostUsd;
5496
+ const subagentId = await director.spawn(cfg);
5497
+ return { subagentId, provider: cfg.provider, model: cfg.model, name: cfg.name };
5498
+ }
5499
+ };
5500
+ }
5501
+ function makeAssignTool(director) {
5502
+ const inputSchema = {
5503
+ type: "object",
5504
+ properties: {
5505
+ subagentId: { type: "string", description: "Target subagent id. Required." },
5506
+ description: { type: "string", description: "The task in natural language \u2014 what you want this subagent to do." },
5507
+ maxToolCalls: { type: "number", description: "Optional per-task tool-call budget override." },
5508
+ timeoutMs: { type: "number", description: "Optional per-task timeout in ms." }
5509
+ },
5510
+ required: ["subagentId", "description"]
5511
+ };
5512
+ return {
5513
+ name: "assign_task",
5514
+ description: "Hand a task to a previously spawned subagent. Returns the task id \u2014 pass it to `await_tasks` to block on completion.",
5515
+ permission: "auto",
5516
+ mutating: false,
5517
+ inputSchema,
5518
+ async execute(input) {
5519
+ const i = input;
5520
+ const task = {
5521
+ id: randomUUID(),
5522
+ description: i.description,
5523
+ subagentId: i.subagentId,
5524
+ maxToolCalls: i.maxToolCalls,
5525
+ timeoutMs: i.timeoutMs
5526
+ };
5527
+ const taskId = await director.assign(task);
5528
+ return { taskId, subagentId: i.subagentId };
5529
+ }
5530
+ };
5531
+ }
5532
+ function makeAwaitTasksTool(director) {
5533
+ const inputSchema = {
5534
+ type: "object",
5535
+ properties: {
5536
+ taskIds: {
5537
+ type: "array",
5538
+ items: { type: "string" },
5539
+ description: "One or more task ids returned by `assign_task`. The call blocks until every id resolves."
5540
+ }
5541
+ },
5542
+ required: ["taskIds"]
5543
+ };
5544
+ return {
5545
+ name: "await_tasks",
5546
+ description: "Block until every named task completes. Returns the array of TaskResult \u2014 use this to gather subagent output before deciding the next step.",
5547
+ permission: "auto",
5548
+ mutating: false,
5549
+ inputSchema,
5550
+ async execute(input) {
5551
+ const i = input;
5552
+ const results = await director.awaitTasks(i.taskIds);
5553
+ return { results };
5554
+ }
5555
+ };
5556
+ }
5557
+ function makeAskTool(director) {
5558
+ const inputSchema = {
5559
+ type: "object",
5560
+ properties: {
5561
+ subagentId: { type: "string", description: "Subagent to ask. Must be a previously spawned id." },
5562
+ question: { type: "string", description: "The question or instruction. Sent as the bridge message payload." },
5563
+ timeoutMs: { type: "number", description: "Optional timeout in ms (default 30s)." }
5564
+ },
5565
+ required: ["subagentId", "question"]
5566
+ };
5567
+ return {
5568
+ name: "ask_subagent",
5569
+ description: "Synchronously ask a subagent a question. Blocks until the subagent replies via the bridge (or the timeout fires). Use this when you need a one-shot answer without spawning a fresh task.",
5570
+ permission: "auto",
5571
+ mutating: false,
5572
+ inputSchema,
5573
+ async execute(input) {
5574
+ const i = input;
5575
+ try {
5576
+ const answer = await director.ask(i.subagentId, { question: i.question }, i.timeoutMs);
5577
+ return { ok: true, answer };
5578
+ } catch (err) {
5579
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
5580
+ }
5581
+ }
5582
+ };
5583
+ }
5584
+ function makeRollUpTool(director) {
5585
+ const inputSchema = {
5586
+ type: "object",
5587
+ properties: {
5588
+ taskIds: {
5589
+ type: "array",
5590
+ items: { type: "string" },
5591
+ description: "Completed task ids to aggregate. Pass the ids returned by previous `assign_task` calls."
5592
+ },
5593
+ style: {
5594
+ type: "string",
5595
+ enum: ["markdown", "json"],
5596
+ description: "Output flavor \u2014 markdown (default) for in-prompt summarization, json for structured downstream processing."
5597
+ }
5598
+ },
5599
+ required: ["taskIds"]
5600
+ };
5601
+ return {
5602
+ name: "roll_up",
5603
+ description: "Aggregate completed task results into a single formatted summary. Use this after `await_tasks` to fold subagent outputs back into the director's context before deciding the next step.",
5604
+ permission: "auto",
5605
+ mutating: false,
5606
+ inputSchema,
5607
+ async execute(input) {
5608
+ const i = input;
5609
+ const summary = director.rollUp(i.taskIds, i.style ?? "markdown");
5610
+ return { summary, count: i.taskIds.length };
5611
+ }
5612
+ };
5613
+ }
5614
+ function makeTerminateTool(director) {
5615
+ const inputSchema = {
5616
+ type: "object",
5617
+ properties: {
5618
+ subagentId: { type: "string", description: "Subagent to abort." }
5619
+ },
5620
+ required: ["subagentId"]
5621
+ };
5622
+ return {
5623
+ name: "terminate_subagent",
5624
+ description: 'Forcibly abort a subagent. Use sparingly \u2014 prefer waiting on the natural budget to expire. The current task (if any) ends with status "stopped".',
5625
+ permission: "auto",
5626
+ mutating: true,
5627
+ inputSchema,
5628
+ async execute(input) {
5629
+ const i = input;
5630
+ await director.terminate(i.subagentId);
5631
+ return { ok: true };
5632
+ }
5633
+ };
5634
+ }
5635
+ function makeFleetStatusTool(director) {
5636
+ return {
5637
+ name: "fleet_status",
5638
+ description: "Snapshot of the fleet \u2014 every subagent's current status, pending vs. completed task counts, and the running total iteration count. Cheap; call freely.",
5639
+ permission: "auto",
5640
+ mutating: false,
5641
+ inputSchema: { type: "object", properties: {}, required: [] },
5642
+ async execute() {
5643
+ return director.status();
5644
+ }
5645
+ };
5646
+ }
5647
+ function makeFleetUsageTool(director) {
5648
+ return {
5649
+ name: "fleet_usage",
5650
+ description: "Token + cost breakdown across the fleet, per-subagent and totals. Use this to reason about which workers to assign costly tasks to or when to wrap up to stay within budget.",
5651
+ permission: "auto",
5652
+ mutating: false,
5653
+ inputSchema: { type: "object", properties: {}, required: [] },
5654
+ async execute() {
5655
+ return director.snapshot();
5656
+ }
5657
+ };
5658
+ }
5659
+ function makeDirectorSessionFactory(opts) {
5660
+ const runId = opts.directorRunId ?? `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-director`;
5661
+ let store;
5662
+ let dir;
5663
+ if (opts.store) {
5664
+ store = opts.store;
5665
+ dir = opts.sessionsRoot ? path2.join(opts.sessionsRoot, runId) : "(caller-managed)";
5666
+ } else if (opts.sessionsRoot) {
5667
+ dir = path2.join(opts.sessionsRoot, runId);
5668
+ store = new DefaultSessionStore({ dir });
5669
+ } else {
5670
+ throw new Error(
5671
+ "makeDirectorSessionFactory requires either `store` or `sessionsRoot`"
5672
+ );
5673
+ }
5674
+ return {
5675
+ dir,
5676
+ directorRunId: runId,
5677
+ async createSubagentSession({ subagentId, provider, model, title }) {
5678
+ return store.create({
5679
+ id: subagentId,
5680
+ title: title ?? subagentId,
5681
+ provider: provider ?? "unknown",
5682
+ model: model ?? "unknown"
5683
+ });
5684
+ }
5685
+ };
5686
+ }
5687
+
4552
5688
  // src/defaults/autonomous-runner.ts
4553
5689
  var DoneConditionChecker = class {
4554
5690
  constructor(condition) {
4555
5691
  this.condition = condition;
5692
+ this.compiledRegex = condition.type === "output_match" && condition.pattern ? new RegExp(condition.pattern) : null;
4556
5693
  }
4557
5694
  condition;
5695
+ compiledRegex;
4558
5696
  check(state) {
4559
5697
  switch (this.condition.type) {
4560
5698
  case "iterations":
@@ -4568,11 +5706,8 @@ var DoneConditionChecker = class {
4568
5706
  }
4569
5707
  break;
4570
5708
  case "output_match":
4571
- if (this.condition.pattern && state.lastOutput) {
4572
- const regex = new RegExp(this.condition.pattern);
4573
- if (regex.test(state.lastOutput)) {
4574
- return { done: true, reason: `output matched pattern "${this.condition.pattern}"`, ...state };
4575
- }
5709
+ if (this.compiledRegex && state.lastOutput && this.compiledRegex.test(state.lastOutput)) {
5710
+ return { done: true, reason: `output matched pattern "${this.condition.pattern}"`, ...state };
4576
5711
  }
4577
5712
  break;
4578
5713
  }
@@ -4591,6 +5726,16 @@ var AutonomousRunner = class {
4591
5726
  stopped = false;
4592
5727
  doneChecker;
4593
5728
  async run() {
5729
+ const offToolExecuted = this.opts.agent.events?.on?.("tool.executed", () => {
5730
+ this.toolCalls++;
5731
+ });
5732
+ try {
5733
+ return await this.runLoop();
5734
+ } finally {
5735
+ offToolExecuted?.();
5736
+ }
5737
+ }
5738
+ async runLoop() {
4594
5739
  while (!this.stopped) {
4595
5740
  const check = this.doneChecker.check({
4596
5741
  iterations: this.iterations,
@@ -4617,7 +5762,6 @@ var AutonomousRunner = class {
4617
5762
  );
4618
5763
  this.iterations++;
4619
5764
  this.lastOutput = result.finalText;
4620
- this.toolCalls++;
4621
5765
  if (result.status === "failed" || result.status === "aborted") {
4622
5766
  const failedResult = {
4623
5767
  status: result.status,
@@ -4629,7 +5773,8 @@ var AutonomousRunner = class {
4629
5773
  return failedResult;
4630
5774
  }
4631
5775
  } catch (e) {
4632
- if (e.message.includes("timeout")) {
5776
+ const msg = e instanceof Error ? e.message : String(e);
5777
+ if (msg.includes("timeout")) {
4633
5778
  const timeoutResult = {
4634
5779
  status: "failed",
4635
5780
  error: toWrongStackError(e),
@@ -4658,14 +5803,11 @@ var AutonomousRunner = class {
4658
5803
 
4659
5804
  // src/defaults/spec-parser.ts
4660
5805
  var SpecParser = class {
4661
- constructor(opts = {}) {
4662
- this.opts = opts;
4663
- }
4664
- opts;
4665
5806
  parse(content) {
4666
5807
  const lines = content.split("\n");
4667
5808
  const sections = this.extractSections(lines);
4668
5809
  const requirements = this.extractRequirements(lines);
5810
+ const now = Date.now();
4669
5811
  return {
4670
5812
  id: crypto.randomUUID(),
4671
5813
  title: this.extractTitle(lines),
@@ -4674,8 +5816,8 @@ var SpecParser = class {
4674
5816
  overview: this.extractOverview(lines),
4675
5817
  sections,
4676
5818
  requirements,
4677
- createdAt: Date.now(),
4678
- updatedAt: Date.now()
5819
+ createdAt: now,
5820
+ updatedAt: now
4679
5821
  };
4680
5822
  }
4681
5823
  extractTitle(lines) {
@@ -4767,20 +5909,13 @@ var SpecParser = class {
4767
5909
  parseRequirementLine(line, id) {
4768
5910
  const trimmed = line.trim();
4769
5911
  if (!trimmed || trimmed.startsWith("#")) return null;
4770
- const typeMap = {
4771
- "functional": "functional",
4772
- "non-functional": "non-functional",
4773
- "security": "security",
4774
- "performance": "performance",
4775
- "ux": "ux"
4776
- };
5912
+ const lower = trimmed.toLowerCase();
5913
+ const types = ["functional", "non-functional", "security", "performance", "ux"];
4777
5914
  let type = "functional";
4778
- let priority = "medium";
4779
- for (const [key, val] of Object.entries(typeMap)) {
4780
- if (trimmed.toLowerCase().includes(`[${key}]`)) {
4781
- type = val;
4782
- }
5915
+ for (const t2 of types) {
5916
+ if (lower.includes(`[${t2}]`)) type = t2;
4783
5917
  }
5918
+ let priority = "medium";
4784
5919
  if (trimmed.includes("[critical]") || trimmed.includes("[prio:high]")) {
4785
5920
  priority = "critical";
4786
5921
  } else if (trimmed.includes("[high]")) {
@@ -4870,9 +6005,10 @@ var SpecParser = class {
4870
6005
  warnings.push({ path: `requirement.${req.id}`, message: "No acceptance criteria defined" });
4871
6006
  }
4872
6007
  }
6008
+ const reqIds = new Set(spec.requirements.map((r) => r.id));
4873
6009
  const blockedByIds = new Set(spec.requirements.flatMap((r) => r.blockedBy ?? []));
4874
6010
  for (const id of blockedByIds) {
4875
- if (!spec.requirements.find((r) => r.id === id)) {
6011
+ if (!reqIds.has(id)) {
4876
6012
  errors.push({ path: "requirements", message: `BlockedBy references non-existent requirement: ${id}` });
4877
6013
  }
4878
6014
  }
@@ -4902,25 +6038,21 @@ var TaskGenerator = class {
4902
6038
  status: "pending"
4903
6039
  });
4904
6040
  }
4905
- const criticalReqs = spec.requirements.filter((r) => r.priority === "critical");
4906
- const highReqs = spec.requirements.filter((r) => r.priority === "high");
4907
- const mediumReqs = spec.requirements.filter((r) => r.priority === "medium");
4908
- const lowReqs = spec.requirements.filter((r) => r.priority === "low");
4909
- for (const req of criticalReqs) {
4910
- const task = this.createTaskFromRequirement(req, spec.title);
4911
- this.opts.taskTracker.addNode(task);
4912
- }
4913
- for (const req of highReqs) {
4914
- const task = this.createTaskFromRequirement(req, spec.title);
4915
- this.opts.taskTracker.addNode(task);
4916
- }
4917
- for (const req of mediumReqs) {
4918
- const task = this.createTaskFromRequirement(req, spec.title);
4919
- this.opts.taskTracker.addNode(task);
6041
+ const byPriority = {
6042
+ critical: [],
6043
+ high: [],
6044
+ medium: [],
6045
+ low: []
6046
+ };
6047
+ for (const req of spec.requirements) {
6048
+ const bucket = byPriority[req.priority] ?? byPriority.medium;
6049
+ bucket.push(req);
4920
6050
  }
4921
- for (const req of lowReqs) {
4922
- const task = this.createTaskFromRequirement(req, spec.title);
4923
- this.opts.taskTracker.addNode(task);
6051
+ const order = ["critical", "high", "medium", "low"];
6052
+ for (const p of order) {
6053
+ for (const req of byPriority[p]) {
6054
+ this.opts.taskTracker.addNode(this.createTaskFromRequirement(req));
6055
+ }
4924
6056
  }
4925
6057
  if (spec.apiEndpoints && spec.apiEndpoints.length > 0) {
4926
6058
  const apiParent = this.opts.taskTracker.addNode({
@@ -4954,17 +6086,15 @@ var TaskGenerator = class {
4954
6086
  });
4955
6087
  return graph;
4956
6088
  }
4957
- createTaskFromRequirement(req, specTitle) {
4958
- const type = this.mapRequirementType(req.type);
4959
- const tags = [req.type, req.priority];
6089
+ createTaskFromRequirement(req) {
4960
6090
  return {
4961
6091
  title: req.description,
4962
- description: this.buildDescription(req, specTitle),
4963
- type,
4964
- priority: this.mapPriority(req.priority),
6092
+ description: this.buildDescription(req),
6093
+ type: this.mapRequirementType(req.type),
6094
+ priority: req.priority,
4965
6095
  status: "pending",
4966
6096
  specRequirementId: req.id,
4967
- tags,
6097
+ tags: [req.type, req.priority],
4968
6098
  estimateHours: this.estimateHours(req)
4969
6099
  };
4970
6100
  }
@@ -4979,7 +6109,7 @@ var TaskGenerator = class {
4979
6109
  estimateHours: this.estimateForEndpoint(endpoint)
4980
6110
  };
4981
6111
  }
4982
- buildDescription(req, specTitle) {
6112
+ buildDescription(req) {
4983
6113
  const lines = [
4984
6114
  req.description,
4985
6115
  "",
@@ -5013,20 +6143,6 @@ var TaskGenerator = class {
5013
6143
  return "feature";
5014
6144
  }
5015
6145
  }
5016
- mapPriority(priority) {
5017
- switch (priority) {
5018
- case "critical":
5019
- return "critical";
5020
- case "high":
5021
- return "high";
5022
- case "medium":
5023
- return "medium";
5024
- case "low":
5025
- return "low";
5026
- default:
5027
- return "medium";
5028
- }
5029
- }
5030
6146
  estimateHours(req) {
5031
6147
  switch (req.priority) {
5032
6148
  case "critical":
@@ -5136,40 +6252,40 @@ var TaskTracker = class {
5136
6252
  this.graph.rootNodes.push(newNode.id);
5137
6253
  }
5138
6254
  this.graph.updatedAt = now;
5139
- this.opts.store.saveGraph(this.graph);
6255
+ this.persist();
5140
6256
  return newNode;
5141
6257
  }
5142
6258
  addEdge(from, to, type = "depends_on") {
5143
6259
  if (!this.graph) throw new Error("No graph loaded");
5144
- const edge = {
6260
+ this.graph.edges.push({
5145
6261
  id: crypto.randomUUID(),
5146
6262
  from,
5147
6263
  to,
5148
6264
  type
5149
- };
5150
- this.graph.edges.push(edge);
6265
+ });
5151
6266
  this.graph.updatedAt = Date.now();
5152
- this.opts.store.saveGraph(this.graph);
6267
+ this.persist();
5153
6268
  }
5154
6269
  updateNodeStatus(id, status, reason) {
5155
6270
  if (!this.graph) throw new Error("No graph loaded");
5156
6271
  const node = this.graph.nodes.get(id);
5157
6272
  if (!node) throw new Error(`Node ${id} not found`);
5158
6273
  const from = node.status;
6274
+ const now = Date.now();
5159
6275
  node.status = status;
5160
- node.updatedAt = Date.now();
6276
+ node.updatedAt = now;
5161
6277
  if (status === "completed") {
5162
- node.completedAt = Date.now();
6278
+ node.completedAt = now;
5163
6279
  }
5164
- this.transitions.push({ from, to: status, timestamp: Date.now(), reason });
6280
+ this.transitions.push({ from, to: status, timestamp: now, reason });
5165
6281
  if (status === "completed") {
5166
6282
  this.unblockDependents(id);
5167
6283
  }
5168
6284
  if (status === "in_progress") {
5169
6285
  this.checkAndBlockIfNeeded(id);
5170
6286
  }
5171
- this.graph.updatedAt = Date.now();
5172
- this.opts.store.saveGraph(this.graph);
6287
+ this.graph.updatedAt = now;
6288
+ this.persist();
5173
6289
  }
5174
6290
  getNode(id) {
5175
6291
  return this.graph?.nodes.get(id);
@@ -5190,9 +6306,7 @@ var TaskTracker = class {
5190
6306
  }
5191
6307
  if (sort) {
5192
6308
  nodes.sort((a, b) => {
5193
- const aVal = a[sort.field] ?? "";
5194
- const bVal = b[sort.field] ?? "";
5195
- const cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
6309
+ const cmp = compareByField(a, b, sort.field);
5196
6310
  return sort.direction === "asc" ? cmp : -cmp;
5197
6311
  });
5198
6312
  }
@@ -5271,7 +6385,47 @@ var TaskTracker = class {
5271
6385
  }
5272
6386
  }
5273
6387
  }
6388
+ /**
6389
+ * Fire-and-forget persistence with attached error handler.
6390
+ * Synchronous mutators (addNode/addEdge/updateNodeStatus) use this to
6391
+ * avoid forcing an async cascade through every caller; if the store
6392
+ * rejects, the configured `onPersistError` is invoked so failures are
6393
+ * surfaced instead of swallowed by an unhandled promise rejection.
6394
+ */
6395
+ persist() {
6396
+ if (!this.graph) return;
6397
+ this.opts.store.saveGraph(this.graph).catch((err) => {
6398
+ if (this.opts.onPersistError) this.opts.onPersistError(err);
6399
+ else console.warn("[task-tracker] saveGraph failed:", err instanceof Error ? err.message : String(err));
6400
+ });
6401
+ }
6402
+ };
6403
+ var PRIORITY_RANK = {
6404
+ critical: 0,
6405
+ high: 1,
6406
+ medium: 2,
6407
+ low: 3
5274
6408
  };
6409
+ var STATUS_RANK = {
6410
+ in_progress: 0,
6411
+ pending: 1,
6412
+ review: 2,
6413
+ blocked: 3,
6414
+ failed: 4,
6415
+ completed: 5
6416
+ };
6417
+ function compareByField(a, b, field) {
6418
+ switch (field) {
6419
+ case "priority":
6420
+ return PRIORITY_RANK[a.priority] - PRIORITY_RANK[b.priority];
6421
+ case "status":
6422
+ return STATUS_RANK[a.status] - STATUS_RANK[b.status];
6423
+ case "createdAt":
6424
+ return a.createdAt - b.createdAt;
6425
+ case "updatedAt":
6426
+ return a.updatedAt - b.updatedAt;
6427
+ }
6428
+ }
5275
6429
 
5276
6430
  // src/defaults/task-flow.ts
5277
6431
  var TaskFlow = class {
@@ -5323,9 +6477,10 @@ var TaskFlow = class {
5323
6477
  const task = batch[i];
5324
6478
  if (!result || !task) continue;
5325
6479
  if (result.status === "rejected") {
5326
- this.opts.tracker.updateNodeStatus(task.id, "failed", result.reason?.message);
5327
- this.emit("task.failed", { taskId: task.id, error: result.reason?.message ?? "unknown" });
5328
- ctx.onTaskFail?.(task, result.reason);
6480
+ const reason = result.reason;
6481
+ this.opts.tracker.updateNodeStatus(task.id, "failed", reason?.message);
6482
+ this.emit("task.failed", { taskId: task.id, error: reason?.message ?? "unknown" });
6483
+ ctx.onTaskFail?.(task, reason);
5329
6484
  } else {
5330
6485
  this.opts.tracker.updateNodeStatus(task.id, "completed");
5331
6486
  this.emit("task.completed", { taskId: task.id, result: result.value });
@@ -5613,7 +6768,7 @@ var ToolExecutor = class {
5613
6768
  return { result, tool, durationMs: Date.now() - start };
5614
6769
  }
5615
6770
  } else {
5616
- const suggestedPattern = this.subjectFor(tool.name, use.input) ?? tool.name;
6771
+ const suggestedPattern = this.subjectFor(tool.name, use.input, tool.subjectKey) ?? tool.name;
5617
6772
  const pending = { type: "tool_confirm_pending", toolUseId: use.id, toolName: tool.name, input: use.input, suggestedPattern };
5618
6773
  return { result: pending, tool, durationMs: Date.now() - start };
5619
6774
  }
@@ -5645,15 +6800,31 @@ var ToolExecutor = class {
5645
6800
  span?.end();
5646
6801
  }
5647
6802
  };
6803
+ const safeRun = async (use) => {
6804
+ try {
6805
+ return await runOne(use);
6806
+ } catch (err) {
6807
+ const msg = err instanceof Error ? err.message : String(err);
6808
+ const scrubbed = this.opts.secretScrubber.scrub(msg);
6809
+ const result = {
6810
+ type: "tool_result",
6811
+ tool_use_id: use.id,
6812
+ content: `Tool "${use.name}" execution failed: ${scrubbed}`,
6813
+ is_error: true
6814
+ };
6815
+ budget = this.decrementBudget(result, budget);
6816
+ return { result, tool: this.registry.get(use.name), durationMs: 0 };
6817
+ }
6818
+ };
5648
6819
  if (strategy === "sequential") {
5649
6820
  const outputs = [];
5650
6821
  for (const use of toolUses) {
5651
- if (use) outputs.push(await runOne(use));
6822
+ if (use) outputs.push(await safeRun(use));
5652
6823
  }
5653
6824
  return { outputs, remainingBudget: budget };
5654
6825
  }
5655
6826
  if (strategy === "parallel") {
5656
- const outputs = await Promise.all(toolUses.map((use) => runOne(use)));
6827
+ const outputs = await Promise.all(toolUses.map((use) => safeRun(use)));
5657
6828
  return { outputs, remainingBudget: budget };
5658
6829
  }
5659
6830
  const nonMutating = [];
@@ -5664,10 +6835,10 @@ var ToolExecutor = class {
5664
6835
  if (tool?.mutating) mutating.push(use);
5665
6836
  else nonMutating.push(use);
5666
6837
  }
5667
- const firstPass = await Promise.all(nonMutating.map((use) => runOne(use)));
6838
+ const firstPass = await Promise.all(nonMutating.map((use) => safeRun(use)));
5668
6839
  const secondPass = [];
5669
6840
  for (const use of mutating) {
5670
- secondPass.push(await runOne(use));
6841
+ secondPass.push(await safeRun(use));
5671
6842
  }
5672
6843
  return {
5673
6844
  outputs: [...firstPass, ...secondPass],
@@ -5702,7 +6873,8 @@ var ToolExecutor = class {
5702
6873
  }
5703
6874
  async runWithTimeout(tool, input, parentSignal, ctx, toolUseId) {
5704
6875
  if (parentSignal.aborted) {
5705
- throw parentSignal.reason instanceof Error ? parentSignal.reason : new Error(typeof parentSignal.reason === "string" ? parentSignal.reason : "aborted");
6876
+ if (parentSignal.reason instanceof Error) throw parentSignal.reason;
6877
+ throw new Error(typeof parentSignal.reason === "string" ? parentSignal.reason : "aborted");
5706
6878
  }
5707
6879
  const timeoutMs = tool.timeoutMs ?? this.iterationTimeoutMs;
5708
6880
  const ctrl = new AbortController();
@@ -5771,16 +6943,23 @@ var ToolExecutor = class {
5771
6943
  * Matches the logic in DefaultPermissionPolicy so the TUI shows the
5772
6944
  * same subject that the trust file would use.
5773
6945
  */
5774
- subjectFor(toolName, input) {
6946
+ subjectFor(toolName, input, subjectKey) {
5775
6947
  if (!input || typeof input !== "object") return void 0;
5776
6948
  const obj = input;
5777
6949
  const globChars = /[*?\[\]]/g;
5778
6950
  const escapeGlob = (s) => s.replace(globChars, (c) => `\\${c}`);
6951
+ const normalizePath = (s) => escapeGlob(s.replace(/\\/g, "/"));
6952
+ if (subjectKey) {
6953
+ const v = obj[subjectKey];
6954
+ if (typeof v === "string") {
6955
+ return subjectKey === "path" || subjectKey === "file" || subjectKey === "files" ? normalizePath(v) : escapeGlob(v);
6956
+ }
6957
+ }
5779
6958
  if (toolName === "bash" && typeof obj.command === "string") {
5780
6959
  return escapeGlob(obj.command);
5781
6960
  }
5782
6961
  if (typeof obj.path === "string") {
5783
- return escapeGlob(obj.path.replace(/\\/g, "/"));
6962
+ return normalizePath(obj.path);
5784
6963
  }
5785
6964
  if (typeof obj.url === "string") {
5786
6965
  return escapeGlob(obj.url);
@@ -6204,8 +7383,9 @@ var DefaultHealthRegistry = class {
6204
7383
  return { status, timestamp: Date.now(), checks: results };
6205
7384
  }
6206
7385
  async runOne(check) {
7386
+ let timer = null;
6207
7387
  const timeout = new Promise((resolve4) => {
6208
- setTimeout(
7388
+ timer = setTimeout(
6209
7389
  () => resolve4({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
6210
7390
  this.timeoutMs
6211
7391
  );
@@ -6214,6 +7394,8 @@ var DefaultHealthRegistry = class {
6214
7394
  return await Promise.race([check.check(), timeout]);
6215
7395
  } catch (err) {
6216
7396
  return { status: "unhealthy", detail: err instanceof Error ? err.message : String(err) };
7397
+ } finally {
7398
+ if (timer) clearTimeout(timer);
6217
7399
  }
6218
7400
  }
6219
7401
  };
@@ -6386,7 +7568,7 @@ var PROMETHEUS_CONTENT_TYPE = "text/plain; version=0.0.4; charset=utf-8";
6386
7568
  async function startMetricsServer(opts) {
6387
7569
  const { createServer } = await import('http');
6388
7570
  const host = opts.host ?? "127.0.0.1";
6389
- const path15 = opts.path ?? "/metrics";
7571
+ const path17 = opts.path ?? "/metrics";
6390
7572
  const healthPath = opts.healthPath ?? "/healthz";
6391
7573
  const healthRegistry = opts.healthRegistry;
6392
7574
  const server = createServer((req, res) => {
@@ -6396,7 +7578,7 @@ async function startMetricsServer(opts) {
6396
7578
  return;
6397
7579
  }
6398
7580
  const url = req.url.split("?")[0];
6399
- if (url === path15) {
7581
+ if (url === path17) {
6400
7582
  let body;
6401
7583
  try {
6402
7584
  body = renderPrometheus(opts.sink.snapshot());
@@ -6447,7 +7629,7 @@ async function startMetricsServer(opts) {
6447
7629
  const boundPort = typeof addr === "object" && addr ? addr.port : opts.port;
6448
7630
  return {
6449
7631
  port: boundPort,
6450
- url: `http://${host}:${boundPort}${path15}`,
7632
+ url: `http://${host}:${boundPort}${path17}`,
6451
7633
  close: () => new Promise((resolve4, reject) => {
6452
7634
  server.close((err) => err ? reject(err) : resolve4());
6453
7635
  })
@@ -6852,7 +8034,6 @@ function createContextManagerTool(opts = {}) {
6852
8034
  notes: `Invalid range [${from}, ${to}] for ${messages.length} messages.`
6853
8035
  };
6854
8036
  }
6855
- messages.slice(from, to + 1);
6856
8037
  const summaryText = input.text ?? '[summary placeholder \u2014 provide "text" to record the summary]';
6857
8038
  const summaryMsg = {
6858
8039
  role: "system",
@@ -7114,13 +8295,23 @@ async function streamProviderToResponse(provider, req, signal, ctx, events) {
7114
8295
  }
7115
8296
  } catch (err) {
7116
8297
  if (signal.aborted) {
7117
- state.stopReason = "max_tokens";
8298
+ state.stopReason = "end_turn";
7118
8299
  return buildResponse(state);
7119
8300
  }
7120
8301
  throw err;
7121
8302
  } finally {
7122
8303
  try {
7123
- await iter.return?.();
8304
+ let drainTimer = null;
8305
+ try {
8306
+ await Promise.race([
8307
+ Promise.resolve(iter.return?.()),
8308
+ new Promise((resolve4) => {
8309
+ drainTimer = setTimeout(resolve4, 500);
8310
+ })
8311
+ ]);
8312
+ } finally {
8313
+ if (drainTimer) clearTimeout(drainTimer);
8314
+ }
7124
8315
  } catch {
7125
8316
  }
7126
8317
  }
@@ -7177,12 +8368,24 @@ async function runProviderWithRetry(opts) {
7177
8368
  });
7178
8369
  }
7179
8370
  await new Promise((resolve4, reject) => {
7180
- const t2 = setTimeout(resolve4, delay);
7181
- const onAbort = () => {
8371
+ let settled = false;
8372
+ const cleanup = () => {
8373
+ if (settled) return;
8374
+ settled = true;
7182
8375
  clearTimeout(t2);
8376
+ };
8377
+ const onAbort = () => {
8378
+ cleanup();
7183
8379
  reject(new Error("aborted"));
7184
8380
  };
7185
- if (signal.aborted) onAbort();
8381
+ const t2 = setTimeout(() => {
8382
+ cleanup();
8383
+ resolve4();
8384
+ }, delay);
8385
+ if (signal.aborted) {
8386
+ onAbort();
8387
+ return;
8388
+ }
7186
8389
  signal.addEventListener("abort", onAbort, { once: true });
7187
8390
  });
7188
8391
  attempt++;
@@ -7306,9 +8509,6 @@ var Agent = class {
7306
8509
  get scrubber() {
7307
8510
  return this.container.resolve(TOKENS.SecretScrubber);
7308
8511
  }
7309
- get compactor() {
7310
- return this.container.has(TOKENS.Compactor) ? this.container.resolve(TOKENS.Compactor) : void 0;
7311
- }
7312
8512
  get renderer() {
7313
8513
  return this.container.has(TOKENS.Renderer) ? this.container.resolve(TOKENS.Renderer) : void 0;
7314
8514
  }
@@ -7354,6 +8554,7 @@ var Agent = class {
7354
8554
  let iterations = 0;
7355
8555
  let effectiveLimit = opts.maxIterations ?? this.maxIterations;
7356
8556
  const hasHardLimit = effectiveLimit > 0 && Number.isFinite(effectiveLimit);
8557
+ let recoveryRetries = 0;
7357
8558
  for (let i = 0; ; i++) {
7358
8559
  iterations = i + 1;
7359
8560
  if (controller.signal.aborted) {
@@ -7383,17 +8584,33 @@ var Agent = class {
7383
8584
  logger: this.logger,
7384
8585
  tracer: this.tracer
7385
8586
  });
8587
+ recoveryRetries = 0;
7386
8588
  } catch (err) {
7387
8589
  if (controller.signal.aborted) {
7388
8590
  this.events.emit("error", { err: toError(err), phase: "provider" });
7389
8591
  return { status: "aborted", iterations, error: toWrongStackError(err, "AGENT_ABORTED") };
7390
8592
  }
7391
8593
  const recovered = await this.errorHandler.recover(err, this.ctx);
7392
- if (!recovered) {
8594
+ if (!recovered || recovered.action === "fail") {
7393
8595
  this.events.emit("error", { err: toError(err), phase: "provider" });
7394
- return { status: "failed", iterations, error: toWrongStackError(err) };
8596
+ return {
8597
+ status: "failed",
8598
+ iterations,
8599
+ error: toWrongStackError(recovered?.error ?? err)
8600
+ };
7395
8601
  }
7396
- res = recovered;
8602
+ if (recovered.action === "retry") {
8603
+ recoveryRetries++;
8604
+ if (recoveryRetries > 2) {
8605
+ this.events.emit("error", { err: toError(err), phase: "provider" });
8606
+ return { status: "failed", iterations, error: toWrongStackError(err) };
8607
+ }
8608
+ if (recovered.model) this.ctx.model = recovered.model;
8609
+ this.logger.info(`Recovered provider error via ${recovered.reason}; retrying turn`);
8610
+ continue;
8611
+ }
8612
+ recoveryRetries = 0;
8613
+ res = recovered.response;
7397
8614
  }
7398
8615
  const responseResult = await this.processResponse(res, req);
7399
8616
  if (responseResult.aborted) {
@@ -7467,7 +8684,7 @@ var Agent = class {
7467
8684
  * update session, render text, handle abort.
7468
8685
  */
7469
8686
  async processResponse(res, req) {
7470
- await this.pipelines.response.run(res);
8687
+ res = await this.pipelines.response.run(res);
7471
8688
  this.events.emit("provider.response", {
7472
8689
  ctx: this.ctx,
7473
8690
  usage: res.usage,
@@ -7538,6 +8755,7 @@ var Agent = class {
7538
8755
  isError: !!reRunResult.result.is_error
7539
8756
  });
7540
8757
  this.events.emit("tool.executed", {
8758
+ id: reRunResult.result.tool_use_id,
7541
8759
  name: tool.name,
7542
8760
  durationMs: reRunResult.durationMs,
7543
8761
  ok: !reRunResult.result.is_error,
@@ -7545,7 +8763,6 @@ var Agent = class {
7545
8763
  output: truncateForEvent(reRunResult.result.content)
7546
8764
  });
7547
8765
  }
7548
- this.ctx.state.appendMessage({ role: "user", content: [reRunResult.result] });
7549
8766
  continue;
7550
8767
  }
7551
8768
  const use = useById.get(result.tool_use_id);
@@ -7564,6 +8781,7 @@ var Agent = class {
7564
8781
  isError: !!result.is_error
7565
8782
  });
7566
8783
  this.events.emit("tool.executed", {
8784
+ id: result.tool_use_id,
7567
8785
  name: use.name,
7568
8786
  durationMs,
7569
8787
  ok: !result.is_error,
@@ -7604,12 +8822,11 @@ var Agent = class {
7604
8822
  }
7605
8823
  }
7606
8824
  /**
7607
- * Run context window pipeline if compactor is present.
8825
+ * Run context window pipeline. The pipeline may be empty, or it may contain
8826
+ * middleware with its own injected dependencies.
7608
8827
  */
7609
8828
  async compactContextIfNeeded() {
7610
- if (this.compactor) {
7611
- await this.pipelines.contextWindow.run(this.ctx);
7612
- }
8829
+ await this.pipelines.contextWindow.run(this.ctx);
7613
8830
  }
7614
8831
  };
7615
8832
  function toError(err) {
@@ -7627,7 +8844,6 @@ var ConversationState = class {
7627
8844
  constructor(ctx) {
7628
8845
  this.ctx = ctx;
7629
8846
  }
7630
- // ─── Read API ───────────────────────────────────────────────────────
7631
8847
  get messages() {
7632
8848
  return this.ctx.messages;
7633
8849
  }
@@ -7642,25 +8858,24 @@ var ConversationState = class {
7642
8858
  * that need a stable view across an async boundary.
7643
8859
  */
7644
8860
  snapshot() {
7645
- return {
7646
- messages: [...this.ctx.messages],
7647
- todos: [...this.ctx.todos],
7648
- meta: { ...this.ctx.meta }
7649
- };
8861
+ return Object.freeze({
8862
+ messages: Object.freeze([...this.ctx.messages]),
8863
+ todos: Object.freeze([...this.ctx.todos]),
8864
+ meta: Object.freeze({ ...this.ctx.meta })
8865
+ });
7650
8866
  }
7651
- // ─── Write API (preferred — fires onChange) ─────────────────────────
7652
8867
  appendMessage(message) {
7653
- this.ctx.messages.push(message);
8868
+ this.ctx.messages.splice(this.ctx.messages.length, 0, message);
7654
8869
  this.emit({ kind: "message_appended", message });
7655
8870
  }
7656
8871
  replaceMessages(messages) {
7657
8872
  this.ctx.messages.length = 0;
7658
- this.ctx.messages.push(...messages);
8873
+ this.ctx.messages.splice(0, 0, ...messages);
7659
8874
  this.emit({ kind: "messages_replaced", messages: [...messages] });
7660
8875
  }
7661
8876
  replaceTodos(todos) {
7662
8877
  this.ctx.todos.length = 0;
7663
- this.ctx.todos.push(...todos);
8878
+ this.ctx.todos.splice(0, 0, ...todos);
7664
8879
  this.emit({ kind: "todos_replaced", todos: [...todos] });
7665
8880
  }
7666
8881
  setMeta(key, value) {
@@ -7672,13 +8887,15 @@ var ConversationState = class {
7672
8887
  delete this.ctx.meta[key];
7673
8888
  this.emit({ kind: "meta_deleted", key });
7674
8889
  }
7675
- // ─── Subscription ───────────────────────────────────────────────────
8890
+ clearMeta() {
8891
+ const keys = Object.keys(this.ctx.meta);
8892
+ if (keys.length === 0) return;
8893
+ for (const key of keys) delete this.ctx.meta[key];
8894
+ this.emit({ kind: "meta_cleared" });
8895
+ }
7676
8896
  /**
7677
- * Subscribe to mutations that go through this wrapper. Note: mutations
7678
- * that bypass the wrapper (e.g. `ctx.messages.push(...)` directly) are
7679
- * NOT observed — by design during migration, since we don't want to
7680
- * monkey-patch arrays. Migrating call sites to use this API is the
7681
- * dev-plan #1 work.
8897
+ * Subscribe to mutations that go through this wrapper. Direct mutations of
8898
+ * the compatibility arrays are intentionally not observed.
7682
8899
  */
7683
8900
  onChange(listener) {
7684
8901
  this.listeners.add(listener);
@@ -7895,34 +9112,60 @@ You operate inside the user's terminal with direct read and write access to thei
7895
9112
 
7896
9113
  ## Core principles
7897
9114
 
7898
- 1. Read before you write. Always inspect the relevant files before proposing changes. Assumptions about code you haven't read are bugs in waiting.
7899
-
7900
- 2. Prefer surgical edits over rewrites. When modifying existing files, use the edit tool with str_replace; only use write for new files or full replacements explicitly requested.
9115
+ 1. **Read before you write.** Always inspect the relevant files before proposing changes. Assumptions about code you haven't read are bugs in waiting.
9116
+ 2. **Prefer surgical edits over rewrites.** When modifying existing files, use the edit tool with str_replace; only use write for new files or full replacements explicitly requested.
9117
+ 3. **Show your work.** Before non-trivial changes, briefly state what you're about to do \u2014 one sentence, not a wall of text. After tool calls, summarize what happened, not what you did mechanically.
9118
+ 4. **Be honest about limits.** If you don't know, say so. If something failed, say what failed and what you'll try next. Never fabricate file contents, API responses, or test results.
9119
+ 5. **Be concise.** The user is a developer in a terminal. No marketing language, no "great question!", no bullet-point lists when prose works. If a one-liner answers, a one-liner is the answer.
9120
+ 6. **Ask when blocked, proceed when not.** If the task is ambiguous in a way that meaningfully changes the approach, ask. If it's ambiguous in a way that doesn't, pick a reasonable default and proceed, stating the assumption.
9121
+ 7. **Trust the tools.** If a permission prompt is shown, the user will answer. Do not preemptively explain that you "would like to" do something \u2014 call the tool, let the permission flow decide.
9122
+ 8. **Format for scanability.** Use code blocks for code, backticks for file paths, bold for key terms. One-liners stay one line. Paragraphs max 3 sentences.
9123
+ 9. **Recover explicitly.** When a tool fails, state: (1) what failed, (2) what you tried, (3) what you'll attempt next. Never silently skip.
7901
9124
 
7902
- 3. Show your work. Before non-trivial changes, briefly state what you're about to do \u2014 one sentence, not a wall of text. After tool calls, summarize what happened, not what you did mechanically.
9125
+ ## Decision heuristics
7903
9126
 
7904
- 4. Honest about limits. If you don't know, say so. If something failed, say what failed and what you'll try next. Never fabricate file contents, API responses, or test results.
9127
+ - **Task is ambiguous** (unclear which file, conflicting requirements) \u2192 ask before proceeding
9128
+ - **Task is clear, approach is unknown** \u2192 try one approach, report what happened
9129
+ - **Tool fails** \u2192 retry once with adjusted params, then report failure
9130
+ - **Permission prompt shown** \u2192 wait for user, do not act unilaterally
9131
+ - **Context window filling up** \u2192 use context_manager proactively; don't wait to be told
7905
9132
 
7906
- 5. Concise output. The user is a developer in a terminal. No marketing language, no "great question!", no bullet-point lists when prose works. If a one-liner answers, a one-liner is the answer.
9133
+ ## How you work
7907
9134
 
7908
- 6. Ask when blocked, proceed when not. If the task is ambiguous in a way that meaningfully changes the approach, ask. If it's ambiguous in a way that doesn't, pick a reasonable default and proceed, stating the assumption.
7909
-
7910
- 7. Trust the tools. If a permission prompt is shown, the user will answer. Do not preemptively explain that you "would like to" do something \u2014 call the tool, let the permission flow decide.
7911
-
7912
- ## What you do not do
7913
-
7914
- - You do not lecture about software engineering principles unless asked.
7915
- - You do not add comments to code unless they materially help or were requested.
7916
- - You do not refactor adjacent code while fixing a bug, unless asked.
7917
- - You do not claim work is "production-ready" or "fully tested" \u2014 the user decides that.
7918
- - You do not apologize for failures. You report them and proceed.`;
9135
+ - **Stay focused.** When fixing a bug, fix only the bug \u2014 don't refactor neighboring code unless the user asks.
9136
+ - **Comment with purpose.** Add comments only when they explain why, not what. The code already says what.
9137
+ - **Own your output.** Never call work "production-ready" or "fully tested" \u2014 the user makes that call.
9138
+ - **Move on from mistakes.** When something fails, report what happened and what you'll do next. No apologies, no hand-wringing.
9139
+ - **Stay in your lane.** Don't lecture about software engineering principles unless explicitly asked \u2014 the user is the expert on their codebase.`;
7919
9140
  var DefaultSystemPromptBuilder = class {
7920
9141
  constructor(opts = {}) {
7921
9142
  this.opts = opts;
7922
9143
  }
7923
9144
  opts;
7924
- envCache;
9145
+ /**
9146
+ * Cached environment block, keyed by projectRoot. A single builder
9147
+ * instance is normally reused across turns of the same agent run, but
9148
+ * tests and library consumers may reuse it across runs with different
9149
+ * roots; keying the cache prevents leaking the first call's project
9150
+ * state into a later call against an unrelated project.
9151
+ */
9152
+ envCacheByRoot = /* @__PURE__ */ new Map();
9153
+ skillCache;
7925
9154
  async build(ctx) {
9155
+ if (this.opts.skillLoader && !this.skillCache) {
9156
+ try {
9157
+ const entries = await this.opts.skillLoader.listEntries();
9158
+ if (entries.length > 0) {
9159
+ const lines = [];
9160
+ for (const e of entries) {
9161
+ const scopeTag = e.scope.length > 0 ? ` \u2014 ${e.scope.slice(0, 4).join(", ")}` : "";
9162
+ lines.push(`- **${e.name}**${scopeTag} (${e.trigger})`);
9163
+ }
9164
+ this.skillCache = lines.join("\n");
9165
+ }
9166
+ } catch {
9167
+ }
9168
+ }
7926
9169
  const layer1 = LAYER_1_IDENTITY;
7927
9170
  const layer2 = this.buildToolUsage(ctx.tools);
7928
9171
  const layer3 = await this.buildEnvironment(ctx);
@@ -7958,8 +9201,20 @@ var DefaultSystemPromptBuilder = class {
7958
9201
  ### ${t2.name}
7959
9202
  ${hint.trim()}`);
7960
9203
  }
9204
+ lines.push(`
9205
+ ## Common patterns
9206
+
9207
+ - **Inspect before edit:** \`read\`/\`glob\`/\`grep\` \u2192 locate target \u2192 \`edit\`
9208
+ - **Search then operate:** \`grep\`/\`glob\` \u2192 identify targets \u2192 \`batch_tool_use\` or iterative \`edit\`
9209
+ - **Verify after mutate:** \`write\`/\`edit\`/\`patch\` \u2192 \`read\` back to confirm \u2192 report outcome
9210
+ - **Explore project:** \`glob\` for structure \u2192 \`read\` key files \u2192 \`grep\` for patterns
9211
+ - **Batch ops:** Use \`replace\` with glob patterns for multi-file surgical changes
9212
+
9213
+ When unsure about a file's current state, read it first rather than assuming.`);
7961
9214
  const hasContextManager = tools.some((t2) => t2.name === "context_manager");
7962
9215
  if (hasContextManager) {
9216
+ const maxCtx = this.opts.modelCapabilities?.maxContextTokens ?? 128e3;
9217
+ const threshold = maxCtx <= 32e3 ? "50" : "70";
7963
9218
  lines.push(`
7964
9219
  ## Context management
7965
9220
 
@@ -7967,7 +9222,7 @@ When the conversation grows long and context window usage exceeds what you can t
7967
9222
  use the context_manager tool proactively \u2014 do NOT wait to be told:
7968
9223
 
7969
9224
  - Call \`context_manager\` with \`{"action":"check"}\` to see current token budget and message counts.
7970
- - When the conversation exceeds ~70% of your context window, call \`{"action":"summary"}\` or \`{"action":"compact"}\` to reclaim space.
9225
+ - When the conversation exceeds ~${threshold}% of your context window, call \`{"action":"summary"}\` or \`{"action":"compact"}\` to reclaim space.
7971
9226
  - Use \`{"action":"prune"}\` to surgically remove specific irrelevant message ranges (e.g. old debug output).
7972
9227
  - Use \`{"action":"add_note"}\` to inject a summary note at a specific point after a complex operation.
7973
9228
 
@@ -7977,14 +9232,17 @@ summarize it, and let the tool result hold only the summary.`);
7977
9232
  return lines.join("\n");
7978
9233
  }
7979
9234
  async buildEnvironment(ctx) {
7980
- if (this.envCache) return this.envCache;
9235
+ const cached = this.envCacheByRoot.get(ctx.projectRoot);
9236
+ if (cached) return cached;
7981
9237
  const today = this.opts.todayIso ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
7982
9238
  const platform2 = `${os3.platform()} ${os3.release()}`;
7983
9239
  const shell = process.env.SHELL ?? process.env.ComSpec ?? "unknown";
7984
9240
  const node = process.version;
7985
9241
  const isGit = await this.dirExists(path2.join(ctx.projectRoot, ".git"));
7986
- const git = isGit ? await this.gitStatus(ctx.projectRoot) : "not a git repo";
7987
- const langs = await this.detectLanguages(ctx.projectRoot);
9242
+ const [git, langs] = await Promise.all([
9243
+ isGit ? this.gitStatus(ctx.projectRoot) : Promise.resolve("not a git repo"),
9244
+ this.detectLanguages(ctx.projectRoot)
9245
+ ]);
7988
9246
  const lines = [
7989
9247
  "## Environment",
7990
9248
  "",
@@ -8002,8 +9260,17 @@ summarize it, and let the tool result hold only the summary.`);
8002
9260
  `- Running on: ${ctx.provider ?? "<unknown provider>"}/${ctx.model ?? "<unknown model>"}`
8003
9261
  );
8004
9262
  }
9263
+ if (this.opts.modeId && this.opts.modeId !== "default") {
9264
+ lines.push(`- Mode: ${this.opts.modeId}`);
9265
+ }
9266
+ if (this.opts.modelCapabilities) {
9267
+ lines.push(`- Context window: ${this.opts.modelCapabilities.maxContextTokens.toLocaleString()} tokens max`);
9268
+ }
9269
+ if (this.skillCache) {
9270
+ lines.push("", "## Skills in scope for this session", this.skillCache);
9271
+ }
8005
9272
  const text = lines.join("\n");
8006
- this.envCache = text;
9273
+ this.envCacheByRoot.set(ctx.projectRoot, text);
8007
9274
  return text;
8008
9275
  }
8009
9276
  async buildMemoryAndSkills() {
@@ -8017,16 +9284,10 @@ ${mem}`);
8017
9284
  } catch {
8018
9285
  }
8019
9286
  }
8020
- if (this.opts.skillLoader) {
8021
- try {
8022
- const manifest = await this.opts.skillLoader.manifestText();
8023
- if (manifest.trim()) parts.push(manifest);
8024
- } catch {
8025
- }
8026
- }
8027
9287
  return parts.join("\n\n");
8028
9288
  }
8029
9289
  async buildMode() {
9290
+ if (this.opts.modePrompt) return this.opts.modePrompt;
8030
9291
  if (!this.opts.modeStore) return "";
8031
9292
  const mode = await this.opts.modeStore.getActiveMode();
8032
9293
  if (!mode?.prompt) return "";
@@ -8042,8 +9303,19 @@ ${mem}`);
8042
9303
  }
8043
9304
  async gitStatus(root) {
8044
9305
  return new Promise((resolve4) => {
9306
+ let settled = false;
9307
+ const finish = (s) => {
9308
+ if (settled) return;
9309
+ settled = true;
9310
+ resolve4(s);
9311
+ };
9312
+ let proc;
9313
+ const timer = setTimeout(() => {
9314
+ proc?.kill("SIGKILL");
9315
+ finish("git timeout");
9316
+ }, 2e3);
8045
9317
  try {
8046
- const proc = spawn("git", ["status", "--porcelain=v1", "--branch"], {
9318
+ proc = spawn("git", ["status", "--porcelain=v1", "--branch"], {
8047
9319
  cwd: root,
8048
9320
  stdio: ["ignore", "pipe", "ignore"]
8049
9321
  });
@@ -8051,19 +9323,24 @@ ${mem}`);
8051
9323
  proc.stdout?.on("data", (c) => {
8052
9324
  buf += c.toString();
8053
9325
  });
8054
- proc.on("error", () => resolve4("git error"));
9326
+ proc.on("error", () => {
9327
+ clearTimeout(timer);
9328
+ finish("git error");
9329
+ });
8055
9330
  proc.on("close", () => {
9331
+ clearTimeout(timer);
8056
9332
  const lines = buf.split("\n").filter(Boolean);
8057
9333
  const branchLine = lines[0] ?? "";
8058
- const branchMatch = /## ([^\s.]+)/.exec(branchLine);
9334
+ const branchMatch = branchLine.match(/## ([^\s.]+)/);
8059
9335
  const branch = branchMatch?.[1] ?? "detached";
8060
9336
  const dirty = lines.slice(1);
8061
9337
  const staged = dirty.filter((l) => /^[MARCD]/.test(l)).length;
8062
9338
  const modified = dirty.length - staged;
8063
- resolve4(`branch=${branch}, ${modified} modified, ${staged} staged`);
9339
+ finish(`branch=${branch}, ${modified} modified, ${staged} staged`);
8064
9340
  });
8065
9341
  } catch {
8066
- resolve4("git unavailable");
9342
+ clearTimeout(timer);
9343
+ finish("git unavailable");
8067
9344
  }
8068
9345
  });
8069
9346
  }
@@ -8081,14 +9358,17 @@ ${mem}`);
8081
9358
  ["composer.json", "PHP"],
8082
9359
  ["mix.exs", "Elixir"]
8083
9360
  ];
8084
- const langs = /* @__PURE__ */ new Set();
8085
- for (const [marker, lang] of checks) {
8086
- try {
8087
- await fsp.access(path2.join(root, marker));
8088
- langs.add(lang);
8089
- } catch {
8090
- }
8091
- }
9361
+ const hits = await Promise.all(
9362
+ checks.map(async ([marker, lang]) => {
9363
+ try {
9364
+ await fsp.access(path2.join(root, marker));
9365
+ return lang;
9366
+ } catch {
9367
+ return null;
9368
+ }
9369
+ })
9370
+ );
9371
+ const langs = new Set(hits.filter((l) => l !== null));
8092
9372
  return langs.size === 0 ? "unknown" : Array.from(langs).join(", ");
8093
9373
  }
8094
9374
  };
@@ -8387,7 +9667,7 @@ var noopSlashCommands = {
8387
9667
  };
8388
9668
 
8389
9669
  // src/plugin/loader.ts
8390
- var KERNEL_API_VERSION = "0.1.1";
9670
+ var KERNEL_API_VERSION = "0.1.8";
8391
9671
  function parseSemver(v) {
8392
9672
  const parts = v.replace(/^[^0-9]*/, "").split(".").map((s) => Number.parseInt(s, 10) || 0);
8393
9673
  return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
@@ -8619,6 +9899,6 @@ function wrapApiForCapabilityCheck(plugin, api, log) {
8619
9899
  });
8620
9900
  }
8621
9901
 
8622
- export { Agent, AgentError, AutoCompactionMiddleware, AutonomousRunner, BudgetExceededError, ConfigError, ConfigMigrationError, Container, Context, ConversationState, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_SPEC_TEMPLATE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, DoneConditionChecker, ENCRYPTED_PREFIX, EventBus, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, LAYER_1_IDENTITY, LLMSelector, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, RecoveryLock, RunController, SelectiveCompactor, SessionError, SlashCommandRegistry, SpecDrivenDev, SpecParser, SubagentBudget, TOKENS, TaskFlow, TaskGenerator, TaskTracker, ToolError, ToolExecutor, ToolRegistry, WrongStackError, allServers, asBlocks, asText, atomicWrite, awsServer, blockServer, braveSearchServer, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, color, compileGlob, computeTaskProgress, context7Server, contextManagerTool, createContextManagerTool, createDefaultPipelines, createMessage, createToolOutputSerializer, decryptConfigSecrets, detectNewlineStyle, encryptConfigSecrets, ensureDir, estimateTextTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, extractRunEnv, filesystemServer, findCriticalPath, githubServer, googleMapsServer, isAgentError, isConfigError, isImageBlock, isPluginError, isSessionError, isTextBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, loadPlugins, loadProjectModes, loadUserModes, makeAgentSubagentRunner, matchAny, matchGlob, migratePlaintextSecrets, normalizeToLf, projectHash, renderPrometheus, resolveWstackPaths, rewriteConfigEncrypted, runConfigMigrations, safeParse, safeStringify, sanitizeJsonString, sentinelServer, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState };
9902
+ export { Agent, AgentError, AutoCompactionMiddleware, AutonomousRunner, BudgetExceededError, ConfigError, ConfigMigrationError, Container, Context, ConversationState, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DoneConditionChecker, ENCRYPTED_PREFIX, EventBus, FleetBus, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, LAYER_1_IDENTITY, LLMSelector, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, RecoveryLock, RunController, SelectiveCompactor, SessionError, SlashCommandRegistry, SpecDrivenDev, SpecParser, SubagentBudget, TOKENS, TaskFlow, TaskGenerator, TaskTracker, ToolError, ToolExecutor, ToolRegistry, WrongStackError, allServers, asBlocks, asText, atomicWrite, awsServer, blockServer, braveSearchServer, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, color, compileGlob, composeDirectorPrompt, composeSubagentPrompt, computeTaskProgress, context7Server, contextManagerTool, createContextManagerTool, createDefaultPipelines, createMessage, createToolOutputSerializer, decryptConfigSecrets, detectNewlineStyle, encryptConfigSecrets, ensureDir, estimateTextTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, extractRunEnv, filesystemServer, findCriticalPath, githubServer, googleMapsServer, isAgentError, isConfigError, isImageBlock, isPluginError, isSessionError, isTextBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, loadPlugins, loadProjectModes, loadUserModes, makeAgentSubagentRunner, makeDirectorSessionFactory, matchAny, matchGlob, migratePlaintextSecrets, normalizeToLf, projectHash, renderPrometheus, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, safeParse, safeStringify, sanitizeJsonString, sentinelServer, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState };
8623
9903
  //# sourceMappingURL=index.js.map
8624
9904
  //# sourceMappingURL=index.js.map