@wrongstack/core 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/{agent-bridge-DmBiCipY.d.ts → agent-bridge-C3DUGjSb.d.ts} +1 -1
  2. package/dist/{compactor-DSl2FK7a.d.ts → compactor-BUU6Zm_3.d.ts} +1 -1
  3. package/dist/{config-DXrqb41m.d.ts → config-CKLYPkCi.d.ts} +1 -1
  4. package/dist/{context-u0bryklF.d.ts → context-IovtuTf8.d.ts} +2 -0
  5. package/dist/coordination/index.d.ts +11 -11
  6. package/dist/coordination/index.js +24 -1
  7. package/dist/coordination/index.js.map +1 -1
  8. package/dist/defaults/index.d.ts +30 -15
  9. package/dist/defaults/index.js +321 -2
  10. package/dist/defaults/index.js.map +1 -1
  11. package/dist/{events-B6Q03pTu.d.ts → events-CNB9PALO.d.ts} +27 -1
  12. package/dist/execution/index.d.ts +12 -12
  13. package/dist/extension/index.d.ts +9 -0
  14. package/dist/extension/index.js +234 -0
  15. package/dist/extension/index.js.map +1 -0
  16. package/dist/{plugin-CoYYZKdn.d.ts → index-BDb0cAMP.d.ts} +370 -11
  17. package/dist/index.d.ts +75 -25
  18. package/dist/index.js +1864 -1347
  19. package/dist/index.js.map +1 -1
  20. package/dist/infrastructure/index.d.ts +6 -6
  21. package/dist/kernel/index.d.ts +12 -9
  22. package/dist/kernel/index.js +73 -7
  23. package/dist/kernel/index.js.map +1 -1
  24. package/dist/{mcp-servers-BA1Ofmfj.d.ts → mcp-servers-DR35ojJZ.d.ts} +3 -3
  25. package/dist/models/index.d.ts +2 -2
  26. package/dist/models/index.js +24 -1
  27. package/dist/models/index.js.map +1 -1
  28. package/dist/{multi-agent-BDfkxL5C.d.ts → multi-agent-B9a6sflH.d.ts} +2 -2
  29. package/dist/observability/index.d.ts +2 -2
  30. package/dist/{path-resolver-Crkt8wTQ.d.ts → path-resolver-Cl_q0u-R.d.ts} +2 -2
  31. package/dist/provider-runner-BXuADQqQ.d.ts +36 -0
  32. package/dist/sdd/index.d.ts +3 -3
  33. package/dist/{secret-scrubber-3TLUkiCV.d.ts → secret-scrubber-CgG2tV2B.d.ts} +1 -1
  34. package/dist/{secret-scrubber-CwYliRWd.d.ts → secret-scrubber-Cuy5afaQ.d.ts} +1 -1
  35. package/dist/security/index.d.ts +3 -3
  36. package/dist/security/index.js +24 -1
  37. package/dist/security/index.js.map +1 -1
  38. package/dist/{selector-BRqzvugb.d.ts → selector-wT2fv9Fg.d.ts} +1 -1
  39. package/dist/{session-reader-C3x96CDR.d.ts → session-reader-CcPi4BQ8.d.ts} +1 -1
  40. package/dist/{skill-Bx8jxznf.d.ts → skill-C_7znCIC.d.ts} +2 -2
  41. package/dist/storage/index.d.ts +5 -5
  42. package/dist/storage/index.js +24 -1
  43. package/dist/storage/index.js.map +1 -1
  44. package/dist/{renderer-0A2ZEtca.d.ts → system-prompt-Dk1qm8ey.d.ts} +30 -2
  45. package/dist/{tool-executor-CYdZdtno.d.ts → tool-executor-DKu4A6nB.d.ts} +5 -5
  46. package/dist/types/index.d.ts +16 -16
  47. package/dist/types/index.js +24 -1
  48. package/dist/types/index.js.map +1 -1
  49. package/dist/utils/index.d.ts +1 -1
  50. package/dist/utils/index.js +24 -1
  51. package/dist/utils/index.js.map +1 -1
  52. package/package.json +5 -1
  53. package/dist/system-prompt-CG9jU5-5.d.ts +0 -31
package/dist/index.js CHANGED
@@ -395,6 +395,7 @@ var Pipeline = class {
395
395
  // src/kernel/events.ts
396
396
  var EventBus = class {
397
397
  listeners = /* @__PURE__ */ new Map();
398
+ wildcards = [];
398
399
  logger;
399
400
  setLogger(logger) {
400
401
  this.logger = logger;
@@ -421,24 +422,73 @@ var EventBus = class {
421
422
  this.off(event, wrapper);
422
423
  };
423
424
  }
425
+ /**
426
+ * Subscribe to all events whose name matches a glob-style prefix.
427
+ * `'tool.*'` matches `tool.started`, `tool.executed`, `tool.progress`, etc.
428
+ * `'*'` matches every event.
429
+ *
430
+ * The handler receives `(eventName, payload)` with the event name as a
431
+ * string and the payload as `unknown`. Use for logging, debugging, or
432
+ * metrics collection across a family of events.
433
+ *
434
+ * Returns an unsubscribe function.
435
+ */
436
+ onPattern(pattern, fn) {
437
+ const match = makePatternMatcher(pattern);
438
+ const entry = { match, fn };
439
+ this.wildcards.push(entry);
440
+ return () => {
441
+ const idx = this.wildcards.indexOf(entry);
442
+ if (idx >= 0) this.wildcards.splice(idx, 1);
443
+ };
444
+ }
445
+ /**
446
+ * Subscribe to all events whose name matches a RegExp.
447
+ * More flexible than `onPattern` — use when you need regex features
448
+ * (alternation, character classes, capture groups).
449
+ *
450
+ * Returns an unsubscribe function.
451
+ */
452
+ onRegex(regex, fn) {
453
+ const entry = { match: (e) => regex.test(e), fn };
454
+ this.wildcards.push(entry);
455
+ return () => {
456
+ const idx = this.wildcards.indexOf(entry);
457
+ if (idx >= 0) this.wildcards.splice(idx, 1);
458
+ };
459
+ }
424
460
  emit(event, payload) {
425
461
  const set = this.listeners.get(event);
426
- if (!set) return;
427
- for (const fn of set) {
428
- try {
429
- fn(payload);
430
- } catch (err) {
431
- this.logger?.error(`EventBus listener for "${event}" threw`, err);
462
+ if (set) {
463
+ for (const fn of set) {
464
+ try {
465
+ fn(payload);
466
+ } catch (err) {
467
+ this.logger?.error(`EventBus listener for "${event}" threw`, err);
468
+ }
469
+ }
470
+ }
471
+ if (this.wildcards.length > 0) {
472
+ const name = event;
473
+ for (const { match, fn } of this.wildcards) {
474
+ if (!match(name)) continue;
475
+ try {
476
+ fn(name, payload);
477
+ } catch (err) {
478
+ this.logger?.error(`EventBus wildcard listener for "${name}" threw`, err);
479
+ }
432
480
  }
433
481
  }
434
482
  }
435
483
  clear() {
436
484
  this.listeners.clear();
485
+ this.wildcards.length = 0;
437
486
  }
438
487
  /**
439
488
  * V2-D: introspection helper. Pass an `event` to count handlers for a
440
489
  * single key, or omit to get the total across every event. Used by the
441
490
  * leak-detection smoke test to flag handler accumulation across runs.
491
+ * Does NOT include wildcard listeners.
442
492
  */
443
493
  listenerCount(event) {
444
494
  if (event !== void 0) return this.listeners.get(event)?.size ?? 0;
@@ -446,7 +496,21 @@ var EventBus = class {
446
496
  for (const set of this.listeners.values()) total += set.size;
447
497
  return total;
448
498
  }
499
+ /**
500
+ * Number of wildcard listeners currently registered.
501
+ */
502
+ wildcardCount() {
503
+ return this.wildcards.length;
504
+ }
449
505
  };
506
+ function makePatternMatcher(pattern) {
507
+ if (pattern === "*") return () => true;
508
+ if (pattern.endsWith(".*")) {
509
+ const prefix = pattern.slice(0, -2);
510
+ return (e) => e.startsWith(`${prefix}.`);
511
+ }
512
+ return (e) => e === pattern;
513
+ }
450
514
 
451
515
  // src/kernel/tokens.ts
452
516
  var t = (name) => Symbol(name);
@@ -468,7 +532,9 @@ var TOKENS = {
468
532
  SystemPromptBuilder: t("SystemPromptBuilder"),
469
533
  SecretScrubber: t("SecretScrubber"),
470
534
  ModelsRegistry: t("ModelsRegistry"),
471
- ModeStore: t("ModeStore")
535
+ ModeStore: t("ModeStore"),
536
+ /** Replaces the entire provider call layer — retry, streaming, tracing. */
537
+ ProviderRunner: t("ProviderRunner")
472
538
  };
473
539
 
474
540
  // src/kernel/run-controller.ts
@@ -1539,7 +1605,7 @@ async function atomicWrite(targetPath, content, opts = {}) {
1539
1605
  if (mode !== void 0) {
1540
1606
  await fsp2.chmod(tmp, mode);
1541
1607
  }
1542
- await fsp2.rename(tmp, targetPath);
1608
+ await renameWithRetry(tmp, targetPath);
1543
1609
  } catch (err) {
1544
1610
  try {
1545
1611
  await fsp2.unlink(tmp);
@@ -1551,6 +1617,29 @@ async function atomicWrite(targetPath, content, opts = {}) {
1551
1617
  async function ensureDir(dir) {
1552
1618
  await fsp2.mkdir(dir, { recursive: true });
1553
1619
  }
1620
+ var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
1621
+ async function renameWithRetry(from, to) {
1622
+ if (process.platform !== "win32") {
1623
+ await fsp2.rename(from, to);
1624
+ return;
1625
+ }
1626
+ const delays = [10, 25, 60, 120, 250];
1627
+ let lastErr;
1628
+ for (let i = 0; i <= delays.length; i++) {
1629
+ try {
1630
+ await fsp2.rename(from, to);
1631
+ return;
1632
+ } catch (err) {
1633
+ lastErr = err;
1634
+ const code = err?.code;
1635
+ if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
1636
+ throw err;
1637
+ }
1638
+ await new Promise((resolve4) => setTimeout(resolve4, delays[i]));
1639
+ }
1640
+ }
1641
+ throw lastErr;
1642
+ }
1554
1643
 
1555
1644
  // src/models/models-registry.ts
1556
1645
  var DEFAULT_URL = "https://models.dev/api.json";
@@ -4889,501 +4978,424 @@ function parseDescription(raw) {
4889
4978
  return { trigger, scope };
4890
4979
  }
4891
4980
 
4892
- // src/execution/intelligent-compactor.ts
4893
- var IntelligentCompactor = class {
4894
- provider;
4895
- warnThreshold;
4896
- softThreshold;
4897
- hardThreshold;
4898
- maxContext;
4899
- preserveK;
4900
- eliseThreshold;
4901
- summarizerPrompt;
4902
- summarizerModel;
4903
- constructor(opts) {
4904
- this.provider = opts.provider;
4905
- this.warnThreshold = opts.warnThreshold ?? 0.6;
4906
- this.softThreshold = opts.softThreshold ?? 0.75;
4907
- this.hardThreshold = opts.hardThreshold ?? 0.9;
4908
- this.maxContext = opts.maxContext ?? 128e3;
4909
- this.preserveK = opts.preserveK ?? 4;
4910
- this.eliseThreshold = opts.eliseThreshold ?? 500;
4911
- this.summarizerPrompt = opts.summarizerPrompt ?? "You are a context summarizer. Given a list of conversation messages, produce a concise but complete summary that preserves all factual information, decisions made, and any state changes (e.g. file edits, todo updates). Do not add commentary. Output only the summary.";
4912
- this.summarizerModel = opts.summarizerModel;
4913
- }
4914
- async compact(ctx, opts = {}) {
4915
- const beforeTokens = this.estimateTokens(ctx.messages);
4916
- const reductions = [];
4917
- const load = beforeTokens / this.maxContext;
4918
- const aggressive = load >= this.hardThreshold ? true : opts.aggressive ?? load >= this.softThreshold;
4919
- const saved1 = this.eliseOldToolResults(ctx);
4920
- if (saved1 > 0) reductions.push({ phase: "elision", saved: saved1 });
4921
- if (aggressive) {
4922
- const saved2 = await this.summarizeAncientTurns(ctx);
4923
- if (saved2 > 0) reductions.push({ phase: "summary", saved: saved2 });
4924
- } else if (load >= this.warnThreshold) {
4925
- const saved2 = this.lightweightCompact(ctx);
4926
- if (saved2 > 0) reductions.push({ phase: "elision", saved: saved2 });
4927
- }
4928
- const afterTokens = this.estimateTokens(ctx.messages);
4929
- return { before: beforeTokens, after: afterTokens, reductions };
4930
- }
4931
- async summarizeAncientTurns(ctx) {
4932
- const messages = ctx.messages;
4933
- const cutoff = Math.max(0, messages.length - this.preserveK * 2);
4934
- if (cutoff <= 2) return 0;
4935
- const boundary = this.findSafeBoundary(messages, 0, cutoff);
4936
- if (boundary <= 1) return 0;
4937
- const toSummarize = messages.slice(0, boundary);
4938
- const removedTokens = this.estimateTokens(toSummarize);
4939
- let summaryText;
4940
- try {
4941
- summaryText = await this.callSummarizer(toSummarize, ctx);
4942
- } catch {
4943
- summaryText = `[${toSummarize.length} earlier turns omitted \u2014 key decisions and file states preserved in context]`;
4944
- }
4945
- const summaryMsg = {
4946
- role: "system",
4947
- content: `[prior_turns_summary: ${summaryText}]`
4948
- };
4949
- const summaryTokens = this.estimateTokens([summaryMsg]);
4950
- const tail = ctx.messages.slice(boundary);
4951
- ctx.state.replaceMessages([summaryMsg, ...tail]);
4952
- return Math.max(0, removedTokens - summaryTokens);
4953
- }
4954
- findSafeBoundary(messages, from, to) {
4955
- for (let i = to; i >= from; i--) {
4956
- const m = messages[i];
4957
- if (!m) continue;
4958
- if (m.role === "user" && this.hasTextContent(m)) {
4959
- return this.findExchangeStart(messages, i);
4981
+ // src/core/streaming-response-builder.ts
4982
+ function buildResponse(state) {
4983
+ const content = [];
4984
+ for (const b of state.blockOrder) {
4985
+ if (b.kind === "text") {
4986
+ const txt = state.textBuffers[b.idx] ?? "";
4987
+ if (txt) content.push({ type: "text", text: txt });
4988
+ } else if (b.kind === "thinking") {
4989
+ const t2 = state.thinking[b.idx];
4990
+ if (!t2) continue;
4991
+ if (!t2.textBuf && !t2.signature) continue;
4992
+ const block = { type: "thinking", thinking: t2.textBuf };
4993
+ if (t2.signature) block.signature = t2.signature;
4994
+ if (t2.providerMeta && Object.keys(t2.providerMeta).length > 0) {
4995
+ block.providerMeta = t2.providerMeta;
4960
4996
  }
4961
- }
4962
- return -1;
4963
- }
4964
- findExchangeStart(messages, userIndex) {
4965
- for (let i = userIndex - 1; i >= 0; i--) {
4966
- const m = messages[i];
4967
- if (!m) continue;
4968
- if (m.role === "assistant") {
4969
- const hasToolUse = Array.isArray(m.content) ? m.content.some((b) => b.type === "tool_use") : false;
4970
- if (!hasToolUse) {
4971
- return i + 1;
4997
+ content.push(block);
4998
+ } else {
4999
+ const tb = state.tools.get(b.id);
5000
+ if (tb) {
5001
+ const block = {
5002
+ type: "tool_use",
5003
+ id: b.id,
5004
+ name: tb.name,
5005
+ input: tb.input ?? {}
5006
+ };
5007
+ if (tb.providerMeta && Object.keys(tb.providerMeta).length > 0) {
5008
+ block.providerMeta = tb.providerMeta;
4972
5009
  }
4973
- } else if (m.role !== "user") ; else {
4974
- return i;
5010
+ content.push(block);
4975
5011
  }
4976
5012
  }
4977
- return 0;
4978
5013
  }
4979
- async callSummarizer(messages, ctx) {
4980
- const prompt = [
4981
- { type: "text", text: this.summarizerPrompt },
4982
- { type: "text", text: "\n\nConversation to summarize:\n" },
4983
- ...this.messagesToText(messages)
4984
- ];
4985
- const req = {
4986
- model: this.summarizerModel ?? ctx.model,
4987
- system: prompt,
4988
- messages: [],
4989
- maxTokens: 1024
4990
- };
4991
- const ac = ctx.signal ? void 0 : new AbortController();
4992
- const signal = ctx.signal ?? ac.signal;
4993
- const res = await this.provider.complete(req, { signal });
4994
- const textBlocks = res.content.filter(isTextBlock);
4995
- return textBlocks.map((b) => b.text).join("\n").trim() || "(empty summary)";
5014
+ if (content.length === 0) content.push({ type: "text", text: "" });
5015
+ return { content, stopReason: state.stopReason, usage: state.usage, model: state.model };
5016
+ }
5017
+ function createStreamingState(model) {
5018
+ return {
5019
+ model,
5020
+ stopReason: "end_turn",
5021
+ usage: { input: 0, output: 0 },
5022
+ textBuffers: [],
5023
+ currentTextIndex: -1,
5024
+ tools: /* @__PURE__ */ new Map(),
5025
+ thinking: [],
5026
+ currentThinkingIndex: -1,
5027
+ blockOrder: []
5028
+ };
5029
+ }
5030
+ function handleMessageStart(state, model) {
5031
+ state.model = model;
5032
+ }
5033
+ function handleContentBlockStart(state, ev) {
5034
+ const kind = ev.kind ?? "text";
5035
+ if (kind === "text") {
5036
+ state.currentTextIndex = state.textBuffers.length;
5037
+ state.textBuffers.push("");
5038
+ state.blockOrder.push({ kind: "text", idx: state.currentTextIndex });
5039
+ } else if (kind === "tool_use") {
5040
+ const id = ev.id ?? crypto.randomUUID();
5041
+ state.tools.set(id, { name: ev.name ?? "unknown", partial: "" });
5042
+ state.blockOrder.push({ kind: "tool", id });
5043
+ state.currentTextIndex = -1;
5044
+ } else if (kind === "thinking") {
5045
+ state.currentThinkingIndex = state.thinking.length;
5046
+ state.thinking.push({
5047
+ textBuf: "",
5048
+ ...ev.providerMeta ? { providerMeta: ev.providerMeta } : {}
5049
+ });
5050
+ state.blockOrder.push({ kind: "thinking", idx: state.currentThinkingIndex });
5051
+ state.currentTextIndex = -1;
4996
5052
  }
4997
- messagesToText(messages) {
4998
- const lines = [];
4999
- for (const m of messages) {
5000
- const role = m.role.padEnd(10, " ");
5001
- if (typeof m.content === "string") {
5002
- lines.push(`[${role}]: ${m.content.slice(0, 500)}`);
5003
- } else if (Array.isArray(m.content)) {
5004
- const textParts = m.content.filter(isTextBlock).map((b) => b.text);
5005
- if (textParts.length > 0) {
5006
- lines.push(`[${role}]: ${textParts.join(" ").slice(0, 500)}`);
5007
- }
5008
- }
5009
- }
5010
- return [{ type: "text", text: lines.join("\n") }];
5053
+ }
5054
+ function handleContentBlockStop(state, ev) {
5055
+ }
5056
+ function handleTextDelta(state, text) {
5057
+ if (state.currentTextIndex === -1) {
5058
+ state.currentTextIndex = state.textBuffers.length;
5059
+ state.textBuffers.push("");
5060
+ state.blockOrder.push({ kind: "text", idx: state.currentTextIndex });
5011
5061
  }
5012
- lightweightCompact(ctx) {
5013
- return this.eliseOldToolResults(ctx);
5062
+ state.textBuffers[state.currentTextIndex] = (state.textBuffers[state.currentTextIndex] ?? "") + text;
5063
+ }
5064
+ function handleToolUseStart(state, ev) {
5065
+ state.currentTextIndex = -1;
5066
+ state.tools.set(ev.id, { name: ev.name, partial: "" });
5067
+ state.blockOrder.push({ kind: "tool", id: ev.id });
5068
+ }
5069
+ function handleToolUseInputDelta(state, ev) {
5070
+ const t2 = state.tools.get(ev.id);
5071
+ if (t2) t2.partial += ev.partial;
5072
+ }
5073
+ function safeJsonOrRaw(s) {
5074
+ if (!s) return {};
5075
+ try {
5076
+ return JSON.parse(s);
5077
+ } catch {
5078
+ return { _raw: s };
5014
5079
  }
5015
- eliseOldToolResults(ctx) {
5016
- const messages = ctx.messages;
5017
- let pairCount = 0;
5018
- let preserveStart = messages.length;
5019
- for (let i = messages.length - 1; i >= 0 && pairCount < this.preserveK; i--) {
5020
- const m = messages[i];
5021
- if (!m) continue;
5022
- if (m.role === "user" || m.role === "assistant") {
5023
- pairCount++;
5024
- preserveStart = i;
5025
- }
5026
- }
5027
- let saved = 0;
5028
- let changed = false;
5029
- const nextMessages = new Array(messages.length);
5030
- for (let i = 0; i < messages.length; i++) {
5031
- const msg = messages[i];
5032
- if (i >= preserveStart) {
5033
- nextMessages[i] = msg;
5034
- continue;
5035
- }
5036
- if (!msg || !Array.isArray(msg.content)) {
5037
- nextMessages[i] = msg;
5038
- continue;
5039
- }
5040
- const newContent = msg.content.map((b) => {
5041
- if (b.type !== "tool_result") return b;
5042
- const tokens = estimateToolResultTokens(b.content);
5043
- if (tokens < this.eliseThreshold) return b;
5044
- saved += tokens;
5045
- return {
5046
- type: "tool_result",
5047
- tool_use_id: b.tool_use_id,
5048
- content: `[elided: ~${tokens} tokens]`,
5049
- is_error: b.is_error
5050
- };
5051
- });
5052
- if (newContent.length === msg.content.length && newContent.every((b, idx) => b === msg.content[idx])) {
5053
- nextMessages[i] = msg;
5054
- } else {
5055
- nextMessages[i] = { ...msg, content: newContent };
5056
- changed = true;
5057
- }
5058
- }
5059
- if (changed) ctx.state.replaceMessages(nextMessages);
5060
- return saved;
5080
+ }
5081
+ function handleToolUseStop(state, ev) {
5082
+ const t2 = state.tools.get(ev.id);
5083
+ if (t2) {
5084
+ t2.input = ev.input !== void 0 ? ev.input : safeJsonOrRaw(t2.partial);
5085
+ if (ev.providerMeta) t2.providerMeta = ev.providerMeta;
5061
5086
  }
5062
- hasTextContent(m) {
5063
- if (typeof m.content === "string") return m.content.trim().length > 0;
5064
- return m.content.some((b) => b.type === "text" && b.text.trim().length > 0);
5087
+ state.currentTextIndex = -1;
5088
+ }
5089
+ function handleThinkingStart(state, ev) {
5090
+ state.currentThinkingIndex = state.thinking.length;
5091
+ state.thinking.push({
5092
+ textBuf: "",
5093
+ ...ev.providerMeta ? { providerMeta: ev.providerMeta } : {}
5094
+ });
5095
+ state.blockOrder.push({ kind: "thinking", idx: state.currentThinkingIndex });
5096
+ state.currentTextIndex = -1;
5097
+ }
5098
+ function handleThinkingDelta(state, text) {
5099
+ if (state.currentThinkingIndex === -1) {
5100
+ handleThinkingStart(state, {});
5065
5101
  }
5066
- estimateTokens(messages) {
5067
- let total = 0;
5068
- for (const m of messages) {
5069
- if (typeof m.content === "string") {
5070
- total += estimateTextTokens(m.content);
5071
- } else {
5072
- for (const b of m.content) {
5073
- if (b.type === "text") total += estimateTextTokens(b.text);
5074
- else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
5075
- else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
5076
- }
5077
- }
5078
- }
5079
- return total;
5102
+ const t2 = state.thinking[state.currentThinkingIndex];
5103
+ if (t2) t2.textBuf += text;
5104
+ }
5105
+ function handleThinkingSignature(state, signature) {
5106
+ if (state.currentThinkingIndex === -1) {
5107
+ handleThinkingStart(state, {});
5080
5108
  }
5081
- };
5082
-
5083
- // src/models/llm-selector.ts
5084
- var DEFAULT_SYSTEM_PROMPT = `You are a context pruning assistant. Given a conversation history and a token budget, decide which message ranges are worth keeping verbatim and which should be collapsed into summaries.
5085
-
5086
- Output a JSON object with this structure:
5087
- {
5088
- "kept": [{"from": 0, "to": 5, "importance": "critical"}],
5089
- "collapsed": [{"from": 6, "to": 20, "summary": "optional summary"}],
5090
- "reasoning": "brief explanation of decisions"
5109
+ const t2 = state.thinking[state.currentThinkingIndex];
5110
+ if (t2) t2.signature = signature;
5091
5111
  }
5092
-
5093
- Importance tiers:
5094
- - "critical": decisions, file edits, tool results that affect state, final answers
5095
- - "high": substantive tool use, complex reasoning, non-obvious observations
5096
- - "medium": routine exchanges, confirmations, straightforward Q&A
5097
-
5098
- Rules:
5099
- - Always keep the most recent K pairs (preserve recency)
5100
- - Never collapse the final 2 user/assistant pairs (working memory)
5101
- - Preserve tool results that modified files or had external effects
5102
- - Collapse old, low-information exchanges (greetings, acknowledgements, etc.)
5103
- - If unsure, keep rather than collapse (errors are more costly than waste)
5104
-
5105
- Return ONLY the JSON object, no markdown, no explanation outside the JSON.`;
5106
- function estimateTokens(messages) {
5107
- let total = 0;
5108
- for (const m of messages) {
5109
- if (typeof m.content === "string") {
5110
- total += Math.ceil(m.content.length / 4);
5111
- } else if (Array.isArray(m.content)) {
5112
- for (const b of m.content) {
5113
- if (b.type === "text") total += Math.ceil(b.text.length / 4);
5114
- else total += Math.ceil(JSON.stringify(b).length / 4);
5112
+ function handleThinkingStop(state) {
5113
+ state.currentThinkingIndex = -1;
5114
+ }
5115
+ function handleMessageStop(state, ev) {
5116
+ state.stopReason = ev.stopReason ?? "end_turn";
5117
+ state.usage = ev.usage ?? { input: 0, output: 0 };
5118
+ }
5119
+ async function streamProviderToResponse(provider, req, signal, ctx, events) {
5120
+ const state = createStreamingState(req.model);
5121
+ const iter = provider.stream(req, { signal })[Symbol.asyncIterator]();
5122
+ try {
5123
+ for (; ; ) {
5124
+ const next = await iter.next();
5125
+ if (next.done) break;
5126
+ const ev = next.value;
5127
+ switch (ev.type) {
5128
+ case "message_start":
5129
+ handleMessageStart(state, ev.model);
5130
+ break;
5131
+ case "content_block_start":
5132
+ handleContentBlockStart(state, ev);
5133
+ break;
5134
+ case "content_block_stop":
5135
+ handleContentBlockStop(state, ev);
5136
+ break;
5137
+ case "text_delta":
5138
+ handleTextDelta(state, ev.text);
5139
+ events.emit("provider.text_delta", { ctx, text: ev.text });
5140
+ break;
5141
+ case "tool_use_start":
5142
+ handleToolUseStart(state, ev);
5143
+ events.emit("provider.tool_use_start", { ctx, id: ev.id, name: ev.name });
5144
+ break;
5145
+ case "tool_use_input_delta":
5146
+ handleToolUseInputDelta(state, ev);
5147
+ break;
5148
+ case "tool_use_stop":
5149
+ handleToolUseStop(state, ev);
5150
+ events.emit("provider.tool_use_stop", { ctx, id: ev.id });
5151
+ break;
5152
+ case "thinking_start":
5153
+ handleThinkingStart(state, ev);
5154
+ break;
5155
+ case "thinking_delta":
5156
+ handleThinkingDelta(state, ev.text);
5157
+ events.emit("provider.thinking_delta", { ctx, text: ev.text });
5158
+ break;
5159
+ case "thinking_signature":
5160
+ handleThinkingSignature(state, ev.signature);
5161
+ break;
5162
+ case "thinking_stop":
5163
+ handleThinkingStop(state);
5164
+ break;
5165
+ case "message_stop":
5166
+ handleMessageStop(state, ev);
5167
+ break;
5115
5168
  }
5116
5169
  }
5117
- }
5118
- return total;
5119
- }
5120
- function formatMessages(messages, maxChars = 8e3) {
5121
- const lines = [];
5122
- let used = 0;
5123
- for (let i = 0; i < messages.length; i++) {
5124
- const m = messages[i];
5125
- const role = m.role.padEnd(10, " ");
5126
- let text;
5127
- if (typeof m.content === "string") {
5128
- text = m.content.slice(0, 500);
5129
- } else {
5130
- const content = m.content;
5131
- text = content.filter(isTextBlock).map((b) => b.text).join(" ");
5132
- const toolUses = content.filter((b) => b.type === "tool_use");
5133
- if (toolUses.length > 0) {
5134
- text += ` [tools: ${toolUses.map((b) => b.name).join(", ")}]`;
5170
+ } catch (err) {
5171
+ if (signal.aborted) {
5172
+ state.stopReason = "end_turn";
5173
+ return buildResponse(state);
5174
+ }
5175
+ throw err;
5176
+ } finally {
5177
+ try {
5178
+ let drainTimer = null;
5179
+ try {
5180
+ await Promise.race([
5181
+ Promise.resolve(iter.return?.()),
5182
+ new Promise((resolve4) => {
5183
+ drainTimer = setTimeout(resolve4, 500);
5184
+ })
5185
+ ]);
5186
+ } finally {
5187
+ if (drainTimer) clearTimeout(drainTimer);
5135
5188
  }
5189
+ } catch {
5136
5190
  }
5137
- const line = `[${i}][${role}]: ${text}`;
5138
- if (used + line.length > maxChars) break;
5139
- lines.push(line);
5140
- used += line.length;
5141
5191
  }
5142
- return lines.join("\n");
5192
+ return buildResponse(state);
5143
5193
  }
5144
- var LLMSelector = class {
5145
- provider;
5146
- model;
5147
- maxContextTokens;
5148
- systemPrompt;
5149
- constructor(opts) {
5150
- this.provider = opts.provider;
5151
- this.model = opts.model ?? "unknown";
5152
- this.maxContextTokens = opts.maxContextTokens ?? 4e4;
5153
- this.systemPrompt = opts.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
5154
- }
5155
- async select(messages, maxToKeep) {
5156
- const effectiveBudget = Math.min(maxToKeep, this.maxContextTokens);
5157
- const historyText = formatMessages(messages);
5158
- const totalTokens = estimateTokens(messages);
5159
- const systemText = `${this.systemPrompt}
5160
5194
 
5161
- Conversation (${messages.length} messages, ~${totalTokens} tokens, budget: ${effectiveBudget}):
5162
- `;
5163
- const budgetInstruction = totalTokens > effectiveBudget ? `
5164
-
5165
- IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiveBudget}). You MUST collapse enough to fit. Prefer collapsing older/lower-importance ranges.` : "";
5166
- const req = {
5167
- model: this.model,
5168
- system: [{ type: "text", text: systemText + budgetInstruction }],
5169
- messages: [{ role: "user", content: historyText }],
5170
- maxTokens: 1024
5171
- };
5172
- let raw;
5195
+ // src/core/provider-runner.ts
5196
+ async function runProviderWithRetry(opts) {
5197
+ const { provider, request, signal, ctx, events, retry, logger, tracer } = opts;
5198
+ let attempt = 0;
5199
+ for (; ; ) {
5200
+ const span = tracer?.startSpan("provider.complete", {
5201
+ "provider.id": provider.id,
5202
+ "provider.model": request.model,
5203
+ "provider.streaming": provider.capabilities.streaming,
5204
+ "provider.attempt": attempt
5205
+ });
5173
5206
  try {
5174
- const ac = new AbortController();
5175
- const res = await this.provider.complete(req, { signal: ac.signal });
5176
- const textBlocks = res.content.filter(isTextBlock);
5177
- raw = textBlocks.map((b) => b.text).join("\n").trim();
5178
- } catch (err) {
5179
- return this.fallbackSelect(messages, effectiveBudget);
5180
- }
5181
- return this.parseSelectorOutput(raw, messages.length);
5182
- }
5183
- fallbackSelect(messages, budget) {
5184
- const toKeep = [];
5185
- const toCollapse = [];
5186
- let tokenCount = 0;
5187
- let startIdx = 0;
5188
- for (let i = messages.length - 1; i >= 0; i--) {
5189
- const m = messages[i];
5190
- const cost = typeof m.content === "string" ? Math.ceil(m.content.length / 4) : m.content.reduce(
5191
- (acc, b) => acc + (b.type === "text" ? Math.ceil(b.text.length / 4) : Math.ceil(JSON.stringify(b).length / 4)),
5192
- 0
5193
- );
5194
- if (tokenCount + cost <= budget) {
5195
- tokenCount += cost;
5196
- } else {
5197
- startIdx = i + 1;
5198
- break;
5207
+ const res = provider.capabilities.streaming ? await streamProviderToResponse(provider, request, signal, ctx, events) : await provider.complete(request, { signal });
5208
+ span?.setAttribute("provider.stopReason", res.stopReason);
5209
+ span?.setAttribute("provider.usage_in", res.usage.input);
5210
+ span?.setAttribute("provider.usage_out", res.usage.output);
5211
+ span?.end();
5212
+ return res;
5213
+ } catch (err) {
5214
+ if (err instanceof Error) span?.recordError(err);
5215
+ span?.end();
5216
+ if (signal.aborted) throw err;
5217
+ const isProviderErr = err instanceof ProviderError;
5218
+ const errAsErr = err instanceof Error ? err : new Error(String(err));
5219
+ const canRetry = retry.shouldRetry(isProviderErr ? err : errAsErr, attempt);
5220
+ const description = isProviderErr ? err.describe() : errAsErr.message;
5221
+ if (!canRetry) {
5222
+ if (isProviderErr) {
5223
+ events.emit("provider.error", {
5224
+ providerId: err.providerId,
5225
+ status: err.status,
5226
+ description,
5227
+ retryable: false
5228
+ });
5229
+ }
5230
+ throw err;
5199
5231
  }
5232
+ const delay = Math.round(retry.delayMs(attempt));
5233
+ const attemptNum = attempt + 1;
5234
+ logger.warn(`Provider retry ${attemptNum} in ${delay}ms \u2014 ${description}`);
5235
+ if (isProviderErr) {
5236
+ events.emit("provider.retry", {
5237
+ providerId: err.providerId,
5238
+ attempt: attemptNum,
5239
+ delayMs: delay,
5240
+ status: err.status,
5241
+ description
5242
+ });
5243
+ }
5244
+ await new Promise((resolve4, reject) => {
5245
+ let settled = false;
5246
+ const onAbort = () => {
5247
+ if (settled) return;
5248
+ settled = true;
5249
+ clearTimeout(t2);
5250
+ reject(new Error("aborted"));
5251
+ };
5252
+ const t2 = setTimeout(() => {
5253
+ if (settled) return;
5254
+ settled = true;
5255
+ clearTimeout(t2);
5256
+ signal.removeEventListener("abort", onAbort);
5257
+ resolve4();
5258
+ }, delay);
5259
+ if (signal.aborted) {
5260
+ onAbort();
5261
+ return;
5262
+ }
5263
+ signal.addEventListener("abort", onAbort, { once: true });
5264
+ });
5265
+ attempt++;
5200
5266
  }
5201
- if (startIdx > 0) {
5202
- toCollapse.push({ from: 0, to: startIdx - 1 });
5203
- }
5204
- toKeep.push({ from: startIdx, to: messages.length - 1, importance: "high" });
5205
- return {
5206
- kept: toKeep,
5207
- collapsed: toCollapse,
5208
- reasoning: `Fallback: kept last ${messages.length - startIdx} messages within ${budget} token budget`
5209
- };
5210
5267
  }
5211
- parseSelectorOutput(raw, messageCount) {
5212
- const jsonStart = raw.indexOf("{");
5213
- const jsonEnd = raw.lastIndexOf("}");
5214
- if (jsonStart === -1 || jsonEnd === -1) {
5215
- return this.fallbackSelect(
5216
- Array.from({ length: messageCount }, () => ({ role: "user", content: "" })),
5217
- this.maxContextTokens
5218
- );
5219
- }
5220
- let parsed;
5221
- try {
5222
- parsed = JSON.parse(raw.slice(jsonStart, jsonEnd + 1));
5223
- } catch {
5224
- return this.fallbackSelect(
5225
- Array.from({ length: messageCount }, () => ({ role: "user", content: "" })),
5226
- this.maxContextTokens
5227
- );
5228
- }
5229
- const obj = parsed;
5230
- const kept = obj.kept ?? [];
5231
- const collapsed = obj.collapsed ?? [];
5232
- return {
5233
- kept: kept.map((k) => ({
5234
- from: k.from,
5235
- to: k.to,
5236
- importance: k.importance ?? "medium"
5237
- })),
5238
- collapsed: collapsed.map((c) => ({ from: c.from, to: c.to, summary: c.summary })),
5239
- reasoning: typeof obj.reasoning === "string" ? obj.reasoning : ""
5240
- };
5268
+ }
5269
+
5270
+ // src/execution/provider-runner-impl.ts
5271
+ var DefaultProviderRunner = class {
5272
+ async run(opts) {
5273
+ return runProviderWithRetry(opts);
5241
5274
  }
5242
5275
  };
5243
5276
 
5244
- // src/execution/selective-compactor.ts
5245
- var SelectiveCompactor = class {
5277
+ // src/execution/intelligent-compactor.ts
5278
+ var IntelligentCompactor = class {
5246
5279
  provider;
5247
- selector;
5248
5280
  warnThreshold;
5249
5281
  softThreshold;
5250
5282
  hardThreshold;
5251
5283
  maxContext;
5252
5284
  preserveK;
5253
5285
  eliseThreshold;
5254
- summarizerModel;
5255
5286
  summarizerPrompt;
5287
+ summarizerModel;
5256
5288
  constructor(opts) {
5257
5289
  this.provider = opts.provider;
5258
- this.selector = opts.selector ?? new LLMSelector({ provider: opts.provider, model: opts.selectorModel });
5259
5290
  this.warnThreshold = opts.warnThreshold ?? 0.6;
5260
5291
  this.softThreshold = opts.softThreshold ?? 0.75;
5261
5292
  this.hardThreshold = opts.hardThreshold ?? 0.9;
5262
5293
  this.maxContext = opts.maxContext ?? 128e3;
5263
5294
  this.preserveK = opts.preserveK ?? 4;
5264
5295
  this.eliseThreshold = opts.eliseThreshold ?? 500;
5265
- this.summarizerModel = opts.summarizerModel ?? opts.selectorModel ?? "unknown";
5266
- this.summarizerPrompt = opts.summarizerPrompt ?? "You are a context summarizer. Given a list of messages, produce a concise summary that preserves all factual information, decisions, file changes, and state changes. Do not add commentary or opinions.";
5296
+ this.summarizerPrompt = opts.summarizerPrompt ?? "You are a context summarizer. Given a list of conversation messages, produce a concise but complete summary that preserves all factual information, decisions made, and any state changes (e.g. file edits, todo updates). Do not add commentary. Output only the summary.";
5297
+ this.summarizerModel = opts.summarizerModel;
5267
5298
  }
5268
5299
  async compact(ctx, opts = {}) {
5269
5300
  const beforeTokens = this.estimateTokens(ctx.messages);
5270
5301
  const reductions = [];
5271
5302
  const load = beforeTokens / this.maxContext;
5272
- const shouldCompact = load >= this.warnThreshold || opts.aggressive;
5273
- if (!shouldCompact) {
5274
- const saved = this.eliseOldToolResults(ctx);
5275
- if (saved > 0) reductions.push({ phase: "elision", saved });
5276
- const afterTokens2 = this.estimateTokens(ctx.messages);
5277
- return { before: beforeTokens, after: afterTokens2, reductions };
5278
- }
5279
- const savedElision = this.eliseOldToolResults(ctx);
5280
- if (savedElision > 0) reductions.push({ phase: "elision", saved: savedElision });
5281
- const afterPhase1 = this.estimateTokens(ctx.messages);
5282
- const targetBudget = this.computeTargetBudget(load);
5283
- if (afterPhase1 > targetBudget) {
5284
- const savedSelective = await this.runSelector(ctx, targetBudget);
5285
- if (savedSelective > 0) reductions.push({ phase: "selective", saved: savedSelective });
5303
+ const aggressive = load >= this.hardThreshold ? true : opts.aggressive ?? load >= this.softThreshold;
5304
+ const saved1 = this.eliseOldToolResults(ctx);
5305
+ if (saved1 > 0) reductions.push({ phase: "elision", saved: saved1 });
5306
+ if (aggressive) {
5307
+ const saved2 = await this.summarizeAncientTurns(ctx);
5308
+ if (saved2 > 0) reductions.push({ phase: "summary", saved: saved2 });
5309
+ } else if (load >= this.warnThreshold) {
5310
+ const saved2 = this.lightweightCompact(ctx);
5311
+ if (saved2 > 0) reductions.push({ phase: "elision", saved: saved2 });
5286
5312
  }
5287
5313
  const afterTokens = this.estimateTokens(ctx.messages);
5288
5314
  return { before: beforeTokens, after: afterTokens, reductions };
5289
5315
  }
5290
- /**
5291
- * Run the LLM selector to decide what to keep vs collapse.
5292
- * Returns the token savings achieved.
5293
- */
5294
- async runSelector(ctx, targetBudget) {
5295
- const before = this.estimateTokens(ctx.messages);
5296
- let result;
5316
+ async summarizeAncientTurns(ctx) {
5317
+ const messages = ctx.messages;
5318
+ const cutoff = Math.max(0, messages.length - this.preserveK * 2);
5319
+ if (cutoff <= 2) return 0;
5320
+ const boundary = this.findSafeBoundary(messages, 0, cutoff);
5321
+ if (boundary <= 1) return 0;
5322
+ const toSummarize = messages.slice(0, boundary);
5323
+ const removedTokens = this.estimateTokens(toSummarize);
5324
+ let summaryText;
5297
5325
  try {
5298
- result = await this.selector.select(ctx.messages, targetBudget);
5326
+ summaryText = await this.callSummarizer(toSummarize, ctx);
5299
5327
  } catch {
5300
- return this.aggressiveRecencyTrim(ctx);
5301
- }
5302
- await this.executePlan(ctx, result);
5303
- const after = this.estimateTokens(ctx.messages);
5304
- return Math.max(0, before - after);
5305
- }
5306
- /**
5307
- * Execute a SelectorResult plan: collapse/remove ranges and
5308
- * insert summaries where the selector provided them.
5309
- */
5310
- async executePlan(ctx, plan) {
5311
- if (ctx.messages.length === 0) return;
5312
- const messages = [...ctx.messages];
5313
- const sortedCollapsed = [...plan.collapsed].sort((a, b) => b.from - a.from);
5314
- for (const range of sortedCollapsed) {
5315
- if (range.from < 0 || range.to >= messages.length || range.from > range.to) continue;
5316
- let summary = range.summary;
5317
- if (!summary) {
5318
- const toSummarize = messages.slice(range.from, range.to + 1);
5319
- summary = await this.summarizeRange(toSummarize, ctx);
5320
- }
5321
- const summaryMsg = {
5322
- role: "system",
5323
- content: `[prior_turns_${range.from}-${range.to}: ${summary}]`
5324
- };
5325
- messages.splice(range.from, range.to - range.from + 1, summaryMsg);
5328
+ summaryText = `[${toSummarize.length} earlier turns omitted \u2014 key decisions and file states preserved in context]`;
5326
5329
  }
5327
- ctx.state.replaceMessages(messages);
5328
- }
5329
- async summarizeRange(messages, ctx) {
5330
- const systemText = `${this.summarizerPrompt}
5331
-
5332
- Summarize the following message range:`;
5333
- const body = messages.map((m, i) => `[${i}] ${m.role}: ${this.messagePreview(m)}`).join("\n");
5334
- const req = {
5335
- model: this.summarizerModel,
5336
- system: [{ type: "text", text: systemText }],
5337
- messages: [{ role: "user", content: body }],
5338
- maxTokens: 512
5330
+ const summaryMsg = {
5331
+ role: "system",
5332
+ content: `[prior_turns_summary: ${summaryText}]`
5339
5333
  };
5340
- try {
5341
- const res = await this.provider.complete(req, {
5342
- signal: ctx.signal ?? new AbortController().signal
5343
- });
5344
- return res.content.filter(isTextBlock).map((b) => b.text).join("\n").trim() || "(empty)";
5345
- } catch {
5346
- return `[${messages.length} earlier turns omitted]`;
5347
- }
5348
- }
5349
- messagePreview(m) {
5350
- if (typeof m.content === "string") return m.content.slice(0, 300);
5351
- return m.content.filter(isTextBlock).map((b) => b.text).join(" ").slice(0, 300);
5334
+ const summaryTokens = this.estimateTokens([summaryMsg]);
5335
+ const tail = ctx.messages.slice(boundary);
5336
+ ctx.state.replaceMessages([summaryMsg, ...tail]);
5337
+ return Math.max(0, removedTokens - summaryTokens);
5352
5338
  }
5353
- /**
5354
- * Fallback when selector fails: aggressively trim from the oldest end
5355
- * until we hit targetBudget.
5356
- */
5357
- aggressiveRecencyTrim(ctx) {
5358
- const messages = ctx.messages;
5359
- const preserveIdx = Math.max(0, messages.length - this.preserveK * 2);
5360
- if (preserveIdx <= 0) return 0;
5361
- let boundary = preserveIdx;
5362
- for (let i = preserveIdx; i < messages.length && i < preserveIdx + 6; i++) {
5339
+ findSafeBoundary(messages, from, to) {
5340
+ for (let i = to; i >= from; i--) {
5363
5341
  const m = messages[i];
5342
+ if (!m) continue;
5364
5343
  if (m.role === "user" && this.hasTextContent(m)) {
5365
- boundary = i;
5366
- break;
5344
+ return this.findExchangeStart(messages, i);
5367
5345
  }
5368
5346
  }
5369
- const removed = messages.slice(0, boundary);
5370
- const removedTokens = this.estimateTokens(removed);
5371
- const summaryMsg = {
5372
- role: "system",
5373
- content: `[${removed.length} earlier turns trimmed \u2014 see session log for details]`
5374
- };
5375
- const tail = messages.slice(boundary);
5376
- ctx.state.replaceMessages([summaryMsg, ...tail]);
5377
- return Math.max(0, removedTokens - this.estimateTokens([summaryMsg]));
5347
+ return -1;
5378
5348
  }
5379
- computeTargetBudget(load) {
5380
- if (load >= this.hardThreshold) {
5381
- return Math.floor(this.maxContext * 0.5);
5382
- }
5383
- if (load >= this.softThreshold) {
5384
- return Math.floor(this.maxContext * 0.65);
5349
+ findExchangeStart(messages, userIndex) {
5350
+ for (let i = userIndex - 1; i >= 0; i--) {
5351
+ const m = messages[i];
5352
+ if (!m) continue;
5353
+ if (m.role === "assistant") {
5354
+ const hasToolUse = Array.isArray(m.content) ? m.content.some((b) => b.type === "tool_use") : false;
5355
+ if (!hasToolUse) {
5356
+ return i + 1;
5357
+ }
5358
+ } else if (m.role !== "user") ; else {
5359
+ return i;
5360
+ }
5385
5361
  }
5386
- return Math.floor(this.maxContext * 0.75);
5362
+ return 0;
5363
+ }
5364
+ async callSummarizer(messages, ctx) {
5365
+ const prompt = [
5366
+ { type: "text", text: this.summarizerPrompt },
5367
+ { type: "text", text: "\n\nConversation to summarize:\n" },
5368
+ ...this.messagesToText(messages)
5369
+ ];
5370
+ const req = {
5371
+ model: this.summarizerModel ?? ctx.model,
5372
+ system: prompt,
5373
+ messages: [],
5374
+ maxTokens: 1024
5375
+ };
5376
+ const ac = ctx.signal ? void 0 : new AbortController();
5377
+ const signal = ctx.signal ?? ac.signal;
5378
+ const res = await this.provider.complete(req, { signal });
5379
+ const textBlocks = res.content.filter(isTextBlock);
5380
+ return textBlocks.map((b) => b.text).join("\n").trim() || "(empty summary)";
5381
+ }
5382
+ messagesToText(messages) {
5383
+ const lines = [];
5384
+ for (const m of messages) {
5385
+ const role = m.role.padEnd(10, " ");
5386
+ if (typeof m.content === "string") {
5387
+ lines.push(`[${role}]: ${m.content.slice(0, 500)}`);
5388
+ } else if (Array.isArray(m.content)) {
5389
+ const textParts = m.content.filter(isTextBlock).map((b) => b.text);
5390
+ if (textParts.length > 0) {
5391
+ lines.push(`[${role}]: ${textParts.join(" ").slice(0, 500)}`);
5392
+ }
5393
+ }
5394
+ }
5395
+ return [{ type: "text", text: lines.join("\n") }];
5396
+ }
5397
+ lightweightCompact(ctx) {
5398
+ return this.eliseOldToolResults(ctx);
5387
5399
  }
5388
5400
  eliseOldToolResults(ctx) {
5389
5401
  const messages = ctx.messages;
@@ -5412,8 +5424,7 @@ Summarize the following message range:`;
5412
5424
  }
5413
5425
  const newContent = msg.content.map((b) => {
5414
5426
  if (b.type !== "tool_result") return b;
5415
- const text = typeof b.content === "string" ? b.content : JSON.stringify(b.content);
5416
- const tokens = this.roughTokenEstimate(text);
5427
+ const tokens = estimateToolResultTokens(b.content);
5417
5428
  if (tokens < this.eliseThreshold) return b;
5418
5429
  saved += tokens;
5419
5430
  return {
@@ -5423,7 +5434,7 @@ Summarize the following message range:`;
5423
5434
  is_error: b.is_error
5424
5435
  };
5425
5436
  });
5426
- if (newContent.every((b, idx) => b === msg.content[idx])) {
5437
+ if (newContent.length === msg.content.length && newContent.every((b, idx) => b === msg.content[idx])) {
5427
5438
  nextMessages[i] = msg;
5428
5439
  } else {
5429
5440
  nextMessages[i] = { ...msg, content: newContent };
@@ -5441,610 +5452,984 @@ Summarize the following message range:`;
5441
5452
  let total = 0;
5442
5453
  for (const m of messages) {
5443
5454
  if (typeof m.content === "string") {
5444
- total += this.roughTokenEstimate(m.content);
5455
+ total += estimateTextTokens(m.content);
5445
5456
  } else {
5446
5457
  for (const b of m.content) {
5447
- if (b.type === "text") total += this.roughTokenEstimate(b.text);
5448
- else if (b.type === "tool_use") total += this.roughTokenEstimate(JSON.stringify(b.input));
5449
- else if (b.type === "tool_result") {
5450
- total += this.roughTokenEstimate(
5451
- typeof b.content === "string" ? b.content : JSON.stringify(b.content)
5452
- );
5453
- }
5458
+ if (b.type === "text") total += estimateTextTokens(b.text);
5459
+ else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
5460
+ else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
5454
5461
  }
5455
5462
  }
5456
5463
  }
5457
5464
  return total;
5458
5465
  }
5459
- roughTokenEstimate(text) {
5460
- return Math.max(1, Math.ceil(text.length / 4));
5461
- }
5462
5466
  };
5463
5467
 
5464
- // src/execution/auto-compaction-middleware.ts
5465
- var AutoCompactionMiddleware = class {
5466
- name = "AutoCompaction";
5467
- compactor;
5468
- warnThreshold;
5469
- softThreshold;
5470
- hardThreshold;
5471
- maxContext;
5472
- estimator;
5473
- aggressiveOn;
5474
- events;
5475
- failureMode;
5476
- /**
5477
- * @param compactor Compactor to use for compaction.
5478
- * @param maxContext Provider's max context window in tokens.
5479
- * @param estimator Token estimation function.
5480
- * @param thresholds Threshold fractions (0-1) of maxContext.
5481
- * @param opts Optional behavior. By default, failures at the
5482
- * hard threshold throw AGENT_CONTEXT_OVERFLOW so
5483
- * the agent does not continue into a likely
5484
- * provider context overflow. Warn/soft failures
5485
- * still emit compaction.failed and continue.
5486
- */
5487
- constructor(compactor, maxContext, estimator, thresholds, optsOrAggressiveOn = {}, events) {
5488
- const opts = typeof optsOrAggressiveOn === "string" ? { aggressiveOn: optsOrAggressiveOn, events } : optsOrAggressiveOn;
5489
- this.compactor = compactor;
5490
- this.maxContext = maxContext;
5491
- this.estimator = estimator;
5492
- this.warnThreshold = thresholds.warn;
5493
- this.softThreshold = thresholds.soft;
5494
- this.hardThreshold = thresholds.hard;
5495
- this.aggressiveOn = opts.aggressiveOn ?? "soft";
5496
- this.events = opts.events;
5497
- this.failureMode = opts.failureMode ?? "throw_on_hard";
5498
- }
5499
- handler() {
5500
- return async (ctx, next) => {
5501
- const tokens = this.estimator(ctx);
5502
- const load = tokens / this.maxContext;
5503
- if (load >= this.hardThreshold) {
5504
- await this.compact(ctx, true, { level: "hard", tokens, load });
5505
- } else if (load >= this.softThreshold) {
5506
- await this.compact(ctx, this.aggressiveOn !== "hard", { level: "soft", tokens, load });
5507
- } else if (load >= this.warnThreshold) {
5508
- await this.compact(ctx, false, { level: "warn", tokens, load });
5468
+ // src/models/llm-selector.ts
5469
+ var DEFAULT_SYSTEM_PROMPT = `You are a context pruning assistant. Given a conversation history and a token budget, decide which message ranges are worth keeping verbatim and which should be collapsed into summaries.
5470
+
5471
+ Output a JSON object with this structure:
5472
+ {
5473
+ "kept": [{"from": 0, "to": 5, "importance": "critical"}],
5474
+ "collapsed": [{"from": 6, "to": 20, "summary": "optional summary"}],
5475
+ "reasoning": "brief explanation of decisions"
5476
+ }
5477
+
5478
+ Importance tiers:
5479
+ - "critical": decisions, file edits, tool results that affect state, final answers
5480
+ - "high": substantive tool use, complex reasoning, non-obvious observations
5481
+ - "medium": routine exchanges, confirmations, straightforward Q&A
5482
+
5483
+ Rules:
5484
+ - Always keep the most recent K pairs (preserve recency)
5485
+ - Never collapse the final 2 user/assistant pairs (working memory)
5486
+ - Preserve tool results that modified files or had external effects
5487
+ - Collapse old, low-information exchanges (greetings, acknowledgements, etc.)
5488
+ - If unsure, keep rather than collapse (errors are more costly than waste)
5489
+
5490
+ Return ONLY the JSON object, no markdown, no explanation outside the JSON.`;
5491
+ function estimateTokens(messages) {
5492
+ let total = 0;
5493
+ for (const m of messages) {
5494
+ if (typeof m.content === "string") {
5495
+ total += Math.ceil(m.content.length / 4);
5496
+ } else if (Array.isArray(m.content)) {
5497
+ for (const b of m.content) {
5498
+ if (b.type === "text") total += Math.ceil(b.text.length / 4);
5499
+ else total += Math.ceil(JSON.stringify(b).length / 4);
5509
5500
  }
5510
- return next(ctx);
5511
- };
5501
+ }
5512
5502
  }
5513
- async compact(ctx, aggressive, pressure) {
5514
- try {
5515
- await this.compactor.compact(ctx, { aggressive });
5516
- } catch (err) {
5517
- const error = err instanceof Error ? err : new Error(String(err));
5518
- const fatal = this.failureMode === "throw" || this.failureMode === "throw_on_hard" && pressure.level === "hard";
5519
- this.events?.emit("compaction.failed", {
5520
- err: error,
5521
- aggressive,
5522
- level: pressure.level,
5523
- tokens: pressure.tokens,
5524
- maxContext: this.maxContext,
5525
- load: pressure.load,
5526
- fatal
5527
- });
5528
- if (fatal) {
5529
- throw new AgentError({
5530
- message: `Auto-compaction failed at ${pressure.level} threshold`,
5531
- code: "AGENT_CONTEXT_OVERFLOW",
5532
- recoverable: true,
5533
- context: {
5534
- level: pressure.level,
5535
- tokens: pressure.tokens,
5536
- maxContext: this.maxContext
5537
- },
5538
- cause: err
5539
- });
5503
+ return total;
5504
+ }
5505
+ function formatMessages(messages, maxChars = 8e3) {
5506
+ const lines = [];
5507
+ let used = 0;
5508
+ for (let i = 0; i < messages.length; i++) {
5509
+ const m = messages[i];
5510
+ const role = m.role.padEnd(10, " ");
5511
+ let text;
5512
+ if (typeof m.content === "string") {
5513
+ text = m.content.slice(0, 500);
5514
+ } else {
5515
+ const content = m.content;
5516
+ text = content.filter(isTextBlock).map((b) => b.text).join(" ");
5517
+ const toolUses = content.filter((b) => b.type === "tool_use");
5518
+ if (toolUses.length > 0) {
5519
+ text += ` [tools: ${toolUses.map((b) => b.name).join(", ")}]`;
5540
5520
  }
5541
5521
  }
5522
+ const line = `[${i}][${role}]: ${text}`;
5523
+ if (used + line.length > maxChars) break;
5524
+ lines.push(line);
5525
+ used += line.length;
5542
5526
  }
5543
- };
5527
+ return lines.join("\n");
5528
+ }
5529
+ var LLMSelector = class {
5530
+ provider;
5531
+ model;
5532
+ maxContextTokens;
5533
+ systemPrompt;
5534
+ constructor(opts) {
5535
+ this.provider = opts.provider;
5536
+ this.model = opts.model ?? "unknown";
5537
+ this.maxContextTokens = opts.maxContextTokens ?? 4e4;
5538
+ this.systemPrompt = opts.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
5539
+ }
5540
+ async select(messages, maxToKeep) {
5541
+ const effectiveBudget = Math.min(maxToKeep, this.maxContextTokens);
5542
+ const historyText = formatMessages(messages);
5543
+ const totalTokens = estimateTokens(messages);
5544
+ const systemText = `${this.systemPrompt}
5544
5545
 
5545
- // src/execution/autonomous-runner.ts
5546
- var DoneConditionChecker = class {
5547
- constructor(condition) {
5548
- this.condition = condition;
5549
- this.compiledRegex = condition.type === "output_match" && condition.pattern ? new RegExp(condition.pattern) : null;
5546
+ Conversation (${messages.length} messages, ~${totalTokens} tokens, budget: ${effectiveBudget}):
5547
+ `;
5548
+ const budgetInstruction = totalTokens > effectiveBudget ? `
5549
+
5550
+ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiveBudget}). You MUST collapse enough to fit. Prefer collapsing older/lower-importance ranges.` : "";
5551
+ const req = {
5552
+ model: this.model,
5553
+ system: [{ type: "text", text: systemText + budgetInstruction }],
5554
+ messages: [{ role: "user", content: historyText }],
5555
+ maxTokens: 1024
5556
+ };
5557
+ let raw;
5558
+ try {
5559
+ const ac = new AbortController();
5560
+ const res = await this.provider.complete(req, { signal: ac.signal });
5561
+ const textBlocks = res.content.filter(isTextBlock);
5562
+ raw = textBlocks.map((b) => b.text).join("\n").trim();
5563
+ } catch (err) {
5564
+ return this.fallbackSelect(messages, effectiveBudget);
5565
+ }
5566
+ return this.parseSelectorOutput(raw, messages.length);
5550
5567
  }
5551
- condition;
5552
- compiledRegex;
5553
- check(state) {
5554
- switch (this.condition.type) {
5555
- case "iterations":
5556
- if (this.condition.maxIterations && state.iterations >= this.condition.maxIterations) {
5557
- return {
5558
- done: true,
5559
- reason: `max iterations (${this.condition.maxIterations}) reached`,
5560
- ...state
5561
- };
5562
- }
5563
- break;
5564
- case "tool_calls":
5565
- if (this.condition.maxToolCalls && state.toolCalls >= this.condition.maxToolCalls) {
5566
- return {
5567
- done: true,
5568
- reason: `max tool calls (${this.condition.maxToolCalls}) reached`,
5569
- ...state
5570
- };
5571
- }
5572
- break;
5573
- case "output_match":
5574
- if (this.compiledRegex && state.lastOutput && this.compiledRegex.test(state.lastOutput)) {
5575
- return {
5576
- done: true,
5577
- reason: `output matched pattern "${this.condition.pattern}"`,
5578
- ...state
5579
- };
5580
- }
5568
+ fallbackSelect(messages, budget) {
5569
+ const toKeep = [];
5570
+ const toCollapse = [];
5571
+ let tokenCount = 0;
5572
+ let startIdx = 0;
5573
+ for (let i = messages.length - 1; i >= 0; i--) {
5574
+ const m = messages[i];
5575
+ const cost = typeof m.content === "string" ? Math.ceil(m.content.length / 4) : m.content.reduce(
5576
+ (acc, b) => acc + (b.type === "text" ? Math.ceil(b.text.length / 4) : Math.ceil(JSON.stringify(b).length / 4)),
5577
+ 0
5578
+ );
5579
+ if (tokenCount + cost <= budget) {
5580
+ tokenCount += cost;
5581
+ } else {
5582
+ startIdx = i + 1;
5581
5583
  break;
5584
+ }
5582
5585
  }
5583
- return { done: false, iterations: state.iterations, toolCalls: state.toolCalls };
5586
+ if (startIdx > 0) {
5587
+ toCollapse.push({ from: 0, to: startIdx - 1 });
5588
+ }
5589
+ toKeep.push({ from: startIdx, to: messages.length - 1, importance: "high" });
5590
+ return {
5591
+ kept: toKeep,
5592
+ collapsed: toCollapse,
5593
+ reasoning: `Fallback: kept last ${messages.length - startIdx} messages within ${budget} token budget`
5594
+ };
5595
+ }
5596
+ parseSelectorOutput(raw, messageCount) {
5597
+ const jsonStart = raw.indexOf("{");
5598
+ const jsonEnd = raw.lastIndexOf("}");
5599
+ if (jsonStart === -1 || jsonEnd === -1) {
5600
+ return this.fallbackSelect(
5601
+ Array.from({ length: messageCount }, () => ({ role: "user", content: "" })),
5602
+ this.maxContextTokens
5603
+ );
5604
+ }
5605
+ let parsed;
5606
+ try {
5607
+ parsed = JSON.parse(raw.slice(jsonStart, jsonEnd + 1));
5608
+ } catch {
5609
+ return this.fallbackSelect(
5610
+ Array.from({ length: messageCount }, () => ({ role: "user", content: "" })),
5611
+ this.maxContextTokens
5612
+ );
5613
+ }
5614
+ const obj = parsed;
5615
+ const kept = obj.kept ?? [];
5616
+ const collapsed = obj.collapsed ?? [];
5617
+ return {
5618
+ kept: kept.map((k) => ({
5619
+ from: k.from,
5620
+ to: k.to,
5621
+ importance: k.importance ?? "medium"
5622
+ })),
5623
+ collapsed: collapsed.map((c) => ({ from: c.from, to: c.to, summary: c.summary })),
5624
+ reasoning: typeof obj.reasoning === "string" ? obj.reasoning : ""
5625
+ };
5584
5626
  }
5585
5627
  };
5586
- var AutonomousRunner = class {
5628
+
5629
+ // src/execution/selective-compactor.ts
5630
+ var SelectiveCompactor = class {
5631
+ provider;
5632
+ selector;
5633
+ warnThreshold;
5634
+ softThreshold;
5635
+ hardThreshold;
5636
+ maxContext;
5637
+ preserveK;
5638
+ eliseThreshold;
5639
+ summarizerModel;
5640
+ summarizerPrompt;
5587
5641
  constructor(opts) {
5588
- this.opts = opts;
5589
- this.doneChecker = new DoneConditionChecker(opts.doneCondition);
5642
+ this.provider = opts.provider;
5643
+ this.selector = opts.selector ?? new LLMSelector({ provider: opts.provider, model: opts.selectorModel });
5644
+ this.warnThreshold = opts.warnThreshold ?? 0.6;
5645
+ this.softThreshold = opts.softThreshold ?? 0.75;
5646
+ this.hardThreshold = opts.hardThreshold ?? 0.9;
5647
+ this.maxContext = opts.maxContext ?? 128e3;
5648
+ this.preserveK = opts.preserveK ?? 4;
5649
+ this.eliseThreshold = opts.eliseThreshold ?? 500;
5650
+ this.summarizerModel = opts.summarizerModel ?? opts.selectorModel ?? "unknown";
5651
+ this.summarizerPrompt = opts.summarizerPrompt ?? "You are a context summarizer. Given a list of messages, produce a concise summary that preserves all factual information, decisions, file changes, and state changes. Do not add commentary or opinions.";
5590
5652
  }
5591
- opts;
5592
- iterations = 0;
5593
- toolCalls = 0;
5594
- lastOutput;
5595
- stopped = false;
5596
- doneChecker;
5597
- async run() {
5598
- const offToolExecuted = this.opts.agent.events?.on?.("tool.executed", () => {
5599
- this.toolCalls++;
5600
- });
5653
+ async compact(ctx, opts = {}) {
5654
+ const beforeTokens = this.estimateTokens(ctx.messages);
5655
+ const reductions = [];
5656
+ const load = beforeTokens / this.maxContext;
5657
+ const shouldCompact = load >= this.warnThreshold || opts.aggressive;
5658
+ if (!shouldCompact) {
5659
+ const saved = this.eliseOldToolResults(ctx);
5660
+ if (saved > 0) reductions.push({ phase: "elision", saved });
5661
+ const afterTokens2 = this.estimateTokens(ctx.messages);
5662
+ return { before: beforeTokens, after: afterTokens2, reductions };
5663
+ }
5664
+ const savedElision = this.eliseOldToolResults(ctx);
5665
+ if (savedElision > 0) reductions.push({ phase: "elision", saved: savedElision });
5666
+ const afterPhase1 = this.estimateTokens(ctx.messages);
5667
+ const targetBudget = this.computeTargetBudget(load);
5668
+ if (afterPhase1 > targetBudget) {
5669
+ const savedSelective = await this.runSelector(ctx, targetBudget);
5670
+ if (savedSelective > 0) reductions.push({ phase: "selective", saved: savedSelective });
5671
+ }
5672
+ const afterTokens = this.estimateTokens(ctx.messages);
5673
+ return { before: beforeTokens, after: afterTokens, reductions };
5674
+ }
5675
+ /**
5676
+ * Run the LLM selector to decide what to keep vs collapse.
5677
+ * Returns the token savings achieved.
5678
+ */
5679
+ async runSelector(ctx, targetBudget) {
5680
+ const before = this.estimateTokens(ctx.messages);
5681
+ let result;
5601
5682
  try {
5602
- return await this.runLoop();
5603
- } finally {
5604
- offToolExecuted?.();
5683
+ result = await this.selector.select(ctx.messages, targetBudget);
5684
+ } catch {
5685
+ return this.aggressiveRecencyTrim(ctx);
5605
5686
  }
5687
+ await this.executePlan(ctx, result);
5688
+ const after = this.estimateTokens(ctx.messages);
5689
+ return Math.max(0, before - after);
5606
5690
  }
5607
- async runLoop() {
5608
- while (!this.stopped) {
5609
- const check = this.doneChecker.check({
5610
- iterations: this.iterations,
5611
- toolCalls: this.toolCalls,
5612
- lastOutput: this.lastOutput
5613
- });
5614
- if (check.done) {
5615
- const result = {
5616
- status: "done",
5617
- iterations: this.iterations,
5618
- toolCalls: this.toolCalls,
5619
- reason: check.reason
5620
- };
5621
- this.opts.onDone?.(result);
5622
- return result;
5623
- }
5624
- this.opts.onIteration?.({ iteration: this.iterations, toolCalls: this.toolCalls });
5625
- const ctrl = new AbortController();
5626
- const timeout = setTimeout(() => ctrl.abort(), this.opts.iterationTimeoutMs ?? 3e4);
5627
- try {
5628
- const result = await this.opts.agent.run("", {
5629
- signal: ctrl.signal,
5630
- maxIterations: 1,
5631
- executionStrategy: "sequential"
5632
- });
5633
- this.iterations++;
5634
- if (result.status === "done") {
5635
- this.lastOutput = result.finalText;
5636
- }
5637
- if (result.status === "failed" || result.status === "aborted") {
5638
- const failedResult = {
5639
- status: result.status,
5640
- error: result.error,
5641
- iterations: this.iterations,
5642
- toolCalls: this.toolCalls
5643
- };
5644
- this.opts.onDone?.(failedResult);
5645
- return failedResult;
5646
- }
5647
- } catch (e) {
5648
- const isAbort = e instanceof DOMException && e.name === "AbortError" || e instanceof Error && e.name === "AbortError" || e instanceof Error && e.message.includes("iteration timeout");
5649
- if (isAbort) {
5650
- const timeoutResult = {
5651
- status: "failed",
5652
- error: toWrongStackError(e),
5653
- iterations: this.iterations,
5654
- toolCalls: this.toolCalls,
5655
- reason: "iteration timeout"
5656
- };
5657
- this.opts.onDone?.(timeoutResult);
5658
- return timeoutResult;
5659
- }
5660
- this.stopped = true;
5661
- const failedResult = {
5662
- status: "failed",
5663
- error: toWrongStackError(e),
5664
- iterations: this.iterations,
5665
- toolCalls: this.toolCalls,
5666
- reason: e instanceof Error ? e.message : String(e)
5667
- };
5668
- this.opts.onDone?.(failedResult);
5669
- return failedResult;
5670
- } finally {
5671
- clearTimeout(timeout);
5691
+ /**
5692
+ * Execute a SelectorResult plan: collapse/remove ranges and
5693
+ * insert summaries where the selector provided them.
5694
+ */
5695
+ async executePlan(ctx, plan) {
5696
+ if (ctx.messages.length === 0) return;
5697
+ const messages = [...ctx.messages];
5698
+ const sortedCollapsed = [...plan.collapsed].sort((a, b) => b.from - a.from);
5699
+ for (const range of sortedCollapsed) {
5700
+ if (range.from < 0 || range.to >= messages.length || range.from > range.to) continue;
5701
+ let summary = range.summary;
5702
+ if (!summary) {
5703
+ const toSummarize = messages.slice(range.from, range.to + 1);
5704
+ summary = await this.summarizeRange(toSummarize, ctx);
5672
5705
  }
5706
+ const summaryMsg = {
5707
+ role: "system",
5708
+ content: `[prior_turns_${range.from}-${range.to}: ${summary}]`
5709
+ };
5710
+ messages.splice(range.from, range.to - range.from + 1, summaryMsg);
5673
5711
  }
5674
- return {
5675
- status: "aborted",
5676
- iterations: this.iterations,
5677
- toolCalls: this.toolCalls,
5678
- reason: "stopped externally"
5679
- };
5680
- }
5681
- stop() {
5682
- this.stopped = true;
5712
+ ctx.state.replaceMessages(messages);
5683
5713
  }
5684
- };
5685
-
5686
- // src/coordination/director-prompts.ts
5687
- var DEFAULT_DIRECTOR_PREAMBLE = `You are the Director of a multi-agent fleet. You orchestrate worker
5688
- subagents by spawning them, assigning tasks, awaiting completions, and
5689
- rolling up their outputs into your next decision.
5714
+ async summarizeRange(messages, ctx) {
5715
+ const systemText = `${this.summarizerPrompt}
5690
5716
 
5691
- Core fleet tools available to you:
5692
- - spawn_subagent \u2014 create a worker with a chosen provider / model / role
5693
- - assign_task \u2014 hand a piece of work to a specific subagent
5694
- - await_tasks \u2014 block until named task ids complete (parallel-safe)
5695
- - ask_subagent \u2014 synchronously query a running subagent via the bridge
5696
- - roll_up \u2014 aggregate finished tasks into a markdown/json summary
5697
- - terminate_subagent \u2014 abort a stuck worker (use sparingly)
5698
- - fleet_status \u2014 snapshot of all subagents and pending tasks
5699
- - fleet_usage \u2014 token + cost breakdown per subagent and total
5700
-
5701
- Working rules:
5702
- 1. Decompose first. Before spawning, decide which sub-tasks are
5703
- independent and can run in parallel. Sequential work doesn't need a
5704
- subagent \u2014 do it yourself.
5705
- 2. Match worker to job. Cheap/fast model for triage, capable model for
5706
- synthesis. Different providers per sibling is allowed and encouraged.
5707
- 3. Always pair an assign with an await. Don't fire-and-forget; you owe
5708
- the user a single coherent answer at the end.
5709
- 4. Roll up before deciding. After await_tasks resolves, call roll_up so
5710
- the results are folded back into your context in a compact form.
5711
- 5. Budget is real. Check fleet_usage periodically. If a subagent is
5712
- thrashing, terminate it rather than letting cost climb silently.
5713
- 6. Never claim a subagent's work as your own without verifying it. If a
5714
- result looks wrong, ask_subagent for clarification before passing it
5715
- to the user.`;
5716
- var DEFAULT_SUBAGENT_BASELINE = `You are a subagent operating under a Director. You were spawned to handle
5717
- a specific slice of a larger plan \u2014 do that slice well and report back.
5718
-
5719
- Bridge contract:
5720
- - You have a parent (the Director). You may call \`request\` on the
5721
- parent bridge to ask a clarifying question. Use this sparingly; the
5722
- parent is also working.
5723
- - You MAY NOT request the parent's system prompt, tool list, or other
5724
- subagents' context. Those are not yours to read.
5725
- - Your final task output is what the Director sees. Be concise,
5726
- structured, and self-contained \u2014 assume the Director will paste your
5727
- output into its own context.`;
5728
- function composeDirectorPrompt(parts = {}) {
5729
- const sections = [];
5730
- const preamble = parts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
5731
- if (preamble && preamble.trim().length > 0) sections.push(preamble.trim());
5732
- if (parts.rosterSummary && parts.rosterSummary.trim().length > 0) {
5733
- sections.push(`Available roles you can spawn:
5734
- ${parts.rosterSummary.trim()}`);
5735
- }
5736
- if (parts.basePrompt && parts.basePrompt.trim().length > 0) {
5737
- sections.push(parts.basePrompt.trim());
5738
- }
5739
- return sections.join("\n\n");
5740
- }
5741
- function composeSubagentPrompt(parts = {}) {
5742
- const sections = [];
5743
- const baseline = parts.baseline ?? DEFAULT_SUBAGENT_BASELINE;
5744
- if (baseline && baseline.trim().length > 0) sections.push(baseline.trim());
5745
- if (parts.role && parts.role.trim().length > 0) {
5746
- sections.push(`Role:
5747
- ${parts.role.trim()}`);
5748
- }
5749
- if (parts.task && parts.task.trim().length > 0) {
5750
- sections.push(`Task:
5751
- ${parts.task.trim()}`);
5752
- }
5753
- if (parts.sharedScratchpad && parts.sharedScratchpad.trim().length > 0) {
5754
- sections.push(
5755
- `Shared notes:
5756
- A scratchpad shared with the rest of the fleet is mounted at \`${parts.sharedScratchpad.trim()}\`.
5757
- - Write your final findings as markdown files there (e.g. \`findings.md\`, \`security.md\`).
5758
- - Before starting, list the directory and read any sibling files relevant to your task \u2014 they may already contain context you can build on.
5759
- - Use stable filenames (one file per concern); overwrite instead of appending so the Director sees the latest state.`
5760
- );
5761
- }
5762
- if (parts.override && parts.override.trim().length > 0) {
5763
- sections.push(parts.override.trim());
5717
+ Summarize the following message range:`;
5718
+ const body = messages.map((m, i) => `[${i}] ${m.role}: ${this.messagePreview(m)}`).join("\n");
5719
+ const req = {
5720
+ model: this.summarizerModel,
5721
+ system: [{ type: "text", text: systemText }],
5722
+ messages: [{ role: "user", content: body }],
5723
+ maxTokens: 512
5724
+ };
5725
+ try {
5726
+ const res = await this.provider.complete(req, {
5727
+ signal: ctx.signal ?? new AbortController().signal
5728
+ });
5729
+ return res.content.filter(isTextBlock).map((b) => b.text).join("\n").trim() || "(empty)";
5730
+ } catch {
5731
+ return `[${messages.length} earlier turns omitted]`;
5732
+ }
5764
5733
  }
5765
- return sections.join("\n\n");
5766
- }
5767
- function rosterSummaryFromConfigs(roster) {
5768
- const lines = [];
5769
- for (const [roleId, cfg] of Object.entries(roster)) {
5770
- const tag = cfg.provider && cfg.model ? ` (${cfg.provider}/${cfg.model})` : "";
5771
- const headline = cfg.prompt ? (cfg.prompt.split("\n").find((l) => l.trim().length > 0) ?? "").trim().slice(0, 80) : "";
5772
- const tail = headline ? ` \u2014 ${headline}` : "";
5773
- lines.push(`- ${roleId}: ${cfg.name}${tag}${tail}`);
5734
+ messagePreview(m) {
5735
+ if (typeof m.content === "string") return m.content.slice(0, 300);
5736
+ return m.content.filter(isTextBlock).map((b) => b.text).join(" ").slice(0, 300);
5774
5737
  }
5775
- return lines.join("\n");
5776
- }
5777
-
5778
- // src/coordination/fleet-bus.ts
5779
- var FleetBus = class {
5780
- byId = /* @__PURE__ */ new Map();
5781
- byType = /* @__PURE__ */ new Map();
5782
- any = /* @__PURE__ */ new Set();
5783
5738
  /**
5784
- * Hook a subagent's EventBus into the fleet. EventBus is strongly
5785
- * typed and doesn't expose an `onAny` hook, so we subscribe to the
5786
- * canonical set of event types a subagent emits during a run. New
5787
- * event types added to the kernel must be added here too — but the
5788
- * cost is a tiny single line per type, and the explicit list keeps
5789
- * the wire format clear.
5790
- *
5791
- * Returns a disposer that detaches every subscription; call on
5792
- * subagent teardown so the listeners don't outlive the run.
5739
+ * Fallback when selector fails: aggressively trim from the oldest end
5740
+ * until we hit targetBudget.
5793
5741
  */
5794
- attach(subagentId, bus, taskId) {
5795
- const FORWARDED_TYPES = [
5796
- "tool.started",
5797
- "tool.executed",
5798
- "tool.progress",
5799
- "tool.confirm_needed",
5800
- "iteration.started",
5801
- "iteration.completed",
5802
- "provider.text_delta",
5803
- // Subagent extended-thinking output. Forwarded so the FleetPanel /
5804
- // /fleet log can surface "the planner is thinking…" instead of a
5805
- // silent gap between iteration.started and the first text_delta.
5806
- "provider.thinking_delta",
5807
- "provider.response",
5808
- "provider.retry",
5809
- "provider.error",
5810
- "session.started",
5811
- "session.ended",
5812
- "session.damaged",
5813
- "compaction.fired",
5814
- "compaction.failed",
5815
- "token.threshold"
5816
- ];
5817
- const offs = [];
5818
- for (const t2 of FORWARDED_TYPES) {
5819
- offs.push(
5820
- bus.on(t2, (payload) => {
5821
- this.emit({ subagentId, taskId, ts: Date.now(), type: t2, payload });
5822
- })
5823
- );
5742
+ aggressiveRecencyTrim(ctx) {
5743
+ const messages = ctx.messages;
5744
+ const preserveIdx = Math.max(0, messages.length - this.preserveK * 2);
5745
+ if (preserveIdx <= 0) return 0;
5746
+ let boundary = preserveIdx;
5747
+ for (let i = preserveIdx; i < messages.length && i < preserveIdx + 6; i++) {
5748
+ const m = messages[i];
5749
+ if (m.role === "user" && this.hasTextContent(m)) {
5750
+ boundary = i;
5751
+ break;
5752
+ }
5824
5753
  }
5825
- return () => {
5826
- for (const off of offs) off();
5754
+ const removed = messages.slice(0, boundary);
5755
+ const removedTokens = this.estimateTokens(removed);
5756
+ const summaryMsg = {
5757
+ role: "system",
5758
+ content: `[${removed.length} earlier turns trimmed \u2014 see session log for details]`
5827
5759
  };
5760
+ const tail = messages.slice(boundary);
5761
+ ctx.state.replaceMessages([summaryMsg, ...tail]);
5762
+ return Math.max(0, removedTokens - this.estimateTokens([summaryMsg]));
5828
5763
  }
5829
- /** Subscribe to every event from one subagent. */
5830
- subscribe(subagentId, handler) {
5831
- let set = this.byId.get(subagentId);
5832
- if (!set) {
5833
- set = /* @__PURE__ */ new Set();
5834
- this.byId.set(subagentId, set);
5764
+ computeTargetBudget(load) {
5765
+ if (load >= this.hardThreshold) {
5766
+ return Math.floor(this.maxContext * 0.5);
5835
5767
  }
5836
- set.add(handler);
5837
- return () => {
5838
- set.delete(handler);
5839
- };
5840
- }
5841
- /** Subscribe to one event type across all subagents. */
5842
- filter(type, handler) {
5843
- let set = this.byType.get(type);
5844
- if (!set) {
5845
- set = /* @__PURE__ */ new Set();
5846
- this.byType.set(type, set);
5768
+ if (load >= this.softThreshold) {
5769
+ return Math.floor(this.maxContext * 0.65);
5847
5770
  }
5848
- set.add(handler);
5849
- return () => {
5850
- set.delete(handler);
5851
- };
5852
- }
5853
- /** Subscribe to literally everything. The fleet roll-up uses this. */
5854
- onAny(handler) {
5855
- this.any.add(handler);
5856
- return () => {
5857
- this.any.delete(handler);
5858
- };
5771
+ return Math.floor(this.maxContext * 0.75);
5859
5772
  }
5860
- emit(event) {
5861
- const byId = this.byId.get(event.subagentId);
5862
- if (byId)
5863
- for (const h of byId) {
5864
- try {
5865
- h(event);
5866
- } catch {
5867
- }
5773
+ eliseOldToolResults(ctx) {
5774
+ const messages = ctx.messages;
5775
+ let pairCount = 0;
5776
+ let preserveStart = messages.length;
5777
+ for (let i = messages.length - 1; i >= 0 && pairCount < this.preserveK; i--) {
5778
+ const m = messages[i];
5779
+ if (!m) continue;
5780
+ if (m.role === "user" || m.role === "assistant") {
5781
+ pairCount++;
5782
+ preserveStart = i;
5868
5783
  }
5869
- const byType = this.byType.get(event.type);
5870
- if (byType)
5871
- for (const h of byType) {
5872
- try {
5873
- h(event);
5874
- } catch {
5875
- }
5784
+ }
5785
+ let saved = 0;
5786
+ let changed = false;
5787
+ const nextMessages = new Array(messages.length);
5788
+ for (let i = 0; i < messages.length; i++) {
5789
+ const msg = messages[i];
5790
+ if (i >= preserveStart) {
5791
+ nextMessages[i] = msg;
5792
+ continue;
5876
5793
  }
5877
- for (const h of this.any) {
5878
- try {
5879
- h(event);
5880
- } catch {
5794
+ if (!msg || !Array.isArray(msg.content)) {
5795
+ nextMessages[i] = msg;
5796
+ continue;
5797
+ }
5798
+ const newContent = msg.content.map((b) => {
5799
+ if (b.type !== "tool_result") return b;
5800
+ const text = typeof b.content === "string" ? b.content : JSON.stringify(b.content);
5801
+ const tokens = this.roughTokenEstimate(text);
5802
+ if (tokens < this.eliseThreshold) return b;
5803
+ saved += tokens;
5804
+ return {
5805
+ type: "tool_result",
5806
+ tool_use_id: b.tool_use_id,
5807
+ content: `[elided: ~${tokens} tokens]`,
5808
+ is_error: b.is_error
5809
+ };
5810
+ });
5811
+ if (newContent.every((b, idx) => b === msg.content[idx])) {
5812
+ nextMessages[i] = msg;
5813
+ } else {
5814
+ nextMessages[i] = { ...msg, content: newContent };
5815
+ changed = true;
5881
5816
  }
5882
5817
  }
5818
+ if (changed) ctx.state.replaceMessages(nextMessages);
5819
+ return saved;
5883
5820
  }
5884
- };
5885
- var FleetUsageAggregator = class {
5886
- constructor(bus, priceLookup, metaLookup) {
5887
- this.bus = bus;
5888
- this.priceLookup = priceLookup;
5889
- this.metaLookup = metaLookup;
5890
- bus.filter("provider.response", (e) => this.onProviderResponse(e));
5891
- bus.filter("tool.executed", (e) => this.onToolExecuted(e));
5892
- bus.filter("iteration.started", (e) => this.onIterationStarted(e));
5893
- }
5894
- bus;
5895
- priceLookup;
5896
- metaLookup;
5897
- perSubagent = /* @__PURE__ */ new Map();
5898
- total = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
5899
- /** Live snapshot — safe to call from a tool's execute() body. */
5900
- snapshot() {
5901
- return {
5902
- total: { ...this.total },
5903
- perSubagent: Object.fromEntries(
5904
- Array.from(this.perSubagent.entries()).map(([k, v]) => [k, { ...v }])
5905
- )
5906
- };
5907
- }
5908
- ensure(subagentId) {
5909
- let snap = this.perSubagent.get(subagentId);
5910
- if (!snap) {
5911
- const meta = this.metaLookup?.(subagentId);
5912
- snap = {
5913
- subagentId,
5914
- provider: meta?.provider,
5915
- model: meta?.model,
5916
- input: 0,
5917
- output: 0,
5918
- cacheRead: 0,
5919
- cacheWrite: 0,
5920
- cost: 0,
5921
- toolCalls: 0,
5922
- iterations: 0,
5923
- startedAt: Date.now(),
5924
- lastEventAt: Date.now()
5925
- };
5926
- this.perSubagent.set(subagentId, snap);
5927
- }
5928
- return snap;
5929
- }
5930
- onProviderResponse(e) {
5931
- const snap = this.ensure(e.subagentId);
5932
- const p = e.payload;
5933
- const usage = p?.usage;
5934
- if (!usage) return;
5935
- snap.input += usage.input ?? 0;
5936
- snap.output += usage.output ?? 0;
5937
- snap.cacheRead += usage.cacheRead ?? 0;
5938
- snap.cacheWrite += usage.cacheWrite ?? 0;
5939
- this.total.input += usage.input ?? 0;
5940
- this.total.output += usage.output ?? 0;
5941
- this.total.cacheRead += usage.cacheRead ?? 0;
5942
- this.total.cacheWrite += usage.cacheWrite ?? 0;
5943
- const price = this.priceLookup?.(e.subagentId);
5944
- if (price) {
5945
- 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);
5946
- snap.cost += delta;
5947
- this.total.cost += delta;
5948
- }
5949
- snap.lastEventAt = e.ts;
5821
+ hasTextContent(m) {
5822
+ if (typeof m.content === "string") return m.content.trim().length > 0;
5823
+ return m.content.some((b) => b.type === "text" && b.text.trim().length > 0);
5950
5824
  }
5951
- onToolExecuted(e) {
5952
- const snap = this.ensure(e.subagentId);
5953
- snap.toolCalls += 1;
5954
- snap.lastEventAt = e.ts;
5825
+ estimateTokens(messages) {
5826
+ let total = 0;
5827
+ for (const m of messages) {
5828
+ if (typeof m.content === "string") {
5829
+ total += this.roughTokenEstimate(m.content);
5830
+ } else {
5831
+ for (const b of m.content) {
5832
+ if (b.type === "text") total += this.roughTokenEstimate(b.text);
5833
+ else if (b.type === "tool_use") total += this.roughTokenEstimate(JSON.stringify(b.input));
5834
+ else if (b.type === "tool_result") {
5835
+ total += this.roughTokenEstimate(
5836
+ typeof b.content === "string" ? b.content : JSON.stringify(b.content)
5837
+ );
5838
+ }
5839
+ }
5840
+ }
5841
+ }
5842
+ return total;
5955
5843
  }
5956
- onIterationStarted(e) {
5957
- const snap = this.ensure(e.subagentId);
5958
- snap.iterations += 1;
5959
- snap.lastEventAt = e.ts;
5844
+ roughTokenEstimate(text) {
5845
+ return Math.max(1, Math.ceil(text.length / 4));
5960
5846
  }
5961
5847
  };
5962
5848
 
5963
- // src/coordination/subagent-budget.ts
5964
- var BudgetExceededError = class extends Error {
5965
- kind;
5966
- limit;
5967
- observed;
5968
- constructor(kind, limit, observed) {
5969
- super(`Budget exceeded: ${kind} (limit=${limit}, observed=${observed})`);
5970
- this.name = "BudgetExceededError";
5971
- this.kind = kind;
5972
- this.limit = limit;
5973
- this.observed = observed;
5974
- }
5975
- };
5976
- var SubagentBudget = class {
5977
- limits;
5978
- iterations = 0;
5979
- toolCalls = 0;
5980
- tokenInput = 0;
5981
- tokenOutput = 0;
5982
- costUsd = 0;
5983
- startTime = null;
5984
- constructor(limits = {}) {
5985
- this.limits = Object.freeze({ ...limits });
5986
- }
5987
- start() {
5988
- this.startTime = Date.now();
5989
- }
5990
- recordIteration() {
5991
- this.iterations++;
5992
- if (this.limits.maxIterations !== void 0 && this.iterations > this.limits.maxIterations) {
5993
- throw new BudgetExceededError("iterations", this.limits.maxIterations, this.iterations);
5994
- }
5995
- }
5996
- recordToolCall() {
5997
- this.toolCalls++;
5998
- if (this.limits.maxToolCalls !== void 0 && this.toolCalls > this.limits.maxToolCalls) {
5999
- throw new BudgetExceededError("tool_calls", this.limits.maxToolCalls, this.toolCalls);
6000
- }
6001
- }
6002
- recordUsage(usage, costUsd = 0) {
6003
- this.tokenInput += usage.input;
6004
- this.tokenOutput += usage.output;
6005
- this.costUsd += costUsd;
6006
- const totalTokens = this.tokenInput + this.tokenOutput;
6007
- if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
6008
- throw new BudgetExceededError("tokens", this.limits.maxTokens, totalTokens);
6009
- }
6010
- if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
6011
- throw new BudgetExceededError("cost", this.limits.maxCostUsd, this.costUsd);
6012
- }
6013
- }
5849
+ // src/execution/auto-compaction-middleware.ts
5850
+ var AutoCompactionMiddleware = class {
5851
+ name = "AutoCompaction";
5852
+ compactor;
5853
+ warnThreshold;
5854
+ softThreshold;
5855
+ hardThreshold;
5856
+ maxContext;
5857
+ estimator;
5858
+ aggressiveOn;
5859
+ events;
5860
+ failureMode;
6014
5861
  /**
6015
- * Throws if the wall-clock budget is exhausted. Call this from the iteration
6016
- * loop so a hung tool can't keep a subagent running past its deadline.
5862
+ * @param compactor Compactor to use for compaction.
5863
+ * @param maxContext Provider's max context window in tokens.
5864
+ * @param estimator Token estimation function.
5865
+ * @param thresholds Threshold fractions (0-1) of maxContext.
5866
+ * @param opts Optional behavior. By default, failures at the
5867
+ * hard threshold throw AGENT_CONTEXT_OVERFLOW so
5868
+ * the agent does not continue into a likely
5869
+ * provider context overflow. Warn/soft failures
5870
+ * still emit compaction.failed and continue.
6017
5871
  */
6018
- checkTimeout() {
6019
- if (this.startTime === null || this.limits.timeoutMs === void 0) return;
6020
- const elapsed = Date.now() - this.startTime;
6021
- if (elapsed > this.limits.timeoutMs) {
6022
- throw new BudgetExceededError("timeout", this.limits.timeoutMs, elapsed);
6023
- }
6024
- }
6025
- /** Returns true if a timeout has occurred without throwing. Useful for races. */
6026
- isTimedOut() {
6027
- if (this.startTime === null || this.limits.timeoutMs === void 0) return false;
6028
- return Date.now() - this.startTime > this.limits.timeoutMs;
5872
+ constructor(compactor, maxContext, estimator, thresholds, optsOrAggressiveOn = {}, events) {
5873
+ const opts = typeof optsOrAggressiveOn === "string" ? { aggressiveOn: optsOrAggressiveOn, events } : optsOrAggressiveOn;
5874
+ this.compactor = compactor;
5875
+ this.maxContext = maxContext;
5876
+ this.estimator = estimator;
5877
+ this.warnThreshold = thresholds.warn;
5878
+ this.softThreshold = thresholds.soft;
5879
+ this.hardThreshold = thresholds.hard;
5880
+ this.aggressiveOn = opts.aggressiveOn ?? "soft";
5881
+ this.events = opts.events;
5882
+ this.failureMode = opts.failureMode ?? "throw_on_hard";
6029
5883
  }
6030
- usage() {
6031
- return {
6032
- iterations: this.iterations,
6033
- toolCalls: this.toolCalls,
6034
- tokens: {
6035
- input: this.tokenInput,
6036
- output: this.tokenOutput,
6037
- total: this.tokenInput + this.tokenOutput
6038
- },
6039
- costUsd: this.costUsd,
6040
- elapsedMs: this.startTime === null ? 0 : Date.now() - this.startTime
5884
+ handler() {
5885
+ return async (ctx, next) => {
5886
+ const tokens = this.estimator(ctx);
5887
+ const load = tokens / this.maxContext;
5888
+ if (load >= this.hardThreshold) {
5889
+ await this.compact(ctx, true, { level: "hard", tokens, load });
5890
+ } else if (load >= this.softThreshold) {
5891
+ await this.compact(ctx, this.aggressiveOn !== "hard", { level: "soft", tokens, load });
5892
+ } else if (load >= this.warnThreshold) {
5893
+ await this.compact(ctx, false, { level: "warn", tokens, load });
5894
+ }
5895
+ return next(ctx);
6041
5896
  };
6042
5897
  }
5898
+ async compact(ctx, aggressive, pressure) {
5899
+ try {
5900
+ await this.compactor.compact(ctx, { aggressive });
5901
+ } catch (err) {
5902
+ const error = err instanceof Error ? err : new Error(String(err));
5903
+ const fatal = this.failureMode === "throw" || this.failureMode === "throw_on_hard" && pressure.level === "hard";
5904
+ this.events?.emit("compaction.failed", {
5905
+ err: error,
5906
+ aggressive,
5907
+ level: pressure.level,
5908
+ tokens: pressure.tokens,
5909
+ maxContext: this.maxContext,
5910
+ load: pressure.load,
5911
+ fatal
5912
+ });
5913
+ if (fatal) {
5914
+ throw new AgentError({
5915
+ message: `Auto-compaction failed at ${pressure.level} threshold`,
5916
+ code: "AGENT_CONTEXT_OVERFLOW",
5917
+ recoverable: true,
5918
+ context: {
5919
+ level: pressure.level,
5920
+ tokens: pressure.tokens,
5921
+ maxContext: this.maxContext
5922
+ },
5923
+ cause: err
5924
+ });
5925
+ }
5926
+ }
5927
+ }
6043
5928
  };
6044
5929
 
6045
- // src/coordination/multi-agent-coordinator.ts
6046
- var DefaultMultiAgentCoordinator = class extends EventEmitter {
6047
- coordinatorId;
5930
+ // src/execution/autonomous-runner.ts
5931
+ var DoneConditionChecker = class {
5932
+ constructor(condition) {
5933
+ this.condition = condition;
5934
+ this.compiledRegex = condition.type === "output_match" && condition.pattern ? new RegExp(condition.pattern) : null;
5935
+ }
5936
+ condition;
5937
+ compiledRegex;
5938
+ check(state) {
5939
+ switch (this.condition.type) {
5940
+ case "iterations":
5941
+ if (this.condition.maxIterations && state.iterations >= this.condition.maxIterations) {
5942
+ return {
5943
+ done: true,
5944
+ reason: `max iterations (${this.condition.maxIterations}) reached`,
5945
+ ...state
5946
+ };
5947
+ }
5948
+ break;
5949
+ case "tool_calls":
5950
+ if (this.condition.maxToolCalls && state.toolCalls >= this.condition.maxToolCalls) {
5951
+ return {
5952
+ done: true,
5953
+ reason: `max tool calls (${this.condition.maxToolCalls}) reached`,
5954
+ ...state
5955
+ };
5956
+ }
5957
+ break;
5958
+ case "output_match":
5959
+ if (this.compiledRegex && state.lastOutput && this.compiledRegex.test(state.lastOutput)) {
5960
+ return {
5961
+ done: true,
5962
+ reason: `output matched pattern "${this.condition.pattern}"`,
5963
+ ...state
5964
+ };
5965
+ }
5966
+ break;
5967
+ }
5968
+ return { done: false, iterations: state.iterations, toolCalls: state.toolCalls };
5969
+ }
5970
+ };
5971
+ var AutonomousRunner = class {
5972
+ constructor(opts) {
5973
+ this.opts = opts;
5974
+ this.doneChecker = new DoneConditionChecker(opts.doneCondition);
5975
+ }
5976
+ opts;
5977
+ iterations = 0;
5978
+ toolCalls = 0;
5979
+ lastOutput;
5980
+ stopped = false;
5981
+ doneChecker;
5982
+ async run() {
5983
+ const offToolExecuted = this.opts.agent.events?.on?.("tool.executed", () => {
5984
+ this.toolCalls++;
5985
+ });
5986
+ try {
5987
+ return await this.runLoop();
5988
+ } finally {
5989
+ offToolExecuted?.();
5990
+ }
5991
+ }
5992
+ async runLoop() {
5993
+ while (!this.stopped) {
5994
+ const check = this.doneChecker.check({
5995
+ iterations: this.iterations,
5996
+ toolCalls: this.toolCalls,
5997
+ lastOutput: this.lastOutput
5998
+ });
5999
+ if (check.done) {
6000
+ const result = {
6001
+ status: "done",
6002
+ iterations: this.iterations,
6003
+ toolCalls: this.toolCalls,
6004
+ reason: check.reason
6005
+ };
6006
+ this.opts.onDone?.(result);
6007
+ return result;
6008
+ }
6009
+ this.opts.onIteration?.({ iteration: this.iterations, toolCalls: this.toolCalls });
6010
+ const ctrl = new AbortController();
6011
+ const timeout = setTimeout(() => ctrl.abort(), this.opts.iterationTimeoutMs ?? 3e4);
6012
+ try {
6013
+ const result = await this.opts.agent.run("", {
6014
+ signal: ctrl.signal,
6015
+ maxIterations: 1,
6016
+ executionStrategy: "sequential"
6017
+ });
6018
+ this.iterations++;
6019
+ if (result.status === "done") {
6020
+ this.lastOutput = result.finalText;
6021
+ }
6022
+ if (result.status === "failed" || result.status === "aborted") {
6023
+ const failedResult = {
6024
+ status: result.status,
6025
+ error: result.error,
6026
+ iterations: this.iterations,
6027
+ toolCalls: this.toolCalls
6028
+ };
6029
+ this.opts.onDone?.(failedResult);
6030
+ return failedResult;
6031
+ }
6032
+ } catch (e) {
6033
+ const isAbort = e instanceof DOMException && e.name === "AbortError" || e instanceof Error && e.name === "AbortError" || e instanceof Error && e.message.includes("iteration timeout");
6034
+ if (isAbort) {
6035
+ const timeoutResult = {
6036
+ status: "failed",
6037
+ error: toWrongStackError(e),
6038
+ iterations: this.iterations,
6039
+ toolCalls: this.toolCalls,
6040
+ reason: "iteration timeout"
6041
+ };
6042
+ this.opts.onDone?.(timeoutResult);
6043
+ return timeoutResult;
6044
+ }
6045
+ this.stopped = true;
6046
+ const failedResult = {
6047
+ status: "failed",
6048
+ error: toWrongStackError(e),
6049
+ iterations: this.iterations,
6050
+ toolCalls: this.toolCalls,
6051
+ reason: e instanceof Error ? e.message : String(e)
6052
+ };
6053
+ this.opts.onDone?.(failedResult);
6054
+ return failedResult;
6055
+ } finally {
6056
+ clearTimeout(timeout);
6057
+ }
6058
+ }
6059
+ return {
6060
+ status: "aborted",
6061
+ iterations: this.iterations,
6062
+ toolCalls: this.toolCalls,
6063
+ reason: "stopped externally"
6064
+ };
6065
+ }
6066
+ stop() {
6067
+ this.stopped = true;
6068
+ }
6069
+ };
6070
+
6071
+ // src/coordination/director-prompts.ts
6072
+ var DEFAULT_DIRECTOR_PREAMBLE = `You are the Director of a multi-agent fleet. You orchestrate worker
6073
+ subagents by spawning them, assigning tasks, awaiting completions, and
6074
+ rolling up their outputs into your next decision.
6075
+
6076
+ Core fleet tools available to you:
6077
+ - spawn_subagent \u2014 create a worker with a chosen provider / model / role
6078
+ - assign_task \u2014 hand a piece of work to a specific subagent
6079
+ - await_tasks \u2014 block until named task ids complete (parallel-safe)
6080
+ - ask_subagent \u2014 synchronously query a running subagent via the bridge
6081
+ - roll_up \u2014 aggregate finished tasks into a markdown/json summary
6082
+ - terminate_subagent \u2014 abort a stuck worker (use sparingly)
6083
+ - fleet_status \u2014 snapshot of all subagents and pending tasks
6084
+ - fleet_usage \u2014 token + cost breakdown per subagent and total
6085
+
6086
+ Working rules:
6087
+ 1. Decompose first. Before spawning, decide which sub-tasks are
6088
+ independent and can run in parallel. Sequential work doesn't need a
6089
+ subagent \u2014 do it yourself.
6090
+ 2. Match worker to job. Cheap/fast model for triage, capable model for
6091
+ synthesis. Different providers per sibling is allowed and encouraged.
6092
+ 3. Always pair an assign with an await. Don't fire-and-forget; you owe
6093
+ the user a single coherent answer at the end.
6094
+ 4. Roll up before deciding. After await_tasks resolves, call roll_up so
6095
+ the results are folded back into your context in a compact form.
6096
+ 5. Budget is real. Check fleet_usage periodically. If a subagent is
6097
+ thrashing, terminate it rather than letting cost climb silently.
6098
+ 6. Never claim a subagent's work as your own without verifying it. If a
6099
+ result looks wrong, ask_subagent for clarification before passing it
6100
+ to the user.`;
6101
+ var DEFAULT_SUBAGENT_BASELINE = `You are a subagent operating under a Director. You were spawned to handle
6102
+ a specific slice of a larger plan \u2014 do that slice well and report back.
6103
+
6104
+ Bridge contract:
6105
+ - You have a parent (the Director). You may call \`request\` on the
6106
+ parent bridge to ask a clarifying question. Use this sparingly; the
6107
+ parent is also working.
6108
+ - You MAY NOT request the parent's system prompt, tool list, or other
6109
+ subagents' context. Those are not yours to read.
6110
+ - Your final task output is what the Director sees. Be concise,
6111
+ structured, and self-contained \u2014 assume the Director will paste your
6112
+ output into its own context.`;
6113
+ function composeDirectorPrompt(parts = {}) {
6114
+ const sections = [];
6115
+ const preamble = parts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
6116
+ if (preamble && preamble.trim().length > 0) sections.push(preamble.trim());
6117
+ if (parts.rosterSummary && parts.rosterSummary.trim().length > 0) {
6118
+ sections.push(`Available roles you can spawn:
6119
+ ${parts.rosterSummary.trim()}`);
6120
+ }
6121
+ if (parts.basePrompt && parts.basePrompt.trim().length > 0) {
6122
+ sections.push(parts.basePrompt.trim());
6123
+ }
6124
+ return sections.join("\n\n");
6125
+ }
6126
+ function composeSubagentPrompt(parts = {}) {
6127
+ const sections = [];
6128
+ const baseline = parts.baseline ?? DEFAULT_SUBAGENT_BASELINE;
6129
+ if (baseline && baseline.trim().length > 0) sections.push(baseline.trim());
6130
+ if (parts.role && parts.role.trim().length > 0) {
6131
+ sections.push(`Role:
6132
+ ${parts.role.trim()}`);
6133
+ }
6134
+ if (parts.task && parts.task.trim().length > 0) {
6135
+ sections.push(`Task:
6136
+ ${parts.task.trim()}`);
6137
+ }
6138
+ if (parts.sharedScratchpad && parts.sharedScratchpad.trim().length > 0) {
6139
+ sections.push(
6140
+ `Shared notes:
6141
+ A scratchpad shared with the rest of the fleet is mounted at \`${parts.sharedScratchpad.trim()}\`.
6142
+ - Write your final findings as markdown files there (e.g. \`findings.md\`, \`security.md\`).
6143
+ - Before starting, list the directory and read any sibling files relevant to your task \u2014 they may already contain context you can build on.
6144
+ - Use stable filenames (one file per concern); overwrite instead of appending so the Director sees the latest state.`
6145
+ );
6146
+ }
6147
+ if (parts.override && parts.override.trim().length > 0) {
6148
+ sections.push(parts.override.trim());
6149
+ }
6150
+ return sections.join("\n\n");
6151
+ }
6152
+ function rosterSummaryFromConfigs(roster) {
6153
+ const lines = [];
6154
+ for (const [roleId, cfg] of Object.entries(roster)) {
6155
+ const tag = cfg.provider && cfg.model ? ` (${cfg.provider}/${cfg.model})` : "";
6156
+ const headline = cfg.prompt ? (cfg.prompt.split("\n").find((l) => l.trim().length > 0) ?? "").trim().slice(0, 80) : "";
6157
+ const tail = headline ? ` \u2014 ${headline}` : "";
6158
+ lines.push(`- ${roleId}: ${cfg.name}${tag}${tail}`);
6159
+ }
6160
+ return lines.join("\n");
6161
+ }
6162
+
6163
+ // src/coordination/fleet-bus.ts
6164
+ var FleetBus = class {
6165
+ byId = /* @__PURE__ */ new Map();
6166
+ byType = /* @__PURE__ */ new Map();
6167
+ any = /* @__PURE__ */ new Set();
6168
+ /**
6169
+ * Hook a subagent's EventBus into the fleet. EventBus is strongly
6170
+ * typed and doesn't expose an `onAny` hook, so we subscribe to the
6171
+ * canonical set of event types a subagent emits during a run. New
6172
+ * event types added to the kernel must be added here too — but the
6173
+ * cost is a tiny single line per type, and the explicit list keeps
6174
+ * the wire format clear.
6175
+ *
6176
+ * Returns a disposer that detaches every subscription; call on
6177
+ * subagent teardown so the listeners don't outlive the run.
6178
+ */
6179
+ attach(subagentId, bus, taskId) {
6180
+ const FORWARDED_TYPES = [
6181
+ "tool.started",
6182
+ "tool.executed",
6183
+ "tool.progress",
6184
+ "tool.confirm_needed",
6185
+ "iteration.started",
6186
+ "iteration.completed",
6187
+ "provider.text_delta",
6188
+ // Subagent extended-thinking output. Forwarded so the FleetPanel /
6189
+ // /fleet log can surface "the planner is thinking…" instead of a
6190
+ // silent gap between iteration.started and the first text_delta.
6191
+ "provider.thinking_delta",
6192
+ "provider.response",
6193
+ "provider.retry",
6194
+ "provider.error",
6195
+ "session.started",
6196
+ "session.ended",
6197
+ "session.damaged",
6198
+ "compaction.fired",
6199
+ "compaction.failed",
6200
+ "token.threshold"
6201
+ ];
6202
+ const offs = [];
6203
+ for (const t2 of FORWARDED_TYPES) {
6204
+ offs.push(
6205
+ bus.on(t2, (payload) => {
6206
+ this.emit({ subagentId, taskId, ts: Date.now(), type: t2, payload });
6207
+ })
6208
+ );
6209
+ }
6210
+ return () => {
6211
+ for (const off of offs) off();
6212
+ };
6213
+ }
6214
+ /** Subscribe to every event from one subagent. */
6215
+ subscribe(subagentId, handler) {
6216
+ let set = this.byId.get(subagentId);
6217
+ if (!set) {
6218
+ set = /* @__PURE__ */ new Set();
6219
+ this.byId.set(subagentId, set);
6220
+ }
6221
+ set.add(handler);
6222
+ return () => {
6223
+ set.delete(handler);
6224
+ };
6225
+ }
6226
+ /** Subscribe to one event type across all subagents. */
6227
+ filter(type, handler) {
6228
+ let set = this.byType.get(type);
6229
+ if (!set) {
6230
+ set = /* @__PURE__ */ new Set();
6231
+ this.byType.set(type, set);
6232
+ }
6233
+ set.add(handler);
6234
+ return () => {
6235
+ set.delete(handler);
6236
+ };
6237
+ }
6238
+ /** Subscribe to literally everything. The fleet roll-up uses this. */
6239
+ onAny(handler) {
6240
+ this.any.add(handler);
6241
+ return () => {
6242
+ this.any.delete(handler);
6243
+ };
6244
+ }
6245
+ emit(event) {
6246
+ const byId = this.byId.get(event.subagentId);
6247
+ if (byId)
6248
+ for (const h of byId) {
6249
+ try {
6250
+ h(event);
6251
+ } catch {
6252
+ }
6253
+ }
6254
+ const byType = this.byType.get(event.type);
6255
+ if (byType)
6256
+ for (const h of byType) {
6257
+ try {
6258
+ h(event);
6259
+ } catch {
6260
+ }
6261
+ }
6262
+ for (const h of this.any) {
6263
+ try {
6264
+ h(event);
6265
+ } catch {
6266
+ }
6267
+ }
6268
+ }
6269
+ };
6270
+ var FleetUsageAggregator = class {
6271
+ constructor(bus, priceLookup, metaLookup) {
6272
+ this.bus = bus;
6273
+ this.priceLookup = priceLookup;
6274
+ this.metaLookup = metaLookup;
6275
+ bus.filter("provider.response", (e) => this.onProviderResponse(e));
6276
+ bus.filter("tool.executed", (e) => this.onToolExecuted(e));
6277
+ bus.filter("iteration.started", (e) => this.onIterationStarted(e));
6278
+ }
6279
+ bus;
6280
+ priceLookup;
6281
+ metaLookup;
6282
+ perSubagent = /* @__PURE__ */ new Map();
6283
+ total = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
6284
+ /** Live snapshot — safe to call from a tool's execute() body. */
6285
+ snapshot() {
6286
+ return {
6287
+ total: { ...this.total },
6288
+ perSubagent: Object.fromEntries(
6289
+ Array.from(this.perSubagent.entries()).map(([k, v]) => [k, { ...v }])
6290
+ )
6291
+ };
6292
+ }
6293
+ ensure(subagentId) {
6294
+ let snap = this.perSubagent.get(subagentId);
6295
+ if (!snap) {
6296
+ const meta = this.metaLookup?.(subagentId);
6297
+ snap = {
6298
+ subagentId,
6299
+ provider: meta?.provider,
6300
+ model: meta?.model,
6301
+ input: 0,
6302
+ output: 0,
6303
+ cacheRead: 0,
6304
+ cacheWrite: 0,
6305
+ cost: 0,
6306
+ toolCalls: 0,
6307
+ iterations: 0,
6308
+ startedAt: Date.now(),
6309
+ lastEventAt: Date.now()
6310
+ };
6311
+ this.perSubagent.set(subagentId, snap);
6312
+ }
6313
+ return snap;
6314
+ }
6315
+ onProviderResponse(e) {
6316
+ const snap = this.ensure(e.subagentId);
6317
+ const p = e.payload;
6318
+ const usage = p?.usage;
6319
+ if (!usage) return;
6320
+ snap.input += usage.input ?? 0;
6321
+ snap.output += usage.output ?? 0;
6322
+ snap.cacheRead += usage.cacheRead ?? 0;
6323
+ snap.cacheWrite += usage.cacheWrite ?? 0;
6324
+ this.total.input += usage.input ?? 0;
6325
+ this.total.output += usage.output ?? 0;
6326
+ this.total.cacheRead += usage.cacheRead ?? 0;
6327
+ this.total.cacheWrite += usage.cacheWrite ?? 0;
6328
+ const price = this.priceLookup?.(e.subagentId);
6329
+ if (price) {
6330
+ 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);
6331
+ snap.cost += delta;
6332
+ this.total.cost += delta;
6333
+ }
6334
+ snap.lastEventAt = e.ts;
6335
+ }
6336
+ onToolExecuted(e) {
6337
+ const snap = this.ensure(e.subagentId);
6338
+ snap.toolCalls += 1;
6339
+ snap.lastEventAt = e.ts;
6340
+ }
6341
+ onIterationStarted(e) {
6342
+ const snap = this.ensure(e.subagentId);
6343
+ snap.iterations += 1;
6344
+ snap.lastEventAt = e.ts;
6345
+ }
6346
+ };
6347
+
6348
+ // src/coordination/subagent-budget.ts
6349
+ var BudgetExceededError = class extends Error {
6350
+ kind;
6351
+ limit;
6352
+ observed;
6353
+ constructor(kind, limit, observed) {
6354
+ super(`Budget exceeded: ${kind} (limit=${limit}, observed=${observed})`);
6355
+ this.name = "BudgetExceededError";
6356
+ this.kind = kind;
6357
+ this.limit = limit;
6358
+ this.observed = observed;
6359
+ }
6360
+ };
6361
+ var SubagentBudget = class {
6362
+ limits;
6363
+ iterations = 0;
6364
+ toolCalls = 0;
6365
+ tokenInput = 0;
6366
+ tokenOutput = 0;
6367
+ costUsd = 0;
6368
+ startTime = null;
6369
+ constructor(limits = {}) {
6370
+ this.limits = Object.freeze({ ...limits });
6371
+ }
6372
+ start() {
6373
+ this.startTime = Date.now();
6374
+ }
6375
+ recordIteration() {
6376
+ this.iterations++;
6377
+ if (this.limits.maxIterations !== void 0 && this.iterations > this.limits.maxIterations) {
6378
+ throw new BudgetExceededError("iterations", this.limits.maxIterations, this.iterations);
6379
+ }
6380
+ }
6381
+ recordToolCall() {
6382
+ this.toolCalls++;
6383
+ if (this.limits.maxToolCalls !== void 0 && this.toolCalls > this.limits.maxToolCalls) {
6384
+ throw new BudgetExceededError("tool_calls", this.limits.maxToolCalls, this.toolCalls);
6385
+ }
6386
+ }
6387
+ recordUsage(usage, costUsd = 0) {
6388
+ this.tokenInput += usage.input;
6389
+ this.tokenOutput += usage.output;
6390
+ this.costUsd += costUsd;
6391
+ const totalTokens = this.tokenInput + this.tokenOutput;
6392
+ if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
6393
+ throw new BudgetExceededError("tokens", this.limits.maxTokens, totalTokens);
6394
+ }
6395
+ if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
6396
+ throw new BudgetExceededError("cost", this.limits.maxCostUsd, this.costUsd);
6397
+ }
6398
+ }
6399
+ /**
6400
+ * Throws if the wall-clock budget is exhausted. Call this from the iteration
6401
+ * loop so a hung tool can't keep a subagent running past its deadline.
6402
+ */
6403
+ checkTimeout() {
6404
+ if (this.startTime === null || this.limits.timeoutMs === void 0) return;
6405
+ const elapsed = Date.now() - this.startTime;
6406
+ if (elapsed > this.limits.timeoutMs) {
6407
+ throw new BudgetExceededError("timeout", this.limits.timeoutMs, elapsed);
6408
+ }
6409
+ }
6410
+ /** Returns true if a timeout has occurred without throwing. Useful for races. */
6411
+ isTimedOut() {
6412
+ if (this.startTime === null || this.limits.timeoutMs === void 0) return false;
6413
+ return Date.now() - this.startTime > this.limits.timeoutMs;
6414
+ }
6415
+ usage() {
6416
+ return {
6417
+ iterations: this.iterations,
6418
+ toolCalls: this.toolCalls,
6419
+ tokens: {
6420
+ input: this.tokenInput,
6421
+ output: this.tokenOutput,
6422
+ total: this.tokenInput + this.tokenOutput
6423
+ },
6424
+ costUsd: this.costUsd,
6425
+ elapsedMs: this.startTime === null ? 0 : Date.now() - this.startTime
6426
+ };
6427
+ }
6428
+ };
6429
+
6430
+ // src/coordination/multi-agent-coordinator.ts
6431
+ var DefaultMultiAgentCoordinator = class extends EventEmitter {
6432
+ coordinatorId;
6048
6433
  config;
6049
6434
  runner;
6050
6435
  subagents = /* @__PURE__ */ new Map();
@@ -9697,348 +10082,260 @@ var sentinelServer = () => ({
9697
10082
  // security tool — require explicit confirmation
9698
10083
  });
9699
10084
  var allServers = () => ({
9700
- filesystem: { ...filesystemServer(), enabled: false },
9701
- github: { ...githubServer(), enabled: false },
9702
- context7: { ...context7Server(), enabled: false },
9703
- "brave-search": { ...braveSearchServer(), enabled: false },
9704
- block: { ...blockServer(), enabled: false },
9705
- everart: { ...everArtServer(), enabled: false },
9706
- slack: { ...slackServer(), enabled: false },
9707
- aws: { ...awsServer(), enabled: false },
9708
- "google-maps": { ...googleMapsServer(), enabled: false },
9709
- sentinel: { ...sentinelServer(), enabled: false }
9710
- });
9711
-
9712
- // src/core/iteration-limit.ts
9713
- function requestLimitExtension(opts) {
9714
- const { events, currentIterations, currentLimit, autoExtend, timeoutMs = 3e4 } = opts;
9715
- return new Promise((resolve4) => {
9716
- let resolved = false;
9717
- const timer = setTimeout(() => {
9718
- if (!resolved) {
9719
- resolved = true;
9720
- resolve4(0);
9721
- }
9722
- }, timeoutMs);
9723
- const deny = () => {
9724
- if (!resolved) {
9725
- resolved = true;
9726
- clearTimeout(timer);
9727
- resolve4(0);
9728
- }
10085
+ filesystem: { ...filesystemServer(), enabled: false },
10086
+ github: { ...githubServer(), enabled: false },
10087
+ context7: { ...context7Server(), enabled: false },
10088
+ "brave-search": { ...braveSearchServer(), enabled: false },
10089
+ block: { ...blockServer(), enabled: false },
10090
+ everart: { ...everArtServer(), enabled: false },
10091
+ slack: { ...slackServer(), enabled: false },
10092
+ aws: { ...awsServer(), enabled: false },
10093
+ "google-maps": { ...googleMapsServer(), enabled: false },
10094
+ sentinel: { ...sentinelServer(), enabled: false }
10095
+ });
10096
+
10097
+ // src/extension/registry.ts
10098
+ var ExtensionRegistry = class {
10099
+ extensions = [];
10100
+ promptContributors = [];
10101
+ log;
10102
+ setLogger(log) {
10103
+ this.log = log;
10104
+ }
10105
+ /**
10106
+ * Register a system prompt contributor. Returns an unregister function.
10107
+ * Contributors are called on every system prompt build in registration
10108
+ * order. Their output blocks are inserted after the core environment
10109
+ * block, before the mode and plan blocks.
10110
+ */
10111
+ registerSystemPromptContributor(c) {
10112
+ this.promptContributors.push(c);
10113
+ return () => {
10114
+ const idx = this.promptContributors.indexOf(c);
10115
+ if (idx >= 0) this.promptContributors.splice(idx, 1);
9729
10116
  };
9730
- const grant = (extra) => {
9731
- if (!resolved) {
9732
- resolved = true;
9733
- clearTimeout(timer);
9734
- resolve4(Math.max(0, extra));
10117
+ }
10118
+ /**
10119
+ * Build all registered system prompt contributions.
10120
+ * Failures are caught and logged — one bad contributor doesn't
10121
+ * break the prompt assembly.
10122
+ */
10123
+ async buildSystemPromptContributions(ctx) {
10124
+ const blocks = [];
10125
+ for (const c of this.promptContributors) {
10126
+ try {
10127
+ const contributed = await c(ctx);
10128
+ blocks.push(...contributed);
10129
+ } catch (err) {
10130
+ this.log?.error("SystemPromptContributor failed", err);
9735
10131
  }
9736
- };
9737
- events.emit("iteration.limit_reached", {
9738
- currentIterations,
9739
- currentLimit,
9740
- grant,
9741
- deny
9742
- });
9743
- if (autoExtend) {
9744
- setImmediate(() => {
9745
- if (!resolved) {
9746
- resolved = true;
9747
- clearTimeout(timer);
9748
- resolve4(100);
9749
- }
9750
- });
9751
10132
  }
9752
- });
9753
- }
9754
-
9755
- // src/core/streaming-response-builder.ts
9756
- function buildResponse(state) {
9757
- const content = [];
9758
- for (const b of state.blockOrder) {
9759
- if (b.kind === "text") {
9760
- const txt = state.textBuffers[b.idx] ?? "";
9761
- if (txt) content.push({ type: "text", text: txt });
9762
- } else if (b.kind === "thinking") {
9763
- const t2 = state.thinking[b.idx];
9764
- if (!t2) continue;
9765
- if (!t2.textBuf && !t2.signature) continue;
9766
- const block = { type: "thinking", thinking: t2.textBuf };
9767
- if (t2.signature) block.signature = t2.signature;
9768
- if (t2.providerMeta && Object.keys(t2.providerMeta).length > 0) {
9769
- block.providerMeta = t2.providerMeta;
9770
- }
9771
- content.push(block);
9772
- } else {
9773
- const tb = state.tools.get(b.id);
9774
- if (tb) {
9775
- const block = {
9776
- type: "tool_use",
9777
- id: b.id,
9778
- name: tb.name,
9779
- input: tb.input ?? {}
9780
- };
9781
- if (tb.providerMeta && Object.keys(tb.providerMeta).length > 0) {
9782
- block.providerMeta = tb.providerMeta;
9783
- }
9784
- content.push(block);
9785
- }
10133
+ return blocks;
10134
+ }
10135
+ /**
10136
+ * Returns the live array of contributors (readonly snapshot for
10137
+ * passing to DefaultSystemPromptBuilder at build time).
10138
+ */
10139
+ listSystemPromptContributors() {
10140
+ return this.promptContributors;
10141
+ }
10142
+ /**
10143
+ * Register an extension. Duplicate names are rejected.
10144
+ * Returns an unregister function.
10145
+ */
10146
+ register(ext) {
10147
+ if (this.extensions.some((e) => e.name === ext.name)) {
10148
+ throw new WrongStackError({
10149
+ message: `Extension "${ext.name}" already registered`,
10150
+ code: "REGISTRY_DUPLICATE",
10151
+ subsystem: "container",
10152
+ context: { extension: ext.name }
10153
+ });
9786
10154
  }
10155
+ this.extensions.push(ext);
10156
+ return () => this.unregister(ext.name);
9787
10157
  }
9788
- if (content.length === 0) content.push({ type: "text", text: "" });
9789
- return { content, stopReason: state.stopReason, usage: state.usage, model: state.model };
9790
- }
9791
- function createStreamingState(model) {
9792
- return {
9793
- model,
9794
- stopReason: "end_turn",
9795
- usage: { input: 0, output: 0 },
9796
- textBuffers: [],
9797
- currentTextIndex: -1,
9798
- tools: /* @__PURE__ */ new Map(),
9799
- thinking: [],
9800
- currentThinkingIndex: -1,
9801
- blockOrder: []
9802
- };
9803
- }
9804
- function handleMessageStart(state, model) {
9805
- state.model = model;
9806
- }
9807
- function handleContentBlockStart(state, ev) {
9808
- const kind = ev.kind ?? "text";
9809
- if (kind === "text") {
9810
- state.currentTextIndex = state.textBuffers.length;
9811
- state.textBuffers.push("");
9812
- state.blockOrder.push({ kind: "text", idx: state.currentTextIndex });
9813
- } else if (kind === "tool_use") {
9814
- const id = ev.id ?? crypto.randomUUID();
9815
- state.tools.set(id, { name: ev.name ?? "unknown", partial: "" });
9816
- state.blockOrder.push({ kind: "tool", id });
9817
- state.currentTextIndex = -1;
9818
- } else if (kind === "thinking") {
9819
- state.currentThinkingIndex = state.thinking.length;
9820
- state.thinking.push({
9821
- textBuf: "",
9822
- ...ev.providerMeta ? { providerMeta: ev.providerMeta } : {}
9823
- });
9824
- state.blockOrder.push({ kind: "thinking", idx: state.currentThinkingIndex });
9825
- state.currentTextIndex = -1;
10158
+ /**
10159
+ * Register an extension, silently replacing any previous registration
10160
+ * with the same name. Use this when overriding a default extension.
10161
+ */
10162
+ registerOrReplace(ext) {
10163
+ const idx = this.extensions.findIndex((e) => e.name === ext.name);
10164
+ if (idx >= 0) this.extensions.splice(idx, 1);
10165
+ return this.register(ext);
9826
10166
  }
9827
- }
9828
- function handleContentBlockStop(state, ev) {
9829
- }
9830
- function handleTextDelta(state, text) {
9831
- if (state.currentTextIndex === -1) {
9832
- state.currentTextIndex = state.textBuffers.length;
9833
- state.textBuffers.push("");
9834
- state.blockOrder.push({ kind: "text", idx: state.currentTextIndex });
10167
+ /**
10168
+ * Unregister an extension by name. Returns true if found.
10169
+ */
10170
+ unregister(name) {
10171
+ const idx = this.extensions.findIndex((e) => e.name === name);
10172
+ if (idx === -1) return false;
10173
+ this.extensions.splice(idx, 1);
10174
+ return true;
9835
10175
  }
9836
- state.textBuffers[state.currentTextIndex] = (state.textBuffers[state.currentTextIndex] ?? "") + text;
9837
- }
9838
- function handleToolUseStart(state, ev) {
9839
- state.currentTextIndex = -1;
9840
- state.tools.set(ev.id, { name: ev.name, partial: "" });
9841
- state.blockOrder.push({ kind: "tool", id: ev.id });
9842
- }
9843
- function handleToolUseInputDelta(state, ev) {
9844
- const t2 = state.tools.get(ev.id);
9845
- if (t2) t2.partial += ev.partial;
9846
- }
9847
- function safeJsonOrRaw(s) {
9848
- if (!s) return {};
9849
- try {
9850
- return JSON.parse(s);
9851
- } catch {
9852
- return { _raw: s };
10176
+ /**
10177
+ * List registered extension names in order.
10178
+ */
10179
+ list() {
10180
+ return this.extensions.map((e) => e.name);
9853
10181
  }
9854
- }
9855
- function handleToolUseStop(state, ev) {
9856
- const t2 = state.tools.get(ev.id);
9857
- if (t2) {
9858
- t2.input = ev.input !== void 0 ? ev.input : safeJsonOrRaw(t2.partial);
9859
- if (ev.providerMeta) t2.providerMeta = ev.providerMeta;
10182
+ /**
10183
+ * Check if an extension with the given name is registered.
10184
+ */
10185
+ has(name) {
10186
+ return this.extensions.some((e) => e.name === name);
9860
10187
  }
9861
- state.currentTextIndex = -1;
9862
- }
9863
- function handleThinkingStart(state, ev) {
9864
- state.currentThinkingIndex = state.thinking.length;
9865
- state.thinking.push({
9866
- textBuf: "",
9867
- ...ev.providerMeta ? { providerMeta: ev.providerMeta } : {}
9868
- });
9869
- state.blockOrder.push({ kind: "thinking", idx: state.currentThinkingIndex });
9870
- state.currentTextIndex = -1;
9871
- }
9872
- function handleThinkingDelta(state, text) {
9873
- if (state.currentThinkingIndex === -1) {
9874
- handleThinkingStart(state, {});
10188
+ /**
10189
+ * Remove all registered extensions and contributors.
10190
+ */
10191
+ clear() {
10192
+ this.extensions.length = 0;
10193
+ this.promptContributors.length = 0;
9875
10194
  }
9876
- const t2 = state.thinking[state.currentThinkingIndex];
9877
- if (t2) t2.textBuf += text;
9878
- }
9879
- function handleThinkingSignature(state, signature) {
9880
- if (state.currentThinkingIndex === -1) {
9881
- handleThinkingStart(state, {});
10195
+ // ── Hook runners ─────────────────────────────────────────────────
10196
+ async runBeforeRun(...args) {
10197
+ for (const ext of this.extensions) {
10198
+ if (!ext.beforeRun) continue;
10199
+ try {
10200
+ await ext.beforeRun(...args);
10201
+ } catch (err) {
10202
+ this.log?.error(`Extension "${ext.name}" beforeRun hook failed`, err);
10203
+ }
10204
+ }
9882
10205
  }
9883
- const t2 = state.thinking[state.currentThinkingIndex];
9884
- if (t2) t2.signature = signature;
9885
- }
9886
- function handleThinkingStop(state) {
9887
- state.currentThinkingIndex = -1;
9888
- }
9889
- function handleMessageStop(state, ev) {
9890
- state.stopReason = ev.stopReason ?? "end_turn";
9891
- state.usage = ev.usage ?? { input: 0, output: 0 };
9892
- }
9893
- async function streamProviderToResponse(provider, req, signal, ctx, events) {
9894
- const state = createStreamingState(req.model);
9895
- const iter = provider.stream(req, { signal })[Symbol.asyncIterator]();
9896
- try {
9897
- for (; ; ) {
9898
- const next = await iter.next();
9899
- if (next.done) break;
9900
- const ev = next.value;
9901
- switch (ev.type) {
9902
- case "message_start":
9903
- handleMessageStart(state, ev.model);
9904
- break;
9905
- case "content_block_start":
9906
- handleContentBlockStart(state, ev);
9907
- break;
9908
- case "content_block_stop":
9909
- handleContentBlockStop(state, ev);
9910
- break;
9911
- case "text_delta":
9912
- handleTextDelta(state, ev.text);
9913
- events.emit("provider.text_delta", { ctx, text: ev.text });
9914
- break;
9915
- case "tool_use_start":
9916
- handleToolUseStart(state, ev);
9917
- events.emit("provider.tool_use_start", { ctx, id: ev.id, name: ev.name });
9918
- break;
9919
- case "tool_use_input_delta":
9920
- handleToolUseInputDelta(state, ev);
9921
- break;
9922
- case "tool_use_stop":
9923
- handleToolUseStop(state, ev);
9924
- events.emit("provider.tool_use_stop", { ctx, id: ev.id });
9925
- break;
9926
- case "thinking_start":
9927
- handleThinkingStart(state, ev);
9928
- break;
9929
- case "thinking_delta":
9930
- handleThinkingDelta(state, ev.text);
9931
- events.emit("provider.thinking_delta", { ctx, text: ev.text });
9932
- break;
9933
- case "thinking_signature":
9934
- handleThinkingSignature(state, ev.signature);
9935
- break;
9936
- case "thinking_stop":
9937
- handleThinkingStop(state);
9938
- break;
9939
- case "message_stop":
9940
- handleMessageStop(state, ev);
9941
- break;
10206
+ async runAfterRun(...args) {
10207
+ for (const ext of this.extensions) {
10208
+ if (!ext.afterRun) continue;
10209
+ try {
10210
+ await ext.afterRun(...args);
10211
+ } catch (err) {
10212
+ this.log?.error(`Extension "${ext.name}" afterRun hook failed`, err);
9942
10213
  }
9943
10214
  }
9944
- } catch (err) {
9945
- if (signal.aborted) {
9946
- state.stopReason = "end_turn";
9947
- return buildResponse(state);
10215
+ }
10216
+ async runBeforeIteration(...args) {
10217
+ for (const ext of this.extensions) {
10218
+ if (!ext.beforeIteration) continue;
10219
+ try {
10220
+ await ext.beforeIteration(...args);
10221
+ } catch (err) {
10222
+ this.log?.error(`Extension "${ext.name}" beforeIteration hook failed`, err);
10223
+ }
9948
10224
  }
9949
- throw err;
9950
- } finally {
9951
- try {
9952
- let drainTimer = null;
10225
+ }
10226
+ async runAfterIteration(...args) {
10227
+ for (const ext of this.extensions) {
10228
+ if (!ext.afterIteration) continue;
9953
10229
  try {
9954
- await Promise.race([
9955
- Promise.resolve(iter.return?.()),
9956
- new Promise((resolve4) => {
9957
- drainTimer = setTimeout(resolve4, 500);
9958
- })
9959
- ]);
9960
- } finally {
9961
- if (drainTimer) clearTimeout(drainTimer);
10230
+ await ext.afterIteration(...args);
10231
+ } catch (err) {
10232
+ this.log?.error(`Extension "${ext.name}" afterIteration hook failed`, err);
9962
10233
  }
9963
- } catch {
9964
10234
  }
9965
10235
  }
9966
- return buildResponse(state);
9967
- }
9968
-
9969
- // src/core/provider-runner.ts
9970
- async function runProviderWithRetry(opts) {
9971
- const { provider, request, signal, ctx, events, retry, logger, tracer } = opts;
9972
- let attempt = 0;
9973
- for (; ; ) {
9974
- const span = tracer?.startSpan("provider.complete", {
9975
- "provider.id": provider.id,
9976
- "provider.model": request.model,
9977
- "provider.streaming": provider.capabilities.streaming,
9978
- "provider.attempt": attempt
9979
- });
9980
- try {
9981
- const res = provider.capabilities.streaming ? await streamProviderToResponse(provider, request, signal, ctx, events) : await provider.complete(request, { signal });
9982
- span?.setAttribute("provider.stopReason", res.stopReason);
9983
- span?.setAttribute("provider.usage_in", res.usage.input);
9984
- span?.setAttribute("provider.usage_out", res.usage.output);
9985
- span?.end();
9986
- return res;
9987
- } catch (err) {
9988
- if (err instanceof Error) span?.recordError(err);
9989
- span?.end();
9990
- if (signal.aborted) throw err;
9991
- const isProviderErr = err instanceof ProviderError;
9992
- const errAsErr = err instanceof Error ? err : new Error(String(err));
9993
- const canRetry = retry.shouldRetry(isProviderErr ? err : errAsErr, attempt);
9994
- const description = isProviderErr ? err.describe() : errAsErr.message;
9995
- if (!canRetry) {
9996
- if (isProviderErr) {
9997
- events.emit("provider.error", {
9998
- providerId: err.providerId,
9999
- status: err.status,
10000
- description,
10001
- retryable: false
10002
- });
10236
+ /**
10237
+ * Run onError hooks in order. The first hook that returns a non-void
10238
+ * result wins; subsequent hooks are skipped.
10239
+ */
10240
+ async runOnError(...args) {
10241
+ for (const ext of this.extensions) {
10242
+ if (!ext.onError) continue;
10243
+ try {
10244
+ const result = await ext.onError(...args);
10245
+ if (result) return result;
10246
+ } catch (err) {
10247
+ this.log?.error(`Extension "${ext.name}" onError hook failed`, err);
10248
+ }
10249
+ }
10250
+ }
10251
+ /**
10252
+ * Build a composed provider runner. Extensions with `wrapProviderRunner`
10253
+ * form a middleware-style chain: the innermost extension wraps the
10254
+ * default runner, each subsequent wrapper wraps the previous.
10255
+ */
10256
+ wrapProviderRunner(inner) {
10257
+ const wrappers = this.extensions.filter((e) => e.wrapProviderRunner).map((e) => ({ name: e.name, wrap: e.wrapProviderRunner }));
10258
+ if (wrappers.length === 0) return inner;
10259
+ let composed = inner;
10260
+ for (let i = wrappers.length - 1; i >= 0; i--) {
10261
+ const wrapper = wrappers[i];
10262
+ const next = composed;
10263
+ composed = async (ctx, req) => {
10264
+ try {
10265
+ return await wrapper.wrap(ctx, req, next);
10266
+ } catch (err) {
10267
+ this.log?.error(`Extension "${wrapper.name}" wrapProviderRunner failed`, err);
10268
+ throw err;
10003
10269
  }
10004
- throw err;
10270
+ };
10271
+ }
10272
+ return composed;
10273
+ }
10274
+ async runBeforeToolExecution(...args) {
10275
+ let toolUses = args[1];
10276
+ for (const ext of this.extensions) {
10277
+ if (!ext.beforeToolExecution) continue;
10278
+ try {
10279
+ toolUses = await ext.beforeToolExecution(args[0], toolUses);
10280
+ } catch (err) {
10281
+ this.log?.error(`Extension "${ext.name}" beforeToolExecution hook failed`, err);
10005
10282
  }
10006
- const delay = Math.round(retry.delayMs(attempt));
10007
- const attemptNum = attempt + 1;
10008
- logger.warn(`Provider retry ${attemptNum} in ${delay}ms \u2014 ${description}`);
10009
- if (isProviderErr) {
10010
- events.emit("provider.retry", {
10011
- providerId: err.providerId,
10012
- attempt: attemptNum,
10013
- delayMs: delay,
10014
- status: err.status,
10015
- description
10016
- });
10283
+ }
10284
+ return toolUses;
10285
+ }
10286
+ async runAfterToolExecution(...args) {
10287
+ for (const ext of this.extensions) {
10288
+ if (!ext.afterToolExecution) continue;
10289
+ try {
10290
+ await ext.afterToolExecution(...args);
10291
+ } catch (err) {
10292
+ this.log?.error(`Extension "${ext.name}" afterToolExecution hook failed`, err);
10017
10293
  }
10018
- await new Promise((resolve4, reject) => {
10019
- let settled = false;
10020
- const onAbort = () => {
10021
- if (settled) return;
10022
- settled = true;
10023
- clearTimeout(t2);
10024
- reject(new Error("aborted"));
10025
- };
10026
- const t2 = setTimeout(() => {
10027
- if (settled) return;
10028
- settled = true;
10029
- clearTimeout(t2);
10030
- signal.removeEventListener("abort", onAbort);
10031
- resolve4();
10032
- }, delay);
10033
- if (signal.aborted) {
10034
- onAbort();
10035
- return;
10294
+ }
10295
+ }
10296
+ };
10297
+
10298
+ // src/core/iteration-limit.ts
10299
+ function requestLimitExtension(opts) {
10300
+ const { events, currentIterations, currentLimit, autoExtend, timeoutMs = 3e4 } = opts;
10301
+ return new Promise((resolve4) => {
10302
+ let resolved = false;
10303
+ const timer = setTimeout(() => {
10304
+ if (!resolved) {
10305
+ resolved = true;
10306
+ resolve4(0);
10307
+ }
10308
+ }, timeoutMs);
10309
+ const deny = () => {
10310
+ if (!resolved) {
10311
+ resolved = true;
10312
+ clearTimeout(timer);
10313
+ resolve4(0);
10314
+ }
10315
+ };
10316
+ const grant = (extra) => {
10317
+ if (!resolved) {
10318
+ resolved = true;
10319
+ clearTimeout(timer);
10320
+ resolve4(Math.max(0, extra));
10321
+ }
10322
+ };
10323
+ events.emit("iteration.limit_reached", {
10324
+ currentIterations,
10325
+ currentLimit,
10326
+ grant,
10327
+ deny
10328
+ });
10329
+ if (autoExtend) {
10330
+ setImmediate(() => {
10331
+ if (!resolved) {
10332
+ resolved = true;
10333
+ clearTimeout(timer);
10334
+ resolve4(100);
10036
10335
  }
10037
- signal.addEventListener("abort", onAbort, { once: true });
10038
10336
  });
10039
- attempt++;
10040
10337
  }
10041
- }
10338
+ });
10042
10339
  }
10043
10340
 
10044
10341
  // src/core/agent.ts
@@ -10075,6 +10372,7 @@ var Agent = class {
10075
10372
  toolExecutor;
10076
10373
  autoExtendLimit;
10077
10374
  tracer;
10375
+ extensions;
10078
10376
  constructor(init) {
10079
10377
  this.container = init.container;
10080
10378
  this.tools = init.tools;
@@ -10088,6 +10386,8 @@ var Agent = class {
10088
10386
  this.perIterationOutputCapBytes = init.perIterationOutputCapBytes ?? 1e5;
10089
10387
  this.autoExtendLimit = init.autoExtendLimit ?? true;
10090
10388
  this.tracer = init.tracer;
10389
+ this.extensions = init.extensions ?? new ExtensionRegistry();
10390
+ this.extensions.setLogger(this.container.resolve(TOKENS.Logger));
10091
10391
  this.toolExecutor = new ToolExecutor(this.tools, {
10092
10392
  permissionPolicy: init.permissionPolicy ?? this.permission,
10093
10393
  secretScrubber: this.scrubber,
@@ -10133,33 +10433,66 @@ var Agent = class {
10133
10433
  "agent.model": opts.model ?? this.ctx.model,
10134
10434
  "agent.executionStrategy": opts.executionStrategy ?? this.executionStrategy
10135
10435
  });
10436
+ const { blocks, text } = normalizeInput(userInput);
10437
+ const inputPayload = { content: blocks, text, ctx: this.ctx };
10438
+ await this.extensions.runBeforeRun(this.ctx, inputPayload);
10136
10439
  try {
10137
- const result = await this.runInner(userInput, opts, controller);
10440
+ const result = await this.runInner(inputPayload, opts, controller);
10138
10441
  span?.setAttribute("agent.status", result.status);
10139
10442
  span?.setAttribute("agent.iterations", result.iterations);
10443
+ await this.extensions.runAfterRun(this.ctx, result);
10140
10444
  return result;
10141
10445
  } catch (err) {
10142
10446
  const wse = err instanceof AgentError ? err : toWrongStackError(err);
10143
10447
  this.events.emit("error", { err: toError(err), phase: "agent" });
10144
10448
  if (err instanceof Error) span?.recordError(err);
10145
10449
  span?.setAttribute("agent.status", "failed");
10146
- return {
10450
+ const result = {
10147
10451
  status: signal.aborted ? "aborted" : "failed",
10148
10452
  iterations: 0,
10149
10453
  error: wse
10150
10454
  };
10455
+ await this.extensions.runAfterRun(this.ctx, result);
10456
+ return result;
10151
10457
  } finally {
10152
10458
  span?.end();
10153
10459
  await controller.dispose();
10154
10460
  }
10155
10461
  }
10156
- async runInner(userInput, opts, controller) {
10157
- await this.normalizeAndEmitUserInput(userInput);
10462
+ async runInner(inputPayload, opts, controller) {
10463
+ await this.pipelines.userInput.run(inputPayload);
10464
+ this.ctx.state.appendMessage({ role: "user", content: inputPayload.content });
10465
+ await this.ctx.session.append({
10466
+ type: "user_input",
10467
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
10468
+ content: inputPayload.content
10469
+ });
10158
10470
  let finalText = "";
10159
10471
  let iterations = 0;
10160
10472
  let effectiveLimit = opts.maxIterations ?? this.maxIterations;
10161
10473
  const hasHardLimit = effectiveLimit > 0 && Number.isFinite(effectiveLimit);
10162
10474
  let recoveryRetries = 0;
10475
+ const diRunner = this.container.has(TOKENS.ProviderRunner) ? this.container.resolve(TOKENS.ProviderRunner) : null;
10476
+ const baseRunner = diRunner ? (ctx, req) => diRunner.run({
10477
+ provider: ctx.provider,
10478
+ request: req,
10479
+ signal: controller.signal,
10480
+ ctx,
10481
+ events: this.events,
10482
+ retry: this.retry,
10483
+ logger: this.logger,
10484
+ tracer: this.tracer
10485
+ }) : async (ctx, req) => runProviderWithRetry({
10486
+ provider: ctx.provider,
10487
+ request: req,
10488
+ signal: controller.signal,
10489
+ ctx,
10490
+ events: this.events,
10491
+ retry: this.retry,
10492
+ logger: this.logger,
10493
+ tracer: this.tracer
10494
+ });
10495
+ const customRunner = this.extensions.wrapProviderRunner(baseRunner);
10163
10496
  for (let i = 0; ; i++) {
10164
10497
  iterations = i + 1;
10165
10498
  if (controller.signal.aborted) {
@@ -10175,26 +10508,39 @@ var Agent = class {
10175
10508
  if (limitCheck.exit) {
10176
10509
  return { ...limitCheck.exit, finalText };
10177
10510
  }
10511
+ await this.extensions.runBeforeIteration(this.ctx, i);
10178
10512
  this.events.emit("iteration.started", { ctx: this.ctx, index: i });
10179
10513
  const req = await this.buildAndRunRequestPipeline(opts);
10180
10514
  let res;
10181
10515
  try {
10182
- res = await runProviderWithRetry({
10183
- provider: this.ctx.provider,
10184
- request: req,
10185
- signal: controller.signal,
10186
- ctx: this.ctx,
10187
- events: this.events,
10188
- retry: this.retry,
10189
- logger: this.logger,
10190
- tracer: this.tracer
10191
- });
10516
+ res = await customRunner(this.ctx, req);
10192
10517
  recoveryRetries = 0;
10193
10518
  } catch (err) {
10194
10519
  if (controller.signal.aborted) {
10195
10520
  this.events.emit("error", { err: toError(err), phase: "provider" });
10196
10521
  return { status: "aborted", iterations, error: toWrongStackError(err, "AGENT_ABORTED") };
10197
10522
  }
10523
+ const extDecision = await this.extensions.runOnError(this.ctx, err, "provider", i);
10524
+ if (extDecision) {
10525
+ if (extDecision.action === "fail") {
10526
+ this.events.emit("error", { err: toError(err), phase: "provider" });
10527
+ return { status: "failed", iterations, error: toWrongStackError(err) };
10528
+ }
10529
+ if (extDecision.action === "continue") {
10530
+ await this.extensions.runAfterIteration(this.ctx, i);
10531
+ continue;
10532
+ }
10533
+ if (extDecision.action === "retry") {
10534
+ recoveryRetries++;
10535
+ if (recoveryRetries > 2) {
10536
+ this.events.emit("error", { err: toError(err), phase: "provider" });
10537
+ return { status: "failed", iterations, error: toWrongStackError(err) };
10538
+ }
10539
+ if (extDecision.model) this.ctx.model = extDecision.model;
10540
+ this.logger.info("Extension requested retry; retrying turn");
10541
+ continue;
10542
+ }
10543
+ }
10198
10544
  const recovered = await this.errorHandler.recover(err, this.ctx);
10199
10545
  if (!recovered || recovered.action === "fail") {
10200
10546
  this.events.emit("error", { err: toError(err), phase: "provider" });
@@ -10233,21 +10579,9 @@ var Agent = class {
10233
10579
  await this.executeTools(toolUses);
10234
10580
  this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
10235
10581
  await this.compactContextIfNeeded();
10582
+ await this.extensions.runAfterIteration(this.ctx, i);
10236
10583
  }
10237
10584
  }
10238
- /**
10239
- * Normalize user input and emit through userInput pipeline + session append.
10240
- */
10241
- async normalizeAndEmitUserInput(userInput) {
10242
- const { blocks, text } = normalizeInput(userInput);
10243
- await this.pipelines.userInput.run({ content: blocks, text, ctx: this.ctx });
10244
- this.ctx.state.appendMessage({ role: "user", content: blocks });
10245
- await this.ctx.session.append({
10246
- type: "user_input",
10247
- ts: (/* @__PURE__ */ new Date()).toISOString(),
10248
- content: blocks
10249
- });
10250
- }
10251
10585
  /**
10252
10586
  * Check if iteration limit has been reached and request extension if needed.
10253
10587
  * Returns the new effective limit (possibly extended) and a RunResult if
@@ -10331,6 +10665,7 @@ var Agent = class {
10331
10665
  * single tool.
10332
10666
  */
10333
10667
  async executeTools(toolUses) {
10668
+ toolUses = await this.extensions.runBeforeToolExecution(this.ctx, toolUses);
10334
10669
  const { outputs } = await this.toolExecutor.executeBatch(
10335
10670
  toolUses,
10336
10671
  this.ctx,
@@ -10416,6 +10751,7 @@ var Agent = class {
10416
10751
  role: "user",
10417
10752
  content: outputs.map((o) => o.result)
10418
10753
  });
10754
+ await this.extensions.runAfterToolExecution(this.ctx, outputs);
10419
10755
  }
10420
10756
  waitForConfirm(info) {
10421
10757
  return new Promise((resolve4) => {
@@ -10870,6 +11206,15 @@ var DefaultSystemPromptBuilder = class {
10870
11206
  cache_control: { type: "ephemeral" }
10871
11207
  });
10872
11208
  }
11209
+ if (this.opts.contributors && this.opts.contributors.length > 0) {
11210
+ for (const c of this.opts.contributors) {
11211
+ try {
11212
+ const contributed = await c(ctx);
11213
+ blocks.push(...contributed);
11214
+ } catch {
11215
+ }
11216
+ }
11217
+ }
10873
11218
  return blocks;
10874
11219
  }
10875
11220
  /**
@@ -10911,12 +11256,38 @@ var DefaultSystemPromptBuilder = class {
10911
11256
  }
10912
11257
  buildToolUsage(tools) {
10913
11258
  if (tools.length === 0) return "## Tool usage\n\nNo tools registered.";
10914
- const lines = ["## Tool usage"];
11259
+ const byCat = /* @__PURE__ */ new Map();
11260
+ const uncategorized = [];
10915
11261
  for (const t2 of tools) {
10916
- const hint = t2.usageHint ?? t2.description;
11262
+ if (t2.category) {
11263
+ let group = byCat.get(t2.category);
11264
+ if (!group) {
11265
+ group = [];
11266
+ byCat.set(t2.category, group);
11267
+ }
11268
+ group.push(t2);
11269
+ } else {
11270
+ uncategorized.push(t2);
11271
+ }
11272
+ }
11273
+ const lines = ["## Tool usage"];
11274
+ for (const [cat, catTools] of byCat) {
10917
11275
  lines.push(`
11276
+ ### ${cat}`);
11277
+ for (const t2 of catTools) {
11278
+ const hint = t2.usageHint ?? t2.description;
11279
+ const desc = hint.length > 80 ? `${hint.slice(0, 77)}...` : hint.trim();
11280
+ lines.push(`- **${t2.name}** \u2014 ${desc}`);
11281
+ }
11282
+ }
11283
+ if (uncategorized.length > 0) {
11284
+ if (byCat.size > 0) lines.push("");
11285
+ for (const t2 of uncategorized) {
11286
+ const hint = t2.usageHint ?? t2.description;
11287
+ lines.push(`
10918
11288
  ### ${t2.name}
10919
11289
  ${hint.trim()}`);
11290
+ }
10920
11291
  }
10921
11292
  lines.push(`
10922
11293
  ## Common patterns
@@ -11188,6 +11559,21 @@ var ToolRegistry = class {
11188
11559
  this.tools.set(tool.name, { tool, owner });
11189
11560
  return true;
11190
11561
  }
11562
+ /**
11563
+ * Bulk-register multiple tools at once. Each tool that conflicts with an
11564
+ * existing registration is silently skipped — use `registerAllOrThrow`
11565
+ * if you want it to throw on conflicts.
11566
+ */
11567
+ registerAll(tools, owner = "core") {
11568
+ for (const tool of tools) this.tryRegister(tool, owner);
11569
+ }
11570
+ /**
11571
+ * Bulk-register and throw on the first conflict. Use when you need
11572
+ * strict registration (e.g. at boot time).
11573
+ */
11574
+ registerAllOrThrow(tools, owner = "core") {
11575
+ for (const tool of tools) this.register(tool, owner);
11576
+ }
11191
11577
  /**
11192
11578
  * Register a tool as a default. If the tool name is already registered,
11193
11579
  * this is a no-op — the existing registration (from core or another
@@ -11215,6 +11601,30 @@ var ToolRegistry = class {
11215
11601
  }
11216
11602
  this.tools.set(name, { tool, owner });
11217
11603
  }
11604
+ /**
11605
+ * Wrap (decorate) an existing tool. The wrapper receives the current
11606
+ * tool and must return a new tool — typically the same tool with a
11607
+ * wrapped `execute` or `executeStream`. Throws if the tool is not
11608
+ * registered.
11609
+ *
11610
+ * Multiple wraps stack: each wrapper gets the output of the previous.
11611
+ *
11612
+ * @example
11613
+ * registry.wrap('bash', (t) => ({ ...t, permission: 'confirm' }));
11614
+ */
11615
+ wrap(name, wrapper, owner = "core") {
11616
+ const entry = this.tools.get(name);
11617
+ if (!entry) {
11618
+ throw new WrongStackError({
11619
+ message: `Tool "${name}" not registered; cannot wrap`,
11620
+ code: "REGISTRY_NOT_FOUND",
11621
+ subsystem: "container",
11622
+ context: { tool: name }
11623
+ });
11624
+ }
11625
+ const wrapped = wrapper(entry.tool);
11626
+ this.tools.set(name, { tool: wrapped, owner: `${entry.owner}+${owner}` });
11627
+ }
11218
11628
  get(name) {
11219
11629
  return this.tools.get(name)?.tool;
11220
11630
  }
@@ -11224,6 +11634,24 @@ var ToolRegistry = class {
11224
11634
  list() {
11225
11635
  return Array.from(this.tools.values()).map((e) => e.tool);
11226
11636
  }
11637
+ /**
11638
+ * Group tools by their `category` field. Tools without a category
11639
+ * are placed under the key `""` (empty string). Returns a Map of
11640
+ * category → tools, sorted by registration order within each category.
11641
+ */
11642
+ listByCategory() {
11643
+ const map = /* @__PURE__ */ new Map();
11644
+ for (const { tool } of this.tools.values()) {
11645
+ const cat = tool.category ?? "";
11646
+ let group = map.get(cat);
11647
+ if (!group) {
11648
+ group = [];
11649
+ map.set(cat, group);
11650
+ }
11651
+ group.push(tool);
11652
+ }
11653
+ return map;
11654
+ }
11227
11655
  listWithOwner() {
11228
11656
  return Array.from(this.tools.values());
11229
11657
  }
@@ -11243,6 +11671,12 @@ var ProviderRegistry = class {
11243
11671
  register(f) {
11244
11672
  this.factories.set(f.type, f);
11245
11673
  }
11674
+ /**
11675
+ * Bulk-register multiple provider factories at once.
11676
+ */
11677
+ registerAll(factories) {
11678
+ for (const f of factories) this.register(f);
11679
+ }
11246
11680
  /**
11247
11681
  * Override an existing factory. Throws if no factory is registered
11248
11682
  * for the given type. Use this to safely replace a provider at runtime
@@ -11311,6 +11745,12 @@ var SlashCommandRegistry = class {
11311
11745
  }
11312
11746
  return this.cmds.delete(name);
11313
11747
  }
11748
+ /**
11749
+ * Bulk-register multiple slash commands at once.
11750
+ */
11751
+ registerAll(cmds, owner = "core") {
11752
+ for (const cmd of cmds) this.register(cmd, owner);
11753
+ }
11314
11754
  get(name) {
11315
11755
  return this.cmds.get(name)?.cmd;
11316
11756
  }
@@ -11383,15 +11823,23 @@ var DefaultPluginAPI = class {
11383
11823
  providers;
11384
11824
  mcp;
11385
11825
  slashCommands;
11826
+ extensions;
11827
+ session;
11828
+ metrics;
11386
11829
  config;
11387
11830
  log;
11831
+ configStore;
11388
11832
  pluginCleanupFns = [];
11389
11833
  constructor(init) {
11390
11834
  const owner = init.ownerName;
11391
11835
  this.container = init.container;
11392
11836
  this.events = init.events;
11393
11837
  this.config = init.config;
11838
+ this.configStore = init.configStore;
11394
11839
  this.log = init.log.child({ plugin: owner });
11840
+ this.extensions = init.extensions ?? new ExtensionRegistry();
11841
+ this.session = init.sessionWriter ?? noopSession;
11842
+ this.metrics = init.metricsSink ? scopedMetrics(init.metricsSink, owner) : noopMetrics;
11395
11843
  const pipelines = init.pipelines;
11396
11844
  const readonlyPipelines = {};
11397
11845
  for (const [key, pipeline] of Object.entries(pipelines)) {
@@ -11402,6 +11850,7 @@ var DefaultPluginAPI = class {
11402
11850
  this.tools = {
11403
11851
  register: (t2) => tr.register(t2, owner),
11404
11852
  unregister: (name) => tr.unregister(name),
11853
+ wrap: (name, wrapper) => tr.wrap(name, wrapper, owner),
11405
11854
  get: (name) => tr.get(name),
11406
11855
  list: () => tr.list()
11407
11856
  };
@@ -11425,6 +11874,19 @@ var DefaultPluginAPI = class {
11425
11874
  this.pluginCleanupFns.push(off);
11426
11875
  return off;
11427
11876
  }
11877
+ onPattern(pattern, handler) {
11878
+ const off = this.events.onPattern(pattern, handler);
11879
+ this.pluginCleanupFns.push(off);
11880
+ return off;
11881
+ }
11882
+ emitCustom(event, payload) {
11883
+ this.events.emit(event, payload);
11884
+ }
11885
+ onConfigChange(handler) {
11886
+ if (!this.configStore) return () => {
11887
+ };
11888
+ return this.configStore.watch(handler);
11889
+ }
11428
11890
  /** Called by the plugin loader when uninstalling the plugin. */
11429
11891
  drainCleanup() {
11430
11892
  for (const fn of this.pluginCleanupFns.splice(0)) {
@@ -11434,6 +11896,9 @@ var DefaultPluginAPI = class {
11434
11896
  }
11435
11897
  }
11436
11898
  }
11899
+ registerSystemPromptContributor(c) {
11900
+ return this.extensions.registerSystemPromptContributor(c);
11901
+ }
11437
11902
  };
11438
11903
  var noopMcp = {
11439
11904
  start: async () => void 0,
@@ -11454,6 +11919,32 @@ var noopSlashCommands = {
11454
11919
  return [];
11455
11920
  }
11456
11921
  };
11922
+ var noopSession = {
11923
+ append: async () => {
11924
+ }
11925
+ };
11926
+ var noopMetrics = {
11927
+ counter() {
11928
+ },
11929
+ histogram() {
11930
+ },
11931
+ gauge() {
11932
+ }
11933
+ };
11934
+ function scopedMetrics(sink, pluginName) {
11935
+ const prefix = `plugin.${pluginName}.`;
11936
+ return {
11937
+ counter(name, value, labels) {
11938
+ sink.counter(`${prefix}${name}`, value, labels);
11939
+ },
11940
+ histogram(name, value, labels) {
11941
+ sink.histogram(`${prefix}${name}`, value, labels);
11942
+ },
11943
+ gauge(name, value, labels) {
11944
+ sink.gauge(`${prefix}${name}`, value, labels);
11945
+ }
11946
+ };
11947
+ }
11457
11948
 
11458
11949
  // src/plugin/loader.ts
11459
11950
  var KERNEL_API_VERSION = "0.1.10";
@@ -11479,6 +11970,16 @@ function satisfies(range, version) {
11479
11970
  function normalizeDep(d) {
11480
11971
  return typeof d === "string" ? { name: d } : d;
11481
11972
  }
11973
+ function shallowMerge(defaults, overrides) {
11974
+ if (overrides === void 0 || overrides === null) return { ...defaults };
11975
+ if (typeof overrides !== "object") return { ...defaults };
11976
+ const ov = overrides;
11977
+ const out = { ...defaults };
11978
+ for (const key of Object.keys(ov)) {
11979
+ out[key] = ov[key];
11980
+ }
11981
+ return out;
11982
+ }
11482
11983
  function topoSort(plugins) {
11483
11984
  const map = /* @__PURE__ */ new Map();
11484
11985
  for (const p of plugins) map.set(p.name, p);
@@ -11574,6 +12075,11 @@ async function loadPlugins(plugins, opts) {
11574
12075
  failed.push({ plugin, err });
11575
12076
  continue;
11576
12077
  }
12078
+ if (plugin.defaultConfig && opts.pluginOptions) {
12079
+ const userOpts = opts.pluginOptions[plugin.name];
12080
+ const merged = shallowMerge(plugin.defaultConfig, userOpts);
12081
+ opts.pluginOptions[plugin.name] = merged;
12082
+ }
11577
12083
  if (plugin.configSchema && opts.pluginOptions) {
11578
12084
  const pluginOpts = opts.pluginOptions[plugin.name];
11579
12085
  if (pluginOpts !== void 0) {
@@ -11595,7 +12101,7 @@ async function loadPlugins(plugins, opts) {
11595
12101
  }
11596
12102
  try {
11597
12103
  const rawApi = opts.apiFactory(plugin);
11598
- const api = plugin.capabilities ? wrapApiForCapabilityCheck(plugin, rawApi, opts.log) : rawApi;
12104
+ const api = plugin.capabilities ? wrapApiForCapabilityCheck(plugin, rawApi, opts.log, opts.enforceCapabilities) : rawApi;
11599
12105
  await plugin.setup(api);
11600
12106
  loaded.push(plugin);
11601
12107
  opts.log.info(`Plugin "${plugin.name}" loaded`);
@@ -11619,10 +12125,18 @@ async function unloadPlugins(loadedPlugins, opts) {
11619
12125
  }
11620
12126
  }
11621
12127
  }
11622
- function wrapApiForCapabilityCheck(plugin, api, log) {
12128
+ function wrapApiForCapabilityCheck(plugin, api, log, enforce = false) {
11623
12129
  const caps = plugin.capabilities ?? {};
11624
- const warn = (subsystem, detail) => {
12130
+ const violate = (subsystem, detail) => {
11625
12131
  const msg = `Plugin "${plugin.name}" used ${subsystem} without declaring capabilities.${subsystem} \u2014 ${detail}`;
12132
+ if (enforce) {
12133
+ throw new PluginError({
12134
+ message: msg,
12135
+ code: "PLUGIN_LOAD_FAILED",
12136
+ pluginName: plugin.name,
12137
+ context: { subsystem, detail }
12138
+ });
12139
+ }
11626
12140
  if (typeof log.warn === "function") log.warn(msg);
11627
12141
  else log.error(msg);
11628
12142
  };
@@ -11630,7 +12144,7 @@ function wrapApiForCapabilityCheck(plugin, api, log) {
11630
12144
  get(target, prop, receiver) {
11631
12145
  if (prop === "register") {
11632
12146
  return (t2) => {
11633
- warn("tools", `register(${t2?.name ?? "<unknown>"})`);
12147
+ violate("tools", `register(${t2?.name ?? "<unknown>"})`);
11634
12148
  return target.register(t2);
11635
12149
  };
11636
12150
  }
@@ -11641,7 +12155,7 @@ function wrapApiForCapabilityCheck(plugin, api, log) {
11641
12155
  get(target, prop, receiver) {
11642
12156
  if (prop === "register") {
11643
12157
  return (f) => {
11644
- warn("providers", `register(${f?.type ?? "<unknown>"})`);
12158
+ violate("providers", `register(${f?.type ?? "<unknown>"})`);
11645
12159
  return target.register(f);
11646
12160
  };
11647
12161
  }
@@ -11652,7 +12166,10 @@ function wrapApiForCapabilityCheck(plugin, api, log) {
11652
12166
  get(target, prop, receiver) {
11653
12167
  if (prop === "register") {
11654
12168
  return (c) => {
11655
- warn("slashCommands", `register(${c?.name ?? "<unknown>"})`);
12169
+ violate(
12170
+ "slashCommands",
12171
+ `register(${c?.name ?? "<unknown>"})`
12172
+ );
11656
12173
  return target.register(c);
11657
12174
  };
11658
12175
  }
@@ -11663,7 +12180,7 @@ function wrapApiForCapabilityCheck(plugin, api, log) {
11663
12180
  get(target, prop, receiver) {
11664
12181
  if (prop === "start") {
11665
12182
  return (cfg) => {
11666
- warn("mcp", `start(${cfg?.name ?? "<unknown>"})`);
12183
+ violate("mcp", `start(${cfg?.name ?? "<unknown>"})`);
11667
12184
  return target.start(cfg);
11668
12185
  };
11669
12186
  }
@@ -11688,6 +12205,6 @@ function wrapApiForCapabilityCheck(plugin, api, log) {
11688
12205
  });
11689
12206
  }
11690
12207
 
11691
- export { ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, Agent, AgentError, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigError, ConfigMigrationError, Container, Context, ConversationState, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_RECOVERY_STRATEGIES, 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, DirectorBudgetError, DirectorStateCheckpoint, DoneConditionChecker, EventBus, FLEET_ROSTER, 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, REFACTOR_PLANNER_AGENT, RecoveryLock, RunController, SECURITY_SCANNER_AGENT, SelectiveCompactor, SessionAnalyzer, SessionError, SlashCommandRegistry, SpecDrivenDev, SpecParser, SubagentBudget, TOKENS, TaskFlow, TaskGenerator, TaskTracker, ToolError, ToolExecutor, ToolRegistry, WrongStackError, addPlanItem, allServers, asBlocks, asText, atomicWrite, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildChildEnv, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildRecoveryStrategies, classifyFamily, clearPlan, color, compileGlob, composeDirectorPrompt, composeSubagentPrompt, computeTaskProgress, context7Server, contextManagerTool, createContextManagerTool, createDefaultPipelines, createDelegateTool, createMessage, createToolOutputSerializer, decryptConfigSecrets, detectNewlineStyle, emptyPlan, encryptConfigSecrets, ensureDir, estimateTextTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, extractRunEnv, filesystemServer, findCriticalPath, formatPlan, formatTodosList, githubServer, googleMapsServer, isAgentError, isConfigError, isImageBlock, isPluginError, isSessionError, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, loadDirectorState, loadPlan, loadPlugins, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeDirectorSessionFactory, matchAny, matchGlob, migratePlaintextSecrets, normalizeToLf, projectHash, removePlanItem, renderPrometheus, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, safeParse, safeStringify, sanitizeJsonString, savePlan, saveTodosCheckpoint, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState };
12208
+ export { ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, Agent, AgentError, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigError, ConfigMigrationError, Container, Context, ConversationState, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DirectorBudgetError, DirectorStateCheckpoint, DoneConditionChecker, EventBus, ExtensionRegistry, FLEET_ROSTER, 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, REFACTOR_PLANNER_AGENT, RecoveryLock, RunController, SECURITY_SCANNER_AGENT, SelectiveCompactor, SessionAnalyzer, SessionError, SlashCommandRegistry, SpecDrivenDev, SpecParser, SubagentBudget, TOKENS, TaskFlow, TaskGenerator, TaskTracker, ToolError, ToolExecutor, ToolRegistry, WrongStackError, addPlanItem, allServers, asBlocks, asText, atomicWrite, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildChildEnv, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildRecoveryStrategies, classifyFamily, clearPlan, color, compileGlob, composeDirectorPrompt, composeSubagentPrompt, computeTaskProgress, context7Server, contextManagerTool, createContextManagerTool, createDefaultPipelines, createDelegateTool, createMessage, createToolOutputSerializer, decryptConfigSecrets, detectNewlineStyle, emptyPlan, encryptConfigSecrets, ensureDir, estimateTextTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, extractRunEnv, filesystemServer, findCriticalPath, formatPlan, formatTodosList, githubServer, googleMapsServer, isAgentError, isConfigError, isImageBlock, isPluginError, isSessionError, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, loadDirectorState, loadPlan, loadPlugins, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeDirectorSessionFactory, matchAny, matchGlob, migratePlaintextSecrets, normalizeToLf, projectHash, removePlanItem, renderPrometheus, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, safeParse, safeStringify, sanitizeJsonString, savePlan, saveTodosCheckpoint, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState };
11692
12209
  //# sourceMappingURL=index.js.map
11693
12210
  //# sourceMappingURL=index.js.map