@wrongstack/core 0.286.0 → 0.287.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (251) hide show
  1. package/dist/boot.d.ts.map +1 -1
  2. package/dist/coordination/brain-monitor.d.ts +15 -0
  3. package/dist/coordination/brain-monitor.d.ts.map +1 -1
  4. package/dist/coordination/delegate-tool.d.ts +8 -14
  5. package/dist/coordination/delegate-tool.d.ts.map +1 -1
  6. package/dist/coordination/director/director-btw-notes.d.ts +20 -0
  7. package/dist/coordination/director/director-btw-notes.d.ts.map +1 -0
  8. package/dist/coordination/director/director-collab.d.ts +40 -0
  9. package/dist/coordination/director/director-collab.d.ts.map +1 -0
  10. package/dist/coordination/director.d.ts +27 -8
  11. package/dist/coordination/director.d.ts.map +1 -1
  12. package/dist/coordination/fleet-manager.d.ts +21 -0
  13. package/dist/coordination/fleet-manager.d.ts.map +1 -1
  14. package/dist/coordination/fleet.d.ts +2 -3
  15. package/dist/coordination/fleet.d.ts.map +1 -1
  16. package/dist/coordination/global-mailbox.d.ts +36 -22
  17. package/dist/coordination/global-mailbox.d.ts.map +1 -1
  18. package/dist/coordination/icoordinator.d.ts +1 -3
  19. package/dist/coordination/icoordinator.d.ts.map +1 -1
  20. package/dist/coordination/index.js +708 -442
  21. package/dist/coordination/index.js.map +4 -4
  22. package/dist/coordination/mail-tools.d.ts.map +1 -1
  23. package/dist/coordination/mailbox-message-codec.d.ts +21 -1
  24. package/dist/coordination/mailbox-message-codec.d.ts.map +1 -1
  25. package/dist/coordination/mailbox-types.d.ts +42 -1
  26. package/dist/coordination/mailbox-types.d.ts.map +1 -1
  27. package/dist/core/agent-loop.d.ts.map +1 -1
  28. package/dist/core/agent-response.d.ts +10 -1
  29. package/dist/core/agent-response.d.ts.map +1 -1
  30. package/dist/core/agent-tools.d.ts.map +1 -1
  31. package/dist/core/agent.d.ts +8 -0
  32. package/dist/core/agent.d.ts.map +1 -1
  33. package/dist/core/context.d.ts +25 -0
  34. package/dist/core/context.d.ts.map +1 -1
  35. package/dist/core/conversation-state.d.ts +6 -0
  36. package/dist/core/conversation-state.d.ts.map +1 -1
  37. package/dist/core/fallback-model.d.ts.map +1 -1
  38. package/dist/core/mailbox-loop.d.ts +12 -5
  39. package/dist/core/mailbox-loop.d.ts.map +1 -1
  40. package/dist/core/system-prompt-builder.d.ts +14 -6
  41. package/dist/core/system-prompt-builder.d.ts.map +1 -1
  42. package/dist/defaults/index.d.ts +0 -1
  43. package/dist/defaults/index.d.ts.map +1 -1
  44. package/dist/defaults/index.js +834 -1465
  45. package/dist/defaults/index.js.map +4 -4
  46. package/dist/execution/auto-compaction-middleware.d.ts +8 -1
  47. package/dist/execution/auto-compaction-middleware.d.ts.map +1 -1
  48. package/dist/execution/compaction-core.d.ts.map +1 -1
  49. package/dist/execution/council-brain.d.ts.map +1 -1
  50. package/dist/execution/council-orchestrator.d.ts +32 -0
  51. package/dist/execution/council-orchestrator.d.ts.map +1 -0
  52. package/dist/execution/council-personas.d.ts +19 -0
  53. package/dist/execution/council-personas.d.ts.map +1 -0
  54. package/dist/execution/council-profiles.d.ts +34 -0
  55. package/dist/execution/council-profiles.d.ts.map +1 -0
  56. package/dist/execution/council-prompts.d.ts +21 -0
  57. package/dist/execution/council-prompts.d.ts.map +1 -0
  58. package/dist/execution/council-resolution.d.ts +57 -0
  59. package/dist/execution/council-resolution.d.ts.map +1 -0
  60. package/dist/execution/design-kit-loader.d.ts.map +1 -1
  61. package/dist/execution/enhance-recovery.d.ts +35 -0
  62. package/dist/execution/enhance-recovery.d.ts.map +1 -0
  63. package/dist/execution/error-handler.d.ts.map +1 -1
  64. package/dist/execution/index.d.ts +6 -0
  65. package/dist/execution/index.d.ts.map +1 -1
  66. package/dist/execution/index.js +1738 -161
  67. package/dist/execution/index.js.map +4 -4
  68. package/dist/execution/intelligent-compactor.d.ts +18 -7
  69. package/dist/execution/intelligent-compactor.d.ts.map +1 -1
  70. package/dist/execution/one-shot-llm.d.ts +52 -0
  71. package/dist/execution/one-shot-llm.d.ts.map +1 -0
  72. package/dist/execution/prompt-enhancer.d.ts +23 -4
  73. package/dist/execution/prompt-enhancer.d.ts.map +1 -1
  74. package/dist/execution/prompt-enhancer.js +24 -6
  75. package/dist/execution/prompt-enhancer.js.map +2 -2
  76. package/dist/execution/selective-compactor.d.ts +4 -4
  77. package/dist/execution/strategy-compactor.d.ts +7 -0
  78. package/dist/execution/strategy-compactor.d.ts.map +1 -1
  79. package/dist/execution/tool-executor.d.ts +19 -5
  80. package/dist/execution/tool-executor.d.ts.map +1 -1
  81. package/dist/hooks/registry.d.ts.map +1 -1
  82. package/dist/hooks/runner.d.ts.map +1 -1
  83. package/dist/hooks/shell-executor.d.ts.map +1 -1
  84. package/dist/hq/alerts.d.ts +5 -0
  85. package/dist/hq/alerts.d.ts.map +1 -1
  86. package/dist/hq/auth-store.d.ts +17 -7
  87. package/dist/hq/auth-store.d.ts.map +1 -1
  88. package/dist/hq/commands.d.ts +8 -4
  89. package/dist/hq/commands.d.ts.map +1 -1
  90. package/dist/hq/cost-bridge.d.ts +5 -3
  91. package/dist/hq/cost-bridge.d.ts.map +1 -1
  92. package/dist/hq/fleet-bridge.d.ts.map +1 -1
  93. package/dist/hq/index.d.ts +0 -1
  94. package/dist/hq/index.d.ts.map +1 -1
  95. package/dist/hq/index.js +471 -294
  96. package/dist/hq/index.js.map +4 -4
  97. package/dist/hq/persistence.d.ts +42 -0
  98. package/dist/hq/persistence.d.ts.map +1 -1
  99. package/dist/hq/protocol/brain.d.ts +24 -0
  100. package/dist/hq/protocol/brain.d.ts.map +1 -0
  101. package/dist/hq/protocol/browser.d.ts +12 -0
  102. package/dist/hq/protocol/browser.d.ts.map +1 -0
  103. package/dist/hq/protocol/client.d.ts +69 -0
  104. package/dist/hq/protocol/client.d.ts.map +1 -0
  105. package/dist/hq/protocol/core.d.ts +130 -0
  106. package/dist/hq/protocol/core.d.ts.map +1 -0
  107. package/dist/hq/protocol/fleet.d.ts +73 -0
  108. package/dist/hq/protocol/fleet.d.ts.map +1 -0
  109. package/dist/hq/protocol/mailbox.d.ts +76 -0
  110. package/dist/hq/protocol/mailbox.d.ts.map +1 -0
  111. package/dist/hq/protocol/mcp.d.ts +51 -0
  112. package/dist/hq/protocol/mcp.d.ts.map +1 -0
  113. package/dist/hq/protocol/project.d.ts +26 -0
  114. package/dist/hq/protocol/project.d.ts.map +1 -0
  115. package/dist/hq/protocol/session.d.ts +153 -0
  116. package/dist/hq/protocol/session.d.ts.map +1 -0
  117. package/dist/hq/protocol/tool.d.ts +31 -0
  118. package/dist/hq/protocol/tool.d.ts.map +1 -0
  119. package/dist/hq/protocol.d.ts +10 -611
  120. package/dist/hq/protocol.d.ts.map +1 -1
  121. package/dist/index.d.ts +12 -2
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/index.js +4540 -2283
  124. package/dist/index.js.map +4 -4
  125. package/dist/infrastructure/index.js +13 -11
  126. package/dist/infrastructure/index.js.map +2 -2
  127. package/dist/infrastructure/token-counter.d.ts.map +1 -1
  128. package/dist/kernel/events/agent-events.d.ts +283 -0
  129. package/dist/kernel/events/agent-events.d.ts.map +1 -0
  130. package/dist/kernel/events/brain-events.d.ts +176 -0
  131. package/dist/kernel/events/brain-events.d.ts.map +1 -0
  132. package/dist/kernel/events/fleet-events.d.ts +39 -0
  133. package/dist/kernel/events/fleet-events.d.ts.map +1 -0
  134. package/dist/kernel/events/memory-events.d.ts +165 -0
  135. package/dist/kernel/events/memory-events.d.ts.map +1 -0
  136. package/dist/kernel/events/provider-events.d.ts +98 -0
  137. package/dist/kernel/events/provider-events.d.ts.map +1 -0
  138. package/dist/kernel/events/sdd-events.d.ts +118 -0
  139. package/dist/kernel/events/sdd-events.d.ts.map +1 -0
  140. package/dist/kernel/events/session-events.d.ts +227 -0
  141. package/dist/kernel/events/session-events.d.ts.map +1 -0
  142. package/dist/kernel/events/tool-events.d.ts +185 -0
  143. package/dist/kernel/events/tool-events.d.ts.map +1 -0
  144. package/dist/kernel/events/worktree-events.d.ts +58 -0
  145. package/dist/kernel/events/worktree-events.d.ts.map +1 -0
  146. package/dist/kernel/events.d.ts +10 -1298
  147. package/dist/kernel/events.d.ts.map +1 -1
  148. package/dist/kernel/index.js +22 -2
  149. package/dist/kernel/index.js.map +2 -2
  150. package/dist/kernel/run-controller.d.ts +15 -0
  151. package/dist/kernel/run-controller.d.ts.map +1 -1
  152. package/dist/mailbox-attach.d.ts.map +1 -1
  153. package/dist/models/index.js +19 -6
  154. package/dist/models/index.js.map +2 -2
  155. package/dist/models/llm-selector.d.ts +7 -0
  156. package/dist/models/llm-selector.d.ts.map +1 -1
  157. package/dist/plugins/auto-review-plugin.d.ts +36 -0
  158. package/dist/plugins/auto-review-plugin.d.ts.map +1 -0
  159. package/dist/plugins/chimera-plugin.d.ts +8 -0
  160. package/dist/plugins/chimera-plugin.d.ts.map +1 -1
  161. package/dist/security/index.js +7 -2
  162. package/dist/security/index.js.map +2 -2
  163. package/dist/security/secret-scrubber.d.ts.map +1 -1
  164. package/dist/skills/index.js.map +2 -2
  165. package/dist/storage/config-loader.d.ts.map +1 -1
  166. package/dist/storage/goal-kanban.d.ts +69 -0
  167. package/dist/storage/goal-kanban.d.ts.map +1 -0
  168. package/dist/storage/index.d.ts +1 -1
  169. package/dist/storage/index.d.ts.map +1 -1
  170. package/dist/storage/index.js +425 -717
  171. package/dist/storage/index.js.map +4 -4
  172. package/dist/storage/memory-consolidator.d.ts +9 -1
  173. package/dist/storage/memory-consolidator.d.ts.map +1 -1
  174. package/dist/tools/council-tool.d.ts +25 -0
  175. package/dist/tools/council-tool.d.ts.map +1 -0
  176. package/dist/tools/index.d.ts +2 -0
  177. package/dist/tools/index.d.ts.map +1 -1
  178. package/dist/tools/index.js +1884 -12
  179. package/dist/tools/index.js.map +4 -4
  180. package/dist/tools/one-shot-llm-tool.d.ts +51 -0
  181. package/dist/tools/one-shot-llm-tool.d.ts.map +1 -0
  182. package/dist/types/config.d.ts +27 -3
  183. package/dist/types/config.d.ts.map +1 -1
  184. package/dist/types/context-window.d.ts.map +1 -1
  185. package/dist/types/council.d.ts +134 -0
  186. package/dist/types/council.d.ts.map +1 -0
  187. package/dist/types/default-config.d.ts +3 -3
  188. package/dist/types/hooks.d.ts +14 -0
  189. package/dist/types/hooks.d.ts.map +1 -1
  190. package/dist/types/index.d.ts +2 -0
  191. package/dist/types/index.d.ts.map +1 -1
  192. package/dist/types/index.js +29 -8
  193. package/dist/types/index.js.map +3 -3
  194. package/dist/types/one-shot-llm.d.ts +128 -0
  195. package/dist/types/one-shot-llm.d.ts.map +1 -0
  196. package/dist/types/plugin.d.ts +6 -0
  197. package/dist/types/plugin.d.ts.map +1 -1
  198. package/dist/types/provider.d.ts +17 -0
  199. package/dist/types/provider.d.ts.map +1 -1
  200. package/dist/types/system-prompt.d.ts +2 -1
  201. package/dist/types/system-prompt.d.ts.map +1 -1
  202. package/dist/types/tool-executor.d.ts +10 -0
  203. package/dist/types/tool-executor.d.ts.map +1 -1
  204. package/dist/utils/atomic-write.d.ts.map +1 -1
  205. package/dist/utils/index.js +75 -8
  206. package/dist/utils/index.js.map +3 -3
  207. package/dist/utils/tool-wire-compact.d.ts +1 -0
  208. package/dist/utils/tool-wire-compact.d.ts.map +1 -1
  209. package/dist/utils/wstack-paths.d.ts +20 -4
  210. package/dist/utils/wstack-paths.d.ts.map +1 -1
  211. package/instructions/cli/next-task-predictor.md +5 -3
  212. package/instructions/coordination/subagent-baseline.md +8 -7
  213. package/instructions/leader-after-task.md +18 -9
  214. package/instructions/llm/council-judge.md +19 -0
  215. package/instructions/llm/council-voter.md +22 -0
  216. package/instructions/modes/architect.md +14 -9
  217. package/instructions/modes/audit-lite.md +9 -7
  218. package/instructions/modes/brief-system.md +12 -12
  219. package/instructions/modes/brief.md +7 -5
  220. package/instructions/modes/code-auditor.md +13 -8
  221. package/instructions/modes/code-reviewer.md +13 -8
  222. package/instructions/modes/debug-lite.md +7 -6
  223. package/instructions/modes/debugger.md +13 -9
  224. package/instructions/modes/devops.md +13 -9
  225. package/instructions/modes/plan-lite.md +7 -6
  226. package/instructions/modes/refactor-lite.md +7 -6
  227. package/instructions/modes/refactorer.md +14 -9
  228. package/instructions/modes/research-lite.md +8 -6
  229. package/instructions/modes/research-web.md +18 -55
  230. package/instructions/modes/review-lite.md +7 -6
  231. package/instructions/modes/teach-system.md +15 -15
  232. package/instructions/modes/teach.md +13 -12
  233. package/instructions/modes/test-lite.md +6 -5
  234. package/instructions/modes/tester.md +13 -9
  235. package/instructions/modes/ui-design.md +14 -8
  236. package/instructions/sections/tool/commit-hygiene.md +1 -7
  237. package/instructions/sections/tool/common-patterns.md +5 -5
  238. package/instructions/sections/tool/delegation-full.md +3 -31
  239. package/instructions/sections/tool/mailbox-compact.md +1 -1
  240. package/instructions/sections/tool/mailbox-full.md +6 -22
  241. package/instructions/system.md +278 -39
  242. package/package.json +2 -2
  243. package/skills/mailbox-bridge/SKILL.md +16 -7
  244. package/skills/output-standards/SKILL.md +16 -16
  245. package/skills/wrongstack-mailbox/SKILL.md +6 -0
  246. package/dist/coordination/director-construction.d.ts +0 -90
  247. package/dist/coordination/director-construction.d.ts.map +0 -1
  248. package/dist/hq/agent-bridge.d.ts +0 -27
  249. package/dist/hq/agent-bridge.d.ts.map +0 -1
  250. package/dist/storage/memory-store.d.ts +0 -119
  251. package/dist/storage/memory-store.d.ts.map +0 -1
@@ -580,8 +580,8 @@ function subjectForToolInput(toolName, input, subjectKey) {
580
580
  }
581
581
 
582
582
  // src/utils/tool-wire-compact.ts
583
- var TOOL_DESCRIPTION_MAX_CHARS = 640;
584
- var SCHEMA_DESCRIPTION_MAX_CHARS = 180;
583
+ var TOOL_DESCRIPTION_MAX_CHARS = 400;
584
+ var SCHEMA_DESCRIPTION_MAX_CHARS = 120;
585
585
  var compactCache = /* @__PURE__ */ new WeakMap();
586
586
  function compactToolDefinitionForWire(tool, opts = {}) {
587
587
  const useDefaultOptions = opts.descriptionMaxChars === void 0 && opts.schemaDescriptionMaxChars === void 0;
@@ -1501,14 +1501,34 @@ function resolveToolResultRenderMode(modes, toolName) {
1501
1501
 
1502
1502
  // src/utils/wstack-paths.ts
1503
1503
  import { createHash } from "node:crypto";
1504
+ import * as fs2 from "node:fs";
1504
1505
  import * as os from "node:os";
1505
1506
  import * as path2 from "node:path";
1507
+ function canonicalProjectRoot(absRoot) {
1508
+ const checkoutRoot = path2.resolve(absRoot);
1509
+ const dotGit = path2.join(checkoutRoot, ".git");
1510
+ try {
1511
+ if (!fs2.statSync(dotGit).isFile()) return checkoutRoot;
1512
+ const gitDirLine = fs2.readFileSync(dotGit, "utf8").trim();
1513
+ const match = /^gitdir:\s*(.+)$/i.exec(gitDirLine);
1514
+ if (!match?.[1]) return checkoutRoot;
1515
+ const gitDir = path2.resolve(checkoutRoot, match[1].trim());
1516
+ const commonDirFile = path2.join(gitDir, "commondir");
1517
+ if (!fs2.statSync(commonDirFile).isFile()) return checkoutRoot;
1518
+ const commonDir = path2.resolve(gitDir, fs2.readFileSync(commonDirFile, "utf8").trim());
1519
+ if (path2.basename(commonDir).toLowerCase() !== ".git") return checkoutRoot;
1520
+ return path2.dirname(commonDir);
1521
+ } catch {
1522
+ return checkoutRoot;
1523
+ }
1524
+ }
1506
1525
  function projectHash(absRoot) {
1507
- return createHash("sha256").update(path2.resolve(absRoot)).digest("hex").slice(0, 12);
1526
+ return createHash("sha256").update(canonicalProjectRoot(absRoot)).digest("hex").slice(0, 12);
1508
1527
  }
1509
1528
  function projectSlug(absRoot) {
1510
- const base = slugify2(path2.basename(absRoot));
1511
- const hash = createHash("sha256").update(path2.resolve(absRoot)).digest("hex").slice(0, 6);
1529
+ const identityRoot = canonicalProjectRoot(absRoot);
1530
+ const base = slugify2(path2.basename(identityRoot));
1531
+ const hash = createHash("sha256").update(identityRoot).digest("hex").slice(0, 6);
1512
1532
  return `${base}-${hash}`;
1513
1533
  }
1514
1534
  function slugify2(name) {
@@ -1775,6 +1795,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
1775
1795
  failureMode;
1776
1796
  policyProvider;
1777
1797
  sessionBridge;
1798
+ onCompact;
1778
1799
  /**
1779
1800
  * Once a compaction attempt reduces nothing (preserveK protects everything,
1780
1801
  * no oversized tool_results remain to elide), retrying on every iteration
@@ -1821,6 +1842,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
1821
1842
  this.failureMode = opts.failureMode ?? "throw_on_hard";
1822
1843
  this.policyProvider = opts.policyProvider;
1823
1844
  this.sessionBridge = opts.sessionBridge;
1845
+ this.onCompact = opts.onCompact;
1824
1846
  }
1825
1847
  /** Allow callers (e.g. model-switch in WebUI) to update the context window
1826
1848
  * denominator when the active model changes. */
@@ -1943,6 +1965,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
1943
1965
  try {
1944
1966
  const report = await this.compactor.compact(ctx, { aggressive });
1945
1967
  this.recordAttempt(pressure.level, pressure.tokens, report);
1968
+ this.onCompact?.(report);
1946
1969
  this.events?.emit("compaction.fired", {
1947
1970
  sessionId: ctx.session.id,
1948
1971
  level: pressure.level,
@@ -1971,7 +1994,8 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
1971
1994
  });
1972
1995
  ctx.clearFileTracking();
1973
1996
  const afterTokens = report.fullRequestTokensAfter ?? report.after;
1974
- const afterLoad = this._maxContext > 0 ? afterTokens / this._maxContext : 0;
1997
+ const afterBudget = computeContextWindowBudget(ctx, afterTokens, this._maxContext);
1998
+ const afterLoad = afterBudget.load;
1975
1999
  const stillHard = afterLoad >= pressure.hardThreshold;
1976
2000
  const fatal = stillHard && (this.failureMode === "throw" || this.failureMode === "throw_on_hard" && pressure.level === "hard");
1977
2001
  if (stillHard) {
@@ -2254,7 +2278,7 @@ var AutonomousRunner = class {
2254
2278
  };
2255
2279
 
2256
2280
  // src/utils/instruction-file.ts
2257
- import { readFileSync, statSync } from "node:fs";
2281
+ import { readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
2258
2282
  import * as path3 from "node:path";
2259
2283
  import { fileURLToPath } from "node:url";
2260
2284
  var textCache = /* @__PURE__ */ new Map();
@@ -2265,7 +2289,7 @@ function readBundledInstructionText(relativePath) {
2265
2289
  let resolved = "";
2266
2290
  for (const root of instructionRootCandidates()) {
2267
2291
  try {
2268
- resolved = readFileSync(path3.join(root, relativePath), "utf8").trimEnd();
2292
+ resolved = readFileSync2(path3.join(root, relativePath), "utf8").trimEnd();
2269
2293
  break;
2270
2294
  } catch {
2271
2295
  }
@@ -2292,7 +2316,7 @@ function instructionRootCandidates() {
2292
2316
  }
2293
2317
  function isDirectory(candidate) {
2294
2318
  try {
2295
- return statSync(candidate).isDirectory();
2319
+ return statSync2(candidate).isDirectory();
2296
2320
  } catch {
2297
2321
  return false;
2298
2322
  }
@@ -2565,6 +2589,22 @@ var RETRYABLE_BY_KIND = {
2565
2589
  invalid_request: false,
2566
2590
  unknown: false
2567
2591
  };
2592
+ function isFallbackWorthy(kind) {
2593
+ return FALLBACK_WORTHY_BY_KIND[kind];
2594
+ }
2595
+ var FALLBACK_WORTHY_BY_KIND = {
2596
+ rate_limit: true,
2597
+ overloaded: true,
2598
+ server: true,
2599
+ timeout: true,
2600
+ network: true,
2601
+ stream_hang: true,
2602
+ auth: false,
2603
+ context_overflow: false,
2604
+ content_filter: false,
2605
+ invalid_request: false,
2606
+ unknown: false
2607
+ };
2568
2608
  var ProviderError = class extends WrongStackError {
2569
2609
  status;
2570
2610
  retryable;
@@ -2646,6 +2686,10 @@ function kindToCode(kind) {
2646
2686
  }
2647
2687
 
2648
2688
  // src/core/fallback-model.ts
2689
+ function visibleProviderModels(config, providerId, providerModels) {
2690
+ const entry = config.providers?.[providerId];
2691
+ return entry?.models !== void 0 ? [...entry.models] : providerModels;
2692
+ }
2649
2693
  function parseModelRef(ref) {
2650
2694
  const trimmed = ref.trim();
2651
2695
  const slash = trimmed.indexOf("/");
@@ -2661,8 +2705,174 @@ function parseModelRef(ref) {
2661
2705
  }
2662
2706
  return { model: trimmed };
2663
2707
  }
2708
+ function formatModelRef(ref, defaultProvider) {
2709
+ const provider = ref.provider ?? defaultProvider;
2710
+ return provider ? `${provider}/${ref.model}` : ref.model;
2711
+ }
2712
+ function normalizeModelRef(ref, defaultProvider) {
2713
+ const parsed = parseModelRef(ref);
2714
+ return formatModelRef(parsed, defaultProvider);
2715
+ }
2716
+ function fallbackProfileChain(config, profileName) {
2717
+ if (!profileName) return [];
2718
+ const chain = config.fallbackProfiles?.[profileName];
2719
+ return Array.isArray(chain) ? chain.filter((ref) => parseModelRef(ref).model) : [];
2720
+ }
2721
+ function providerHasKey(entry) {
2722
+ if (!entry) return false;
2723
+ if (typeof entry.apiKey === "string" && entry.apiKey.length > 0) return true;
2724
+ if (Array.isArray(entry.apiKeys) && entry.apiKeys.some((k) => k?.apiKey)) return true;
2725
+ if (Array.isArray(entry.envVars) && entry.envVars.some((v) => !!process.env[v])) return true;
2726
+ return false;
2727
+ }
2728
+ var SMART_DEFAULT_MAX = 4;
2729
+ function smartDefaultFallbackChain(config) {
2730
+ const leaderProvider = config.provider;
2731
+ const leaderModel = config.model;
2732
+ const providers = config.providers ?? {};
2733
+ const favoriteSet = new Set(
2734
+ (config.favoriteModels ?? []).map((ref) => normalizeModelRef(ref, leaderProvider))
2735
+ );
2736
+ const hasFavorites = favoriteSet.size > 0;
2737
+ const favoritesOnly = config.favoriteModelsOnly === true;
2738
+ const seen = /* @__PURE__ */ new Set();
2739
+ const favoriteRefs = [];
2740
+ const sameProvider = [];
2741
+ const crossProvider = [];
2742
+ const ids = Object.keys(providers).sort(
2743
+ (a, b) => a === leaderProvider ? -1 : b === leaderProvider ? 1 : a.localeCompare(b)
2744
+ );
2745
+ for (const id of ids) {
2746
+ const entry = providers[id];
2747
+ if (!providerHasKey(entry)) continue;
2748
+ const models = visibleProviderModels(config, id, entry?.models ?? []);
2749
+ for (const model of models) {
2750
+ if (id === leaderProvider && model === leaderModel) continue;
2751
+ const ref = `${id}/${model}`;
2752
+ if (seen.has(ref)) continue;
2753
+ seen.add(ref);
2754
+ if (favoriteSet.has(ref)) {
2755
+ favoriteRefs.push(ref);
2756
+ continue;
2757
+ }
2758
+ if (favoritesOnly && hasFavorites) continue;
2759
+ (id === leaderProvider ? sameProvider : crossProvider).push(ref);
2760
+ }
2761
+ }
2762
+ return [...favoriteRefs, ...sameProvider, ...crossProvider].slice(0, SMART_DEFAULT_MAX);
2763
+ }
2764
+ function effectiveFallbackChain(config) {
2765
+ const explicit = config.fallbackModels ?? [];
2766
+ const filteredExplicit = explicit.filter((ref) => {
2767
+ const parsed = parseModelRef(ref);
2768
+ if (!parsed.model) return false;
2769
+ const providerId = parsed.provider ?? config.provider;
2770
+ const entry = config.providers?.[providerId];
2771
+ if (!entry?.models) return true;
2772
+ return entry.models.includes(parsed.model);
2773
+ });
2774
+ if (filteredExplicit.length > 0) return filteredExplicit;
2775
+ if (config.fallbackAuto === false) return [];
2776
+ return smartDefaultFallbackChain(config);
2777
+ }
2664
2778
  var DEFAULT_PRIMARY_COOLDOWN_MAX_MS = 10 * 6e4;
2665
2779
 
2780
+ // src/execution/council-resolution.ts
2781
+ function resolveCouncilVotes(input) {
2782
+ validateInput(input);
2783
+ const seatById = new Map(input.seats.map((seat) => [seat.id, seat]));
2784
+ const validVotes = [];
2785
+ const votedSeatIds = /* @__PURE__ */ new Set();
2786
+ for (const vote of input.votes) {
2787
+ if (!seatById.has(vote.seatId) || votedSeatIds.has(vote.seatId)) continue;
2788
+ votedSeatIds.add(vote.seatId);
2789
+ validVotes.push(vote);
2790
+ }
2791
+ if (validVotes.length / input.seats.length < input.quorumFraction) {
2792
+ return {
2793
+ status: "abstained",
2794
+ reason: "quorum_not_met",
2795
+ validVoteCount: validVotes.length,
2796
+ seatCount: input.seats.length
2797
+ };
2798
+ }
2799
+ const veto = validVotes.find(
2800
+ (vote) => vote.optionId === input.refusalOptionId && seatById.get(vote.seatId)?.veto === true
2801
+ );
2802
+ if (veto) {
2803
+ return {
2804
+ status: "denied",
2805
+ method: "veto",
2806
+ optionId: veto.optionId,
2807
+ seatId: veto.seatId
2808
+ };
2809
+ }
2810
+ const weightByOption = /* @__PURE__ */ new Map();
2811
+ let castWeight = 0;
2812
+ for (const vote of validVotes) {
2813
+ const weight = seatById.get(vote.seatId)?.weight ?? 1;
2814
+ castWeight += weight;
2815
+ weightByOption.set(vote.optionId, (weightByOption.get(vote.optionId) ?? 0) + weight);
2816
+ }
2817
+ let winner;
2818
+ let contested = false;
2819
+ for (const [optionId, weight] of weightByOption) {
2820
+ if (!winner || weight > winner.weight) {
2821
+ winner = { optionId, weight };
2822
+ contested = false;
2823
+ } else if (weight === winner.weight) {
2824
+ contested = true;
2825
+ }
2826
+ }
2827
+ const decisive = winner !== void 0 && !contested && winner.weight > input.approvalFraction * castWeight;
2828
+ if (!decisive || !winner) {
2829
+ return {
2830
+ status: "needs_judge",
2831
+ reason: contested ? "tie" : "approval_threshold_not_met",
2832
+ castWeight
2833
+ };
2834
+ }
2835
+ if (winner.optionId === input.refusalOptionId) {
2836
+ return {
2837
+ status: "denied",
2838
+ method: "refusal",
2839
+ optionId: winner.optionId,
2840
+ winningWeight: winner.weight,
2841
+ castWeight
2842
+ };
2843
+ }
2844
+ return {
2845
+ status: "decided",
2846
+ method: "majority",
2847
+ optionId: winner.optionId,
2848
+ winningWeight: winner.weight,
2849
+ castWeight
2850
+ };
2851
+ }
2852
+ function validateInput(input) {
2853
+ if (input.seats.length === 0) {
2854
+ throw new Error("resolveCouncilVotes: at least one seat is required.");
2855
+ }
2856
+ requireFraction(input.quorumFraction, "quorumFraction");
2857
+ requireFraction(input.approvalFraction, "approvalFraction");
2858
+ const seatIds = /* @__PURE__ */ new Set();
2859
+ for (const seat of input.seats) {
2860
+ if (!seat.id.trim()) throw new Error("resolveCouncilVotes: seat id must not be empty.");
2861
+ if (seatIds.has(seat.id)) {
2862
+ throw new Error(`resolveCouncilVotes: duplicate seat id "${seat.id}".`);
2863
+ }
2864
+ seatIds.add(seat.id);
2865
+ if (seat.weight !== void 0 && (!Number.isFinite(seat.weight) || seat.weight <= 0)) {
2866
+ throw new Error(`resolveCouncilVotes: invalid weight for seat "${seat.id}".`);
2867
+ }
2868
+ }
2869
+ }
2870
+ function requireFraction(value, label) {
2871
+ if (!Number.isFinite(value) || value <= 0 || value > 1) {
2872
+ throw new Error(`resolveCouncilVotes: ${label} must be in (0, 1].`);
2873
+ }
2874
+ }
2875
+
2666
2876
  // src/execution/council-brain.ts
2667
2877
  var COUNCIL_REFUSE_OPTION_ID = "council_refuse";
2668
2878
  var BUILTIN_PERSONAS = {
@@ -2727,7 +2937,12 @@ ${personaText(voter.persona)}`,
2727
2937
  if (!voter || result.status !== "fulfilled") return;
2728
2938
  const parsed = parseOptionDecision(result.value, ballot);
2729
2939
  if (parsed?.type !== "answer" || !parsed.optionId) return;
2730
- votes.push({ voter, optionId: parsed.optionId, rationale: parsed.rationale });
2940
+ votes.push({
2941
+ seatId: String(i),
2942
+ voter,
2943
+ optionId: parsed.optionId,
2944
+ rationale: parsed.rationale
2945
+ });
2731
2946
  });
2732
2947
  return votes;
2733
2948
  }
@@ -2823,44 +3038,44 @@ ${stanceLines}`,
2823
3038
  }
2824
3039
  const ballot = ballotOptions(request);
2825
3040
  const votes = await collectVotes(request, ballot, digest);
2826
- if (votes.length / opts.voters.length < quorumFraction) {
2827
- return finish(request, abstain(request, `quorum not met (${votes.length}/${opts.voters.length} votes)`));
3041
+ const resolution = resolveCouncilVotes({
3042
+ seats: opts.voters.map((voter, i) => ({
3043
+ id: String(i),
3044
+ weight: voter.weight,
3045
+ veto: voter.veto
3046
+ })),
3047
+ votes: votes.map((vote) => ({ seatId: vote.seatId, optionId: vote.optionId })),
3048
+ refusalOptionId: COUNCIL_REFUSE_OPTION_ID,
3049
+ quorumFraction,
3050
+ approvalFraction
3051
+ });
3052
+ if (resolution.status === "abstained") {
3053
+ return finish(
3054
+ request,
3055
+ abstain(
3056
+ request,
3057
+ `quorum not met (${resolution.validVoteCount}/${resolution.seatCount} votes)`
3058
+ )
3059
+ );
2828
3060
  }
2829
- const vetoRefusal = votes.find(
2830
- (v) => v.voter.veto === true && v.optionId === COUNCIL_REFUSE_OPTION_ID
2831
- );
2832
- if (vetoRefusal) {
3061
+ if (resolution.status === "denied" && resolution.method === "veto") {
3062
+ const vetoRefusal = votes.find((vote) => vote.seatId === resolution.seatId);
3063
+ if (!vetoRefusal) {
3064
+ return finish(request, abstain(request, "veto ballot could not be resolved"));
3065
+ }
2833
3066
  return finish(request, {
2834
3067
  type: "deny",
2835
3068
  reason: `Council veto by ${voterLabel(vetoRefusal.voter, 0)}` + (vetoRefusal.rationale ? `: ${vetoRefusal.rationale}` : ".")
2836
3069
  });
2837
3070
  }
2838
- const weightByOption = /* @__PURE__ */ new Map();
2839
- let castWeight = 0;
2840
- for (const v of votes) {
2841
- const w = v.voter.weight ?? 1;
2842
- castWeight += w;
2843
- weightByOption.set(v.optionId, (weightByOption.get(v.optionId) ?? 0) + w);
2844
- }
2845
- let winner = null;
2846
- let contested = false;
2847
- for (const [optionId, weight] of weightByOption) {
2848
- if (!winner || weight > winner.weight) {
2849
- winner = { optionId, weight };
2850
- contested = false;
2851
- } else if (weight === winner.weight) {
2852
- contested = true;
2853
- }
2854
- }
2855
- const decisive = winner !== null && !contested && winner.weight > approvalFraction * castWeight;
2856
- let finalOptionId = decisive && winner ? winner.optionId : null;
3071
+ let finalOptionId = resolution.status === "decided" || resolution.status === "denied" ? resolution.optionId : null;
2857
3072
  let viaJudge = false;
2858
- if (!finalOptionId) {
3073
+ if (resolution.status === "needs_judge") {
2859
3074
  const judged = await judgeRound(
2860
3075
  request,
2861
3076
  ballot,
2862
3077
  votes,
2863
- contested ? "tie" : "no option cleared the approval threshold"
3078
+ resolution.reason === "tie" ? "tie" : "no option cleared the approval threshold"
2864
3079
  );
2865
3080
  if (judged?.type === "answer" && judged.optionId) {
2866
3081
  finalOptionId = judged.optionId;
@@ -3298,7 +3513,7 @@ function normalizeVoter(raw) {
3298
3513
  if (raw.veto !== void 0) voter.veto = raw.veto;
3299
3514
  return voter;
3300
3515
  }
3301
- function requireFraction(value, label) {
3516
+ function requireFraction2(value, label) {
3302
3517
  if (!Number.isFinite(value) || value <= 0 || value > 1) {
3303
3518
  throw new Error(`Invalid ${label}: ${String(value)} (must be in (0, 1])`);
3304
3519
  }
@@ -3446,10 +3661,10 @@ function createBrainRuntime(opts) {
3446
3661
  c.voters = p.voters === null ? void 0 : p.voters.map(normalizeVoter);
3447
3662
  }
3448
3663
  if (p.quorum !== void 0) {
3449
- c.quorum = p.quorum === null ? void 0 : requireFraction(p.quorum, "council quorum");
3664
+ c.quorum = p.quorum === null ? void 0 : requireFraction2(p.quorum, "council quorum");
3450
3665
  }
3451
3666
  if (p.approval !== void 0) {
3452
- c.approval = p.approval === null ? void 0 : requireFraction(p.approval, "council approval");
3667
+ c.approval = p.approval === null ? void 0 : requireFraction2(p.approval, "council approval");
3453
3668
  }
3454
3669
  if (p.judge !== void 0) {
3455
3670
  c.judge = p.judge === null ? void 0 : normalizeEntry(p.judge);
@@ -3570,6 +3785,1011 @@ function createBrainRuntime(opts) {
3570
3785
  };
3571
3786
  }
3572
3787
 
3788
+ // src/execution/council-personas.ts
3789
+ var PERSONA_ID_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
3790
+ var BUILTIN_COUNCIL_PERSONAS = Object.freeze([
3791
+ freezePersona({
3792
+ id: "executor",
3793
+ name: "Executor",
3794
+ description: "Tests whether a proposal is practical and keeps useful work moving.",
3795
+ instruction: "Evaluate operational feasibility, concrete progress, and the cost of delay. Favor decisive action when evidence supports it, but reject action whose prerequisites are missing.",
3796
+ tags: ["delivery", "feasibility", "progress"]
3797
+ }),
3798
+ freezePersona({
3799
+ id: "skeptic",
3800
+ name: "Skeptic",
3801
+ description: "Challenges assumptions and looks for concrete failure modes.",
3802
+ instruction: "Identify unsupported assumptions, unsafe premises, irreversible consequences, and credible failure modes. Oppose or refuse only when you can name a concrete reason.",
3803
+ defaultVeto: true,
3804
+ tags: ["risk", "assumptions", "failure-modes"]
3805
+ }),
3806
+ freezePersona({
3807
+ id: "auditor",
3808
+ name: "Auditor",
3809
+ description: "Evaluates cost, waste, evidence quality, and expected value.",
3810
+ instruction: "Compare resource cost, opportunity cost, evidence quality, reversibility, and expected value. Prefer the option that achieves the objective with the least avoidable waste.",
3811
+ tags: ["cost", "evidence", "efficiency"]
3812
+ }),
3813
+ freezePersona({
3814
+ id: "security",
3815
+ name: "Security Reviewer",
3816
+ description: "Examines trust boundaries, abuse cases, and security impact.",
3817
+ instruction: "Evaluate trust boundaries, attacker-controlled inputs, privilege changes, data exposure, abuse cases, and recovery options. Treat unmitigated high-impact security risk as grounds to refuse.",
3818
+ defaultVeto: true,
3819
+ tags: ["security", "trust", "abuse-cases"]
3820
+ }),
3821
+ freezePersona({
3822
+ id: "maintainer",
3823
+ name: "Maintainer",
3824
+ description: "Evaluates complexity, compatibility, and long-term ownership.",
3825
+ instruction: "Evaluate behavioral compatibility, conceptual complexity, testability, migration cost, and future maintenance. Prefer the smallest design that remains clear and extensible.",
3826
+ tags: ["maintenance", "compatibility", "simplicity"]
3827
+ }),
3828
+ freezePersona({
3829
+ id: "user-advocate",
3830
+ name: "User Advocate",
3831
+ description: "Evaluates the decision from the affected user\u2019s perspective.",
3832
+ instruction: "Evaluate usability, surprise, accessibility, failure recovery, and whether the outcome solves the user\u2019s stated need. Prefer understandable behavior with safe recovery paths.",
3833
+ tags: ["users", "usability", "accessibility"]
3834
+ })
3835
+ ]);
3836
+ var CouncilPersonaRegistry = class _CouncilPersonaRegistry {
3837
+ byId;
3838
+ constructor(personas = []) {
3839
+ const entries = /* @__PURE__ */ new Map();
3840
+ for (const persona of personas) {
3841
+ const normalized = freezePersona(persona);
3842
+ if (entries.has(normalized.id)) {
3843
+ throw new Error(`CouncilPersonaRegistry: duplicate persona id "${normalized.id}".`);
3844
+ }
3845
+ entries.set(normalized.id, normalized);
3846
+ }
3847
+ this.byId = entries;
3848
+ }
3849
+ has(id) {
3850
+ return this.byId.has(id);
3851
+ }
3852
+ get(id) {
3853
+ return this.byId.get(id);
3854
+ }
3855
+ require(id) {
3856
+ const persona = this.get(id);
3857
+ if (!persona) throw new Error(`CouncilPersonaRegistry: unknown persona "${id}".`);
3858
+ return persona;
3859
+ }
3860
+ list() {
3861
+ return Object.freeze([...this.byId.values()]);
3862
+ }
3863
+ /** Return a new registry; the current registry is never mutated. */
3864
+ with(persona, opts = {}) {
3865
+ const normalized = freezePersona(persona);
3866
+ if (this.has(normalized.id) && opts.replace !== true) {
3867
+ throw new Error(`CouncilPersonaRegistry: persona "${normalized.id}" already exists.`);
3868
+ }
3869
+ return new _CouncilPersonaRegistry([
3870
+ ...this.list().filter((entry) => entry.id !== normalized.id),
3871
+ normalized
3872
+ ]);
3873
+ }
3874
+ };
3875
+ var DEFAULT_COUNCIL_PERSONA_REGISTRY = new CouncilPersonaRegistry(
3876
+ BUILTIN_COUNCIL_PERSONAS
3877
+ );
3878
+ function createCouncilPersonaRegistry(additional = []) {
3879
+ let registry = DEFAULT_COUNCIL_PERSONA_REGISTRY;
3880
+ for (const persona of additional) registry = registry.with(persona);
3881
+ return registry;
3882
+ }
3883
+ function freezePersona(persona) {
3884
+ const id = persona.id.trim();
3885
+ const name = persona.name.trim();
3886
+ const description = persona.description.trim();
3887
+ const instruction = persona.instruction.trim();
3888
+ if (!PERSONA_ID_RE.test(id)) {
3889
+ throw new Error(`CouncilPersonaRegistry: invalid persona id "${persona.id}".`);
3890
+ }
3891
+ if (!name) throw new Error(`CouncilPersonaRegistry: persona "${id}" requires a name.`);
3892
+ if (!description) {
3893
+ throw new Error(`CouncilPersonaRegistry: persona "${id}" requires a description.`);
3894
+ }
3895
+ if (!instruction) {
3896
+ throw new Error(`CouncilPersonaRegistry: persona "${id}" requires an instruction.`);
3897
+ }
3898
+ if (persona.defaultWeight !== void 0 && (!Number.isFinite(persona.defaultWeight) || persona.defaultWeight <= 0)) {
3899
+ throw new Error(`CouncilPersonaRegistry: persona "${id}" has an invalid default weight.`);
3900
+ }
3901
+ const tags = Object.freeze(
3902
+ [...new Set((persona.tags ?? []).map((tag) => tag.trim()).filter(Boolean))]
3903
+ );
3904
+ return Object.freeze({
3905
+ id,
3906
+ name,
3907
+ description,
3908
+ instruction,
3909
+ ...persona.defaultWeight !== void 0 ? { defaultWeight: persona.defaultWeight } : {},
3910
+ ...persona.defaultVeto !== void 0 ? { defaultVeto: persona.defaultVeto } : {},
3911
+ ...tags.length > 0 ? { tags } : {}
3912
+ });
3913
+ }
3914
+
3915
+ // src/execution/council-profiles.ts
3916
+ var PROFILE_ID_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
3917
+ var DISTINCTNESS_VALUES = /* @__PURE__ */ new Set([
3918
+ "none",
3919
+ "model",
3920
+ "provider"
3921
+ ]);
3922
+ var DEFAULT_COUNCIL_QUORUM_FRACTION = 0.5;
3923
+ var DEFAULT_COUNCIL_APPROVAL_FRACTION = 0.5;
3924
+ var DEFAULT_COUNCIL_VOTER_MAX_TOKENS = 300;
3925
+ var DEFAULT_COUNCIL_JUDGE_MAX_TOKENS = 500;
3926
+ var DEFAULT_COUNCIL_PER_CALL_TIMEOUT_MS = 3e4;
3927
+ var DEFAULT_COUNCIL_OVERALL_TIMEOUT_MS = 9e4;
3928
+ var BUILTIN_COUNCIL_PROFILES = Object.freeze([
3929
+ Object.freeze({
3930
+ id: "balanced",
3931
+ name: "Balanced",
3932
+ description: "Three complementary lenses plus an independent judge for general decisions.",
3933
+ seats: Object.freeze([
3934
+ Object.freeze({ persona: "executor", target: Object.freeze({ role: "planner" }) }),
3935
+ Object.freeze({ persona: "skeptic", target: Object.freeze({ role: "critic" }) }),
3936
+ Object.freeze({ persona: "auditor", target: Object.freeze({ role: "analyst" }) })
3937
+ ]),
3938
+ judge: Object.freeze({ role: "reviewer" }),
3939
+ quorumFraction: 0.5,
3940
+ approvalFraction: 0.5,
3941
+ distinctness: "model"
3942
+ }),
3943
+ Object.freeze({
3944
+ id: "fast",
3945
+ name: "Fast",
3946
+ description: "Two-seat panel without a judge for quick, low-cost decisions.",
3947
+ seats: Object.freeze([
3948
+ Object.freeze({ persona: "executor", target: Object.freeze({ role: "planner" }) }),
3949
+ Object.freeze({ persona: "skeptic", target: Object.freeze({ role: "critic" }) })
3950
+ ]),
3951
+ judge: false,
3952
+ quorumFraction: 0.5,
3953
+ approvalFraction: 0.5,
3954
+ distinctness: "none",
3955
+ voterMaxTokens: 200,
3956
+ perCallTimeoutMs: 2e4,
3957
+ overallTimeoutMs: 3e4
3958
+ }),
3959
+ Object.freeze({
3960
+ id: "risk-review",
3961
+ name: "Risk Review",
3962
+ description: "Security, assumptions, maintenance, and cost review for high-impact choices.",
3963
+ seats: Object.freeze([
3964
+ Object.freeze({ persona: "skeptic", target: Object.freeze({ role: "critic" }) }),
3965
+ Object.freeze({ persona: "security", target: Object.freeze({ role: "security-reviewer" }) }),
3966
+ Object.freeze({ persona: "maintainer", target: Object.freeze({ role: "reviewer" }) }),
3967
+ Object.freeze({ persona: "auditor", target: Object.freeze({ role: "analyst" }) })
3968
+ ]),
3969
+ judge: Object.freeze({ role: "architect" }),
3970
+ quorumFraction: 0.75,
3971
+ approvalFraction: 0.5,
3972
+ distinctness: "provider",
3973
+ judgeMaxTokens: 700,
3974
+ overallTimeoutMs: 12e4
3975
+ })
3976
+ ]);
3977
+ var CouncilProfileRegistry = class _CouncilProfileRegistry {
3978
+ byId;
3979
+ personas;
3980
+ constructor(profiles = [], personas = DEFAULT_COUNCIL_PERSONA_REGISTRY) {
3981
+ this.personas = personas;
3982
+ const entries = /* @__PURE__ */ new Map();
3983
+ for (const profile of profiles) {
3984
+ const normalized = normalizeCouncilProfile(profile, personas);
3985
+ if (entries.has(normalized.id)) {
3986
+ throw new Error(`CouncilProfileRegistry: duplicate profile id "${normalized.id}".`);
3987
+ }
3988
+ entries.set(normalized.id, normalized);
3989
+ }
3990
+ this.byId = entries;
3991
+ }
3992
+ has(id) {
3993
+ return this.byId.has(id);
3994
+ }
3995
+ get(id) {
3996
+ return this.byId.get(id);
3997
+ }
3998
+ require(id) {
3999
+ const profile = this.get(id);
4000
+ if (!profile) throw new Error(`CouncilProfileRegistry: unknown profile "${id}".`);
4001
+ return profile;
4002
+ }
4003
+ list() {
4004
+ return Object.freeze([...this.byId.values()]);
4005
+ }
4006
+ /** Return a new registry; the current registry is never mutated. */
4007
+ with(profile, opts = {}) {
4008
+ const personas = opts.personas ?? this.personas;
4009
+ const normalized = normalizeCouncilProfile(profile, personas);
4010
+ if (this.has(normalized.id) && opts.replace !== true) {
4011
+ throw new Error(`CouncilProfileRegistry: profile "${normalized.id}" already exists.`);
4012
+ }
4013
+ return new _CouncilProfileRegistry(
4014
+ [
4015
+ ...this.list().filter((entry) => entry.id !== normalized.id),
4016
+ profile
4017
+ ],
4018
+ personas
4019
+ );
4020
+ }
4021
+ };
4022
+ var DEFAULT_COUNCIL_PROFILE_REGISTRY = new CouncilProfileRegistry(
4023
+ BUILTIN_COUNCIL_PROFILES
4024
+ );
4025
+ function createCouncilProfileRegistry(additional = [], personas = DEFAULT_COUNCIL_PERSONA_REGISTRY) {
4026
+ return new CouncilProfileRegistry([...BUILTIN_COUNCIL_PROFILES, ...additional], personas);
4027
+ }
4028
+ function resolveCouncilProfile(profile, opts = {}) {
4029
+ const personas = opts.personas ?? DEFAULT_COUNCIL_PERSONA_REGISTRY;
4030
+ if (profile && typeof profile !== "string") return normalizeCouncilProfile(profile, personas);
4031
+ const id = profile ?? opts.defaultProfile ?? "balanced";
4032
+ return (opts.registry ?? DEFAULT_COUNCIL_PROFILE_REGISTRY).require(id);
4033
+ }
4034
+ function normalizeCouncilProfile(profile, personas = DEFAULT_COUNCIL_PERSONA_REGISTRY) {
4035
+ const id = profile.id.trim();
4036
+ if (!PROFILE_ID_RE.test(id)) {
4037
+ throw new Error(`CouncilProfileRegistry: invalid profile id "${profile.id}".`);
4038
+ }
4039
+ if (profile.seats.length === 0) {
4040
+ throw new Error(`CouncilProfileRegistry: profile "${id}" requires at least one seat.`);
4041
+ }
4042
+ const usedIds = /* @__PURE__ */ new Set();
4043
+ const seats = profile.seats.map((seat) => {
4044
+ const persona = personas.require(seat.persona.trim());
4045
+ const explicitSeatId = seat.id?.trim();
4046
+ if (explicitSeatId && usedIds.has(explicitSeatId)) {
4047
+ throw new Error(`CouncilProfileRegistry: duplicate seat id "${explicitSeatId}".`);
4048
+ }
4049
+ const seatId = uniqueSeatId(explicitSeatId || persona.id, usedIds);
4050
+ const label = seat.label?.trim() || persona.name;
4051
+ const weight = seat.weight ?? persona.defaultWeight ?? 1;
4052
+ if (!Number.isFinite(weight) || weight <= 0) {
4053
+ throw new Error(`CouncilProfileRegistry: seat "${seatId}" has an invalid weight.`);
4054
+ }
4055
+ return Object.freeze({
4056
+ id: seatId,
4057
+ label,
4058
+ persona: persona.id,
4059
+ ...seat.target ? { target: freezeTarget(seat.target, `seat "${seatId}"`) } : {},
4060
+ weight,
4061
+ veto: seat.veto ?? persona.defaultVeto ?? false
4062
+ });
4063
+ });
4064
+ const quorumFraction = fraction(
4065
+ profile.quorumFraction ?? DEFAULT_COUNCIL_QUORUM_FRACTION,
4066
+ "quorumFraction",
4067
+ id
4068
+ );
4069
+ const approvalFraction = fraction(
4070
+ profile.approvalFraction ?? DEFAULT_COUNCIL_APPROVAL_FRACTION,
4071
+ "approvalFraction",
4072
+ id
4073
+ );
4074
+ const distinctness = profile.distinctness ?? "model";
4075
+ if (!DISTINCTNESS_VALUES.has(distinctness)) {
4076
+ throw new Error(`CouncilProfileRegistry: profile "${id}" has invalid distinctness.`);
4077
+ }
4078
+ const voterMaxTokens = positiveInteger(
4079
+ profile.voterMaxTokens ?? DEFAULT_COUNCIL_VOTER_MAX_TOKENS,
4080
+ "voterMaxTokens",
4081
+ id
4082
+ );
4083
+ const judgeMaxTokens = positiveInteger(
4084
+ profile.judgeMaxTokens ?? DEFAULT_COUNCIL_JUDGE_MAX_TOKENS,
4085
+ "judgeMaxTokens",
4086
+ id
4087
+ );
4088
+ const perCallTimeoutMs = positiveInteger(
4089
+ profile.perCallTimeoutMs ?? DEFAULT_COUNCIL_PER_CALL_TIMEOUT_MS,
4090
+ "perCallTimeoutMs",
4091
+ id
4092
+ );
4093
+ const overallTimeoutMs = positiveInteger(
4094
+ profile.overallTimeoutMs ?? DEFAULT_COUNCIL_OVERALL_TIMEOUT_MS,
4095
+ "overallTimeoutMs",
4096
+ id
4097
+ );
4098
+ if (overallTimeoutMs < perCallTimeoutMs) {
4099
+ throw new Error(
4100
+ `CouncilProfileRegistry: profile "${id}" overallTimeoutMs must be at least perCallTimeoutMs.`
4101
+ );
4102
+ }
4103
+ return Object.freeze({
4104
+ id,
4105
+ name: profile.name?.trim() || id,
4106
+ description: profile.description?.trim() || "",
4107
+ seats: Object.freeze(seats),
4108
+ judge: profile.judge ? freezeTarget(profile.judge, "judge") : false,
4109
+ quorumFraction,
4110
+ approvalFraction,
4111
+ distinctness,
4112
+ voterMaxTokens,
4113
+ judgeMaxTokens,
4114
+ perCallTimeoutMs,
4115
+ overallTimeoutMs
4116
+ });
4117
+ }
4118
+ function uniqueSeatId(base, used) {
4119
+ if (!PROFILE_ID_RE.test(base)) {
4120
+ throw new Error(`CouncilProfileRegistry: invalid seat id "${base}".`);
4121
+ }
4122
+ let id = base;
4123
+ let suffix = 2;
4124
+ while (used.has(id)) id = `${base}-${suffix++}`;
4125
+ used.add(id);
4126
+ return id;
4127
+ }
4128
+ function freezeTarget(target, label) {
4129
+ const providerId = optionalText(target.providerId);
4130
+ const model = optionalText(target.model);
4131
+ const role = optionalText(target.role);
4132
+ const fallbackProfile = optionalText(target.fallbackProfile);
4133
+ const fallbackModels = Object.freeze(
4134
+ [...new Set((target.fallbackModels ?? []).map((ref) => ref.trim()).filter(Boolean))]
4135
+ );
4136
+ if (!providerId && !model && !role && !fallbackProfile && fallbackModels.length === 0) {
4137
+ throw new Error(`CouncilProfileRegistry: ${label} target is empty.`);
4138
+ }
4139
+ return Object.freeze({
4140
+ ...providerId ? { providerId } : {},
4141
+ ...model ? { model } : {},
4142
+ ...role ? { role } : {},
4143
+ ...fallbackProfile ? { fallbackProfile } : {},
4144
+ ...fallbackModels.length > 0 ? { fallbackModels } : {}
4145
+ });
4146
+ }
4147
+ function optionalText(value) {
4148
+ const trimmed = value?.trim();
4149
+ return trimmed || void 0;
4150
+ }
4151
+ function fraction(value, field, profileId) {
4152
+ if (!Number.isFinite(value) || value <= 0 || value > 1) {
4153
+ throw new Error(`CouncilProfileRegistry: profile "${profileId}" ${field} must be in (0, 1].`);
4154
+ }
4155
+ return value;
4156
+ }
4157
+ function positiveInteger(value, field, profileId) {
4158
+ if (!Number.isSafeInteger(value) || value <= 0) {
4159
+ throw new Error(
4160
+ `CouncilProfileRegistry: profile "${profileId}" ${field} must be a positive integer.`
4161
+ );
4162
+ }
4163
+ return value;
4164
+ }
4165
+
4166
+ // src/execution/council-prompts.ts
4167
+ var COUNCIL_VOTER_PROMPT_PATH = "llm/council-voter.md";
4168
+ var COUNCIL_JUDGE_PROMPT_PATH = "llm/council-judge.md";
4169
+ function buildCouncilVoterSystemPrompt(persona) {
4170
+ const template = requiredInstruction(COUNCIL_VOTER_PROMPT_PATH);
4171
+ return renderInstructionTemplate(template, {
4172
+ personaInstruction: persona.instruction
4173
+ });
4174
+ }
4175
+ function buildCouncilJudgeSystemPrompt() {
4176
+ return requiredInstruction(COUNCIL_JUDGE_PROMPT_PATH);
4177
+ }
4178
+ function buildCouncilQuestionPrompt(question, opts = {}) {
4179
+ const text = question.question.trim();
4180
+ if (!text) throw new Error("buildCouncilQuestionPrompt: question must not be empty.");
4181
+ const options = normalizeOptions(question.options);
4182
+ return [
4183
+ "<council-question>",
4184
+ `Question: ${text}`,
4185
+ question.context?.trim() ? `Context:
4186
+ ${question.context.trim()}` : "",
4187
+ options.length > 0 ? `Options (JSON):
4188
+ ${JSON.stringify(options)}` : "Options: none",
4189
+ opts.refusalOptionId ? `Refusal option id: ${opts.refusalOptionId}` : "Refusal option id: not supplied",
4190
+ "</council-question>"
4191
+ ].filter(Boolean).join("\n\n");
4192
+ }
4193
+ function buildCouncilVoterUserPrompt(question, seat, opts = {}) {
4194
+ return [
4195
+ buildCouncilQuestionPrompt(question, opts),
4196
+ "<seat-metadata>",
4197
+ `Seat id: ${seat.id}`,
4198
+ `Seat label: ${seat.label}`,
4199
+ `Persona id: ${seat.persona}`,
4200
+ "</seat-metadata>"
4201
+ ].join("\n");
4202
+ }
4203
+ function buildCouncilJudgeUserPrompt(question, votes, opts = {}) {
4204
+ const ballots = votes.map((vote) => ({
4205
+ seatId: vote.seatId,
4206
+ persona: vote.persona,
4207
+ status: vote.status,
4208
+ ...vote.optionId ? { optionId: vote.optionId } : {},
4209
+ ...vote.stance ? { stance: vote.stance } : {},
4210
+ ...vote.rationale ? { rationale: vote.rationale } : {}
4211
+ }));
4212
+ return [
4213
+ buildCouncilQuestionPrompt(question, { refusalOptionId: opts.refusalOptionId }),
4214
+ "<council-ballots>",
4215
+ opts.reason?.trim() ? `Reason judging is required: ${opts.reason.trim()}` : "",
4216
+ JSON.stringify(ballots),
4217
+ "</council-ballots>"
4218
+ ].filter(Boolean).join("\n\n");
4219
+ }
4220
+ function normalizeOptions(options) {
4221
+ if (!options) return [];
4222
+ const seen = /* @__PURE__ */ new Set();
4223
+ return options.map((option) => {
4224
+ const id = option.id.trim();
4225
+ const label = option.label.trim();
4226
+ if (!id) throw new Error("buildCouncilQuestionPrompt: option id must not be empty.");
4227
+ if (!label) throw new Error(`buildCouncilQuestionPrompt: option "${id}" needs a label.`);
4228
+ if (seen.has(id)) throw new Error(`buildCouncilQuestionPrompt: duplicate option id "${id}".`);
4229
+ seen.add(id);
4230
+ return {
4231
+ id,
4232
+ label,
4233
+ ...option.consequence?.trim() ? { consequence: option.consequence.trim() } : {}
4234
+ };
4235
+ });
4236
+ }
4237
+ function requiredInstruction(path11) {
4238
+ const text = readBundledInstructionText(path11);
4239
+ if (!text) throw new Error(`Council instruction file is unavailable: ${path11}`);
4240
+ return text;
4241
+ }
4242
+
4243
+ // src/execution/council-orchestrator.ts
4244
+ var COUNCIL_REFUSAL_OPTION_ID = "council_refuse";
4245
+ var DEFAULT_COUNCIL_MAX_CONCURRENCY = 3;
4246
+ var MAX_COUNCIL_CONCURRENCY = 8;
4247
+ var CouncilOrchestrator = class {
4248
+ caller;
4249
+ personas;
4250
+ profiles;
4251
+ defaultProfile;
4252
+ maxConcurrency;
4253
+ refusalOptionId;
4254
+ constructor(opts) {
4255
+ this.caller = opts.caller;
4256
+ this.personas = opts.personas ?? DEFAULT_COUNCIL_PERSONA_REGISTRY;
4257
+ this.profiles = opts.profiles ?? DEFAULT_COUNCIL_PROFILE_REGISTRY;
4258
+ this.defaultProfile = opts.defaultProfile;
4259
+ this.maxConcurrency = validateConcurrency(
4260
+ opts.maxConcurrency ?? DEFAULT_COUNCIL_MAX_CONCURRENCY
4261
+ );
4262
+ this.refusalOptionId = opts.refusalOptionId?.trim() || COUNCIL_REFUSAL_OPTION_ID;
4263
+ }
4264
+ async ask(question) {
4265
+ const startedAt = Date.now();
4266
+ const profile = resolveCouncilProfile(question.profile, {
4267
+ registry: this.profiles,
4268
+ personas: this.personas,
4269
+ defaultProfile: this.defaultProfile
4270
+ });
4271
+ validateRefusalCollision(question, this.refusalOptionId);
4272
+ const timeoutSignal = AbortSignal.timeout(profile.overallTimeoutMs);
4273
+ const signal = question.signal ? AbortSignal.any([question.signal, timeoutSignal]) : timeoutSignal;
4274
+ const usage = {
4275
+ calls: 0,
4276
+ inputTokens: 0,
4277
+ outputTokens: 0,
4278
+ totalTokens: 0
4279
+ };
4280
+ const votes = await mapConcurrent(
4281
+ profile.seats,
4282
+ Math.min(this.maxConcurrency, profile.seats.length),
4283
+ async (seat) => {
4284
+ try {
4285
+ return await this.callSeat(question, profile, seat, signal, usage);
4286
+ } catch (error) {
4287
+ return {
4288
+ seatId: seat.id,
4289
+ persona: seat.persona,
4290
+ status: signal.aborted ? "cancelled" : "failed",
4291
+ error: errorMessage(error)
4292
+ };
4293
+ }
4294
+ }
4295
+ );
4296
+ const warnings = distinctnessWarnings(votes, profile);
4297
+ const errors = votes.filter((vote) => vote.status === "failed" || vote.status === "invalid").map((vote) => `${vote.seatId}: ${vote.error ?? vote.status}`);
4298
+ if (question.signal?.aborted) {
4299
+ return resultEnvelope({
4300
+ status: "cancelled",
4301
+ reason: "Council call cancelled.",
4302
+ resolution: "none",
4303
+ votes,
4304
+ profile,
4305
+ usage,
4306
+ startedAt,
4307
+ warnings,
4308
+ errors
4309
+ });
4310
+ }
4311
+ if (timeoutSignal.aborted) {
4312
+ return resultEnvelope({
4313
+ status: "failed",
4314
+ reason: "Council overall timeout exceeded.",
4315
+ resolution: "none",
4316
+ votes,
4317
+ profile,
4318
+ usage,
4319
+ startedAt,
4320
+ warnings,
4321
+ errors: [...errors, "Council overall timeout exceeded."]
4322
+ });
4323
+ }
4324
+ if (!question.options || question.options.length === 0) {
4325
+ return this.resolveOpenQuestion(
4326
+ question,
4327
+ profile,
4328
+ votes,
4329
+ signal,
4330
+ usage,
4331
+ startedAt,
4332
+ warnings,
4333
+ errors
4334
+ );
4335
+ }
4336
+ return this.resolveOptionQuestion(
4337
+ question,
4338
+ profile,
4339
+ votes,
4340
+ signal,
4341
+ usage,
4342
+ startedAt,
4343
+ warnings,
4344
+ errors
4345
+ );
4346
+ }
4347
+ async callSeat(question, profile, seat, signal, usage) {
4348
+ if (signal.aborted) return cancelledVote(seat);
4349
+ let persona;
4350
+ try {
4351
+ persona = this.personas.require(seat.persona);
4352
+ } catch (error) {
4353
+ return {
4354
+ seatId: seat.id,
4355
+ persona: seat.persona,
4356
+ status: "failed",
4357
+ error: errorMessage(error)
4358
+ };
4359
+ }
4360
+ const result = await this.safeCall({
4361
+ system: buildCouncilVoterSystemPrompt(persona),
4362
+ userPrompt: buildCouncilVoterUserPrompt(question, seat, {
4363
+ refusalOptionId: question.options?.length ? this.refusalOptionId : void 0
4364
+ }),
4365
+ target: seat.target,
4366
+ maxTokens: profile.voterMaxTokens,
4367
+ timeoutMs: profile.perCallTimeoutMs,
4368
+ signal,
4369
+ usage
4370
+ });
4371
+ const metadata = callMetadata(result);
4372
+ if (result.error) {
4373
+ return {
4374
+ seatId: seat.id,
4375
+ persona: seat.persona,
4376
+ status: signal.aborted ? "cancelled" : "failed",
4377
+ ...metadata,
4378
+ error: result.error
4379
+ };
4380
+ }
4381
+ const parsed = parseVote(result.text, question, this.refusalOptionId);
4382
+ if (!parsed.ok) {
4383
+ return {
4384
+ seatId: seat.id,
4385
+ persona: seat.persona,
4386
+ status: "invalid",
4387
+ ...metadata,
4388
+ error: parsed.error
4389
+ };
4390
+ }
4391
+ return {
4392
+ seatId: seat.id,
4393
+ persona: seat.persona,
4394
+ status: "valid",
4395
+ ...parsed.vote,
4396
+ ...metadata
4397
+ };
4398
+ }
4399
+ async resolveOptionQuestion(question, profile, votes, signal, usage, startedAt, warnings, errors) {
4400
+ const validVotes = votes.filter(
4401
+ (vote) => vote.status === "valid" && typeof vote.optionId === "string"
4402
+ );
4403
+ const resolution = resolveCouncilVotes({
4404
+ seats: profile.seats.map((seat) => ({
4405
+ id: seat.id,
4406
+ weight: seat.weight,
4407
+ veto: seat.veto
4408
+ })),
4409
+ votes: validVotes.map((vote) => ({ seatId: vote.seatId, optionId: vote.optionId })),
4410
+ refusalOptionId: this.refusalOptionId,
4411
+ quorumFraction: profile.quorumFraction,
4412
+ approvalFraction: profile.approvalFraction
4413
+ });
4414
+ if (resolution.status === "abstained") {
4415
+ return resultEnvelope({
4416
+ status: "abstained",
4417
+ reason: "Council quorum was not met.",
4418
+ resolution: "none",
4419
+ votes,
4420
+ profile,
4421
+ usage,
4422
+ startedAt,
4423
+ warnings,
4424
+ errors
4425
+ });
4426
+ }
4427
+ if (resolution.status === "denied") {
4428
+ return resultEnvelope({
4429
+ status: "denied",
4430
+ optionId: resolution.optionId,
4431
+ reason: `Council denied the proposal via ${resolution.method}.`,
4432
+ resolution: resolution.method,
4433
+ votes,
4434
+ profile,
4435
+ usage,
4436
+ startedAt,
4437
+ warnings,
4438
+ errors
4439
+ });
4440
+ }
4441
+ if (resolution.status === "decided") {
4442
+ return resultEnvelope({
4443
+ status: "decided",
4444
+ optionId: resolution.optionId,
4445
+ answer: optionLabel(question, resolution.optionId),
4446
+ resolution: "majority",
4447
+ votes,
4448
+ profile,
4449
+ usage,
4450
+ startedAt,
4451
+ warnings,
4452
+ errors
4453
+ });
4454
+ }
4455
+ if (!profile.judge) {
4456
+ return resultEnvelope({
4457
+ status: "abstained",
4458
+ reason: `Council requires a judge (${resolution.reason}), but this profile has none.`,
4459
+ resolution: "none",
4460
+ votes,
4461
+ profile,
4462
+ usage,
4463
+ startedAt,
4464
+ warnings,
4465
+ errors
4466
+ });
4467
+ }
4468
+ const judged = await this.callJudge(
4469
+ question,
4470
+ profile,
4471
+ votes,
4472
+ profile.judge,
4473
+ resolution.reason,
4474
+ signal,
4475
+ usage
4476
+ );
4477
+ if (!judged.ok) {
4478
+ return resultEnvelope({
4479
+ status: signal.aborted ? "cancelled" : "abstained",
4480
+ reason: judged.error,
4481
+ resolution: "none",
4482
+ votes,
4483
+ profile,
4484
+ usage,
4485
+ startedAt,
4486
+ warnings,
4487
+ errors: [...errors, judged.error],
4488
+ judgeUsed: true
4489
+ });
4490
+ }
4491
+ if (judged.value.optionId === this.refusalOptionId) {
4492
+ return resultEnvelope({
4493
+ status: "denied",
4494
+ optionId: this.refusalOptionId,
4495
+ reason: judged.value.rationale ?? "Council judge refused all options.",
4496
+ resolution: "judge",
4497
+ votes,
4498
+ profile,
4499
+ usage,
4500
+ startedAt,
4501
+ warnings,
4502
+ errors,
4503
+ judgeUsed: true
4504
+ });
4505
+ }
4506
+ return resultEnvelope({
4507
+ status: "decided",
4508
+ optionId: judged.value.optionId,
4509
+ answer: optionLabel(question, judged.value.optionId),
4510
+ reason: judged.value.rationale,
4511
+ resolution: "judge",
4512
+ votes,
4513
+ profile,
4514
+ usage,
4515
+ startedAt,
4516
+ warnings,
4517
+ errors,
4518
+ judgeUsed: true
4519
+ });
4520
+ }
4521
+ async resolveOpenQuestion(question, profile, votes, signal, usage, startedAt, warnings, errors) {
4522
+ const valid = votes.filter(
4523
+ (vote) => vote.status === "valid" && typeof vote.stance === "string"
4524
+ );
4525
+ if (valid.length / profile.seats.length < profile.quorumFraction) {
4526
+ return resultEnvelope({
4527
+ status: "abstained",
4528
+ reason: "Council quorum was not met.",
4529
+ resolution: "none",
4530
+ votes,
4531
+ profile,
4532
+ usage,
4533
+ startedAt,
4534
+ warnings,
4535
+ errors
4536
+ });
4537
+ }
4538
+ if (!profile.judge) {
4539
+ const first = valid[0];
4540
+ if (!first) {
4541
+ return resultEnvelope({
4542
+ status: "failed",
4543
+ reason: "Council produced no valid stance.",
4544
+ resolution: "none",
4545
+ votes,
4546
+ profile,
4547
+ usage,
4548
+ startedAt,
4549
+ warnings,
4550
+ errors
4551
+ });
4552
+ }
4553
+ return resultEnvelope({
4554
+ status: "decided",
4555
+ answer: first.stance,
4556
+ reason: first.rationale,
4557
+ resolution: "first_stance",
4558
+ votes,
4559
+ profile,
4560
+ usage,
4561
+ startedAt,
4562
+ warnings,
4563
+ errors
4564
+ });
4565
+ }
4566
+ const judged = await this.callJudge(
4567
+ question,
4568
+ profile,
4569
+ votes,
4570
+ profile.judge,
4571
+ "open_question_synthesis",
4572
+ signal,
4573
+ usage
4574
+ );
4575
+ if (!judged.ok) {
4576
+ return resultEnvelope({
4577
+ status: signal.aborted ? "cancelled" : "failed",
4578
+ reason: judged.error,
4579
+ resolution: "none",
4580
+ votes,
4581
+ profile,
4582
+ usage,
4583
+ startedAt,
4584
+ warnings,
4585
+ errors: [...errors, judged.error],
4586
+ judgeUsed: true
4587
+ });
4588
+ }
4589
+ return resultEnvelope({
4590
+ status: "decided",
4591
+ answer: judged.value.answer,
4592
+ reason: judged.value.rationale,
4593
+ resolution: "judge",
4594
+ votes,
4595
+ profile,
4596
+ usage,
4597
+ startedAt,
4598
+ warnings,
4599
+ errors,
4600
+ judgeUsed: true
4601
+ });
4602
+ }
4603
+ async callJudge(question, profile, votes, target, reason, signal, usage) {
4604
+ const result = await this.safeCall({
4605
+ system: buildCouncilJudgeSystemPrompt(),
4606
+ userPrompt: buildCouncilJudgeUserPrompt(question, votes, {
4607
+ reason,
4608
+ refusalOptionId: question.options?.length ? this.refusalOptionId : void 0
4609
+ }),
4610
+ target,
4611
+ maxTokens: profile.judgeMaxTokens,
4612
+ timeoutMs: profile.perCallTimeoutMs,
4613
+ signal,
4614
+ usage
4615
+ });
4616
+ if (result.error) return { ok: false, error: result.error };
4617
+ return parseJudge(result.text, question, this.refusalOptionId);
4618
+ }
4619
+ async safeCall(input) {
4620
+ try {
4621
+ const result = await this.caller.call({
4622
+ system: input.system,
4623
+ userPrompt: input.userPrompt,
4624
+ responseFormat: { type: "json_object" },
4625
+ maxTokens: input.maxTokens,
4626
+ timeoutMs: input.timeoutMs,
4627
+ signal: input.signal,
4628
+ ...input.target?.providerId ? { providerId: input.target.providerId } : {},
4629
+ ...input.target?.model ? { model: input.target.model } : {},
4630
+ ...input.target?.role ? { role: input.target.role } : {},
4631
+ ...input.target?.fallbackProfile ? { fallbackProfile: input.target.fallbackProfile } : {},
4632
+ ...input.target?.fallbackModels ? { fallbackModels: [...input.target.fallbackModels] } : {}
4633
+ });
4634
+ addUsage(input.usage, result);
4635
+ return result;
4636
+ } catch (error) {
4637
+ input.usage.calls += 1;
4638
+ return emptyCallResult(errorMessage(error));
4639
+ }
4640
+ }
4641
+ };
4642
+ function parseVote(text, question, refusalOptionId) {
4643
+ const parsed = parseObject(text);
4644
+ if (!parsed.ok) return parsed;
4645
+ const rationale = optionalString(parsed.value["rationale"]);
4646
+ if (question.options && question.options.length > 0) {
4647
+ const optionId = optionalString(parsed.value["optionId"]);
4648
+ const allowed = /* @__PURE__ */ new Set([...question.options.map((option) => option.id.trim()), refusalOptionId]);
4649
+ if (!optionId || !allowed.has(optionId)) {
4650
+ return { ok: false, error: "Voter returned an unknown or missing optionId." };
4651
+ }
4652
+ return { ok: true, vote: { optionId, ...rationale ? { rationale } : {} } };
4653
+ }
4654
+ const stance = optionalString(parsed.value["stance"]);
4655
+ if (!stance) return { ok: false, error: "Voter returned an empty or missing stance." };
4656
+ return { ok: true, vote: { stance, ...rationale ? { rationale } : {} } };
4657
+ }
4658
+ function parseJudge(text, question, refusalOptionId) {
4659
+ const parsed = parseObject(text);
4660
+ if (!parsed.ok) return parsed;
4661
+ const rationale = optionalString(parsed.value["rationale"]);
4662
+ if (question.options && question.options.length > 0) {
4663
+ const optionId = optionalString(parsed.value["optionId"]);
4664
+ const allowed = /* @__PURE__ */ new Set([...question.options.map((option) => option.id.trim()), refusalOptionId]);
4665
+ if (!optionId || !allowed.has(optionId)) {
4666
+ return { ok: false, error: "Judge returned an unknown or missing optionId." };
4667
+ }
4668
+ return { ok: true, value: { optionId, ...rationale ? { rationale } : {} } };
4669
+ }
4670
+ const answer = optionalString(parsed.value["answer"]);
4671
+ if (!answer) return { ok: false, error: "Judge returned an empty or missing answer." };
4672
+ return { ok: true, value: { answer, ...rationale ? { rationale } : {} } };
4673
+ }
4674
+ function parseObject(text) {
4675
+ const trimmed = text.trim();
4676
+ const first = trimmed.indexOf("{");
4677
+ const last = trimmed.lastIndexOf("}");
4678
+ if (first < 0 || last < first) return { ok: false, error: "LLM response did not contain JSON." };
4679
+ try {
4680
+ const value = JSON.parse(trimmed.slice(first, last + 1));
4681
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
4682
+ return { ok: false, error: "LLM response JSON must be an object." };
4683
+ }
4684
+ return { ok: true, value };
4685
+ } catch (error) {
4686
+ return { ok: false, error: `Invalid LLM response JSON: ${errorMessage(error)}` };
4687
+ }
4688
+ }
4689
+ function resultEnvelope(input) {
4690
+ const validVoteCount = input.votes.filter((vote) => vote.status === "valid").length;
4691
+ return {
4692
+ status: input.status,
4693
+ ...input.answer ? { answer: input.answer } : {},
4694
+ ...input.optionId ? { optionId: input.optionId } : {},
4695
+ ...input.reason ? { reason: input.reason } : {},
4696
+ resolution: input.resolution,
4697
+ votes: Object.freeze([...input.votes]),
4698
+ configuredSeatCount: input.profile.seats.length,
4699
+ validVoteCount,
4700
+ distinctTargetCount: distinctTargetCount(input.votes, input.profile),
4701
+ judgeUsed: input.judgeUsed ?? false,
4702
+ usage: usageResult(input.usage, input.startedAt),
4703
+ ...input.warnings.length > 0 ? { warnings: Object.freeze([...input.warnings]) } : {},
4704
+ ...input.errors.length > 0 ? { errors: Object.freeze([...input.errors]) } : {}
4705
+ };
4706
+ }
4707
+ function callMetadata(result) {
4708
+ return {
4709
+ ...result.provider ? { provider: result.provider } : {},
4710
+ ...result.model ? { model: result.model } : {},
4711
+ ...result.fromFallback ? { fromFallback: true } : {},
4712
+ durationMs: result.durationMs
4713
+ };
4714
+ }
4715
+ function addUsage(usage, result) {
4716
+ usage.calls += 1;
4717
+ usage.inputTokens += result.tokens.input;
4718
+ usage.outputTokens += result.tokens.output;
4719
+ usage.totalTokens += result.tokens.total;
4720
+ }
4721
+ function usageResult(usage, startedAt) {
4722
+ return Object.freeze({ ...usage, durationMs: Math.max(0, Date.now() - startedAt) });
4723
+ }
4724
+ function cancelledVote(seat) {
4725
+ return { seatId: seat.id, persona: seat.persona, status: "cancelled", error: "Cancelled." };
4726
+ }
4727
+ function distinctTargetCount(votes, profile) {
4728
+ const keys = votes.filter((vote) => vote.status === "valid").map(
4729
+ (vote) => profile.distinctness === "provider" ? vote.provider : `${vote.provider ?? ""}/${vote.model ?? ""}`
4730
+ ).filter(Boolean);
4731
+ return new Set(keys).size;
4732
+ }
4733
+ function distinctnessWarnings(votes, profile) {
4734
+ if (profile.distinctness === "none") return [];
4735
+ const valid = votes.filter((vote) => vote.status === "valid");
4736
+ const distinct = distinctTargetCount(valid, profile);
4737
+ if (valid.length > 1 && distinct < valid.length) {
4738
+ return [
4739
+ `Council distinctness policy "${profile.distinctness}" was not met: ${distinct} distinct target(s) served ${valid.length} valid vote(s).`
4740
+ ];
4741
+ }
4742
+ return [];
4743
+ }
4744
+ function optionLabel(question, optionId) {
4745
+ if (!optionId) return void 0;
4746
+ return question.options?.find((option) => option.id.trim() === optionId)?.label.trim();
4747
+ }
4748
+ function validateRefusalCollision(question, refusalOptionId) {
4749
+ if (question.options?.some((option) => option.id.trim() === refusalOptionId)) {
4750
+ throw new Error(`CouncilOrchestrator: option id "${refusalOptionId}" is reserved.`);
4751
+ }
4752
+ }
4753
+ function validateConcurrency(value) {
4754
+ if (!Number.isSafeInteger(value) || value <= 0 || value > MAX_COUNCIL_CONCURRENCY) {
4755
+ throw new Error(
4756
+ `CouncilOrchestrator: maxConcurrency must be an integer in [1, ${MAX_COUNCIL_CONCURRENCY}].`
4757
+ );
4758
+ }
4759
+ return value;
4760
+ }
4761
+ async function mapConcurrent(items, concurrency, worker) {
4762
+ const results = new Array(items.length);
4763
+ let next = 0;
4764
+ const run = async () => {
4765
+ while (true) {
4766
+ const index = next++;
4767
+ if (index >= items.length) return;
4768
+ const item = items[index];
4769
+ if (item !== void 0) results[index] = await worker(item, index);
4770
+ }
4771
+ };
4772
+ await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => run()));
4773
+ return results;
4774
+ }
4775
+ function emptyCallResult(error) {
4776
+ return {
4777
+ text: "",
4778
+ model: "",
4779
+ provider: "",
4780
+ tokens: { input: 0, output: 0, total: 0 },
4781
+ durationMs: 0,
4782
+ fromFallback: false,
4783
+ error
4784
+ };
4785
+ }
4786
+ function optionalString(value) {
4787
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
4788
+ }
4789
+ function errorMessage(error) {
4790
+ return error instanceof Error ? error.message : String(error);
4791
+ }
4792
+
3573
4793
  // src/storage/goal-store.ts
3574
4794
  import * as fsp from "node:fs/promises";
3575
4795
  var MAX_JOURNAL_ENTRIES = 500;
@@ -3943,7 +5163,7 @@ function eliseOldToolResults(messages, opts) {
3943
5163
  }
3944
5164
  if (newContent) {
3945
5165
  next ??= messages.slice();
3946
- next[i] = { ...msg, content: newContent };
5166
+ next[i] = { ...msg, content: newContent, _estTokens: void 0 };
3947
5167
  changed = true;
3948
5168
  }
3949
5169
  if (compactionDebugEnabled()) {
@@ -4337,7 +5557,7 @@ function isDesignStack(v) {
4337
5557
 
4338
5558
  // src/execution/design-kit-loader.ts
4339
5559
  import { existsSync } from "node:fs";
4340
- import * as fs2 from "node:fs/promises";
5560
+ import * as fs3 from "node:fs/promises";
4341
5561
  import * as path4 from "node:path";
4342
5562
  import { fileURLToPath as fileURLToPath2 } from "node:url";
4343
5563
  var KIT_FILE = "KIT.md";
@@ -4377,17 +5597,17 @@ function parseKitFrontmatter(raw) {
4377
5597
  else out[key] = items;
4378
5598
  };
4379
5599
  for (const line of lines) {
4380
- const item = /^\s*-\s+(.*)$/.exec(line);
4381
- if (listKey && item) {
4382
- const parsed = parseList(item[1] ?? "");
5600
+ const bulletMatch = /^\s*-\s+(.*)$/.exec(line);
5601
+ if (listKey && bulletMatch) {
5602
+ const parsed = parseList(bulletMatch[1] ?? "");
4383
5603
  if (listKey === "stacks") out.stacks.push(...parsed.filter(isDesignStack));
4384
5604
  else out[listKey].push(...parsed);
4385
5605
  continue;
4386
5606
  }
4387
- const m = /^([a-zA-Z_]+):\s*(.*)$/.exec(line);
4388
- if (!m) continue;
4389
- const key = m[1] ?? "";
4390
- const rest = (m[2] ?? "").trim();
5607
+ const kvMatch = /^([a-zA-Z_]+):\s*(.*)$/.exec(line);
5608
+ if (!kvMatch) continue;
5609
+ const key = kvMatch[1] ?? "";
5610
+ const rest = (kvMatch[2] ?? "").trim();
4391
5611
  if (key === "tags" || key === "stacks" || key === "themes") {
4392
5612
  if (rest) {
4393
5613
  setList(key, parseList(rest));
@@ -4404,24 +5624,23 @@ function parseKitFrontmatter(raw) {
4404
5624
  }
4405
5625
  function narrowStackSections(body, stack) {
4406
5626
  if (!stack) return body;
4407
- const re = /^##\s+Stack:\s*([a-z-]+)\s*$/gim;
4408
- const matches = [];
4409
- let m;
4410
- while ((m = re.exec(body)) !== null) {
4411
- matches.push({ id: (m[1] ?? "").trim(), start: m.index });
4412
- }
4413
- if (matches.length === 0) return body;
4414
- const firstStart = matches[0]?.start ?? body.length;
4415
- let result = body.slice(0, firstStart).trimEnd();
4416
- for (let i = 0; i < matches.length; i++) {
4417
- const cur = matches[i];
4418
- if (!cur) continue;
4419
- const next = matches[i + 1];
4420
- const sectionEnd = next ? next.start : body.length;
4421
- if (cur.id === stack) {
5627
+ const stackHeading = /^##\s+Stack:\s*([a-z-]+)\s*$/gim;
5628
+ const sections = [];
5629
+ let match;
5630
+ while ((match = stackHeading.exec(body)) !== null) {
5631
+ sections.push({ id: (match[1] ?? "").trim(), start: match.index });
5632
+ }
5633
+ if (sections.length === 0) return body;
5634
+ const preambleEnd = sections[0].start;
5635
+ let result = body.slice(0, preambleEnd).trimEnd();
5636
+ let sectionEnd;
5637
+ for (let i = 0; i < sections.length; i++) {
5638
+ const current = sections[i];
5639
+ sectionEnd = i + 1 < sections.length ? sections[i + 1].start : body.length;
5640
+ if (current.id === stack) {
4422
5641
  result += `
4423
5642
 
4424
- ${body.slice(cur.start, sectionEnd).trimEnd()}`;
5643
+ ${body.slice(current.start, sectionEnd).trimEnd()}`;
4425
5644
  }
4426
5645
  }
4427
5646
  return `${result}
@@ -4446,7 +5665,7 @@ var DefaultDesignKitLoader = class {
4446
5665
  for (const { dir, source } of this.dirs) {
4447
5666
  let entries;
4448
5667
  try {
4449
- entries = await fs2.readdir(dir, { withFileTypes: true });
5668
+ entries = await fs3.readdir(dir, { withFileTypes: true });
4450
5669
  } catch {
4451
5670
  continue;
4452
5671
  }
@@ -4454,7 +5673,7 @@ var DefaultDesignKitLoader = class {
4454
5673
  if (!e.isDirectory()) continue;
4455
5674
  const kitFile = path4.join(dir, e.name, KIT_FILE);
4456
5675
  try {
4457
- const raw = await fs2.readFile(kitFile, "utf8");
5676
+ const raw = await fs3.readFile(kitFile, "utf8");
4458
5677
  const fm = parseKitFrontmatter(raw);
4459
5678
  const id = fm.id ?? e.name;
4460
5679
  if (!fm.name) continue;
@@ -4515,7 +5734,7 @@ var DefaultDesignKitLoader = class {
4515
5734
  if (cached !== void 0) return cached;
4516
5735
  const m = await this.find(id);
4517
5736
  if (!m) throw new Error(`Design kit "${id}" not found`);
4518
- const raw = await fs2.readFile(m.path, "utf8");
5737
+ const raw = await fs3.readFile(m.path, "utf8");
4519
5738
  const body = narrowStackSections(stripFrontmatter(raw), stack);
4520
5739
  this.bodyCache.set(key, body);
4521
5740
  return body;
@@ -4528,7 +5747,7 @@ var DefaultDesignKitLoader = class {
4528
5747
  if (m) {
4529
5748
  const tokensPath = path4.join(path4.dirname(m.path), TOKENS_FILE);
4530
5749
  try {
4531
- const raw = await fs2.readFile(tokensPath, "utf8");
5750
+ const raw = await fs3.readFile(tokensPath, "utf8");
4532
5751
  const parsed = JSON.parse(raw);
4533
5752
  tokens = parsed;
4534
5753
  } catch {
@@ -4586,7 +5805,7 @@ function _resetDesignKitLoaderMemo() {
4586
5805
 
4587
5806
  // src/execution/design-project-store.ts
4588
5807
  import { existsSync as existsSync2 } from "node:fs";
4589
- import * as fs3 from "node:fs/promises";
5808
+ import * as fs4 from "node:fs/promises";
4590
5809
  import * as path5 from "node:path";
4591
5810
  var DESIGN_DIR = ".design";
4592
5811
  function designProjectDir(projectRoot) {
@@ -4599,7 +5818,7 @@ async function loadProjectDesignRules(projectRoot) {
4599
5818
  let rules;
4600
5819
  for (const name of RULE_FILES) {
4601
5820
  try {
4602
- const txt = await fs3.readFile(path5.join(designProjectDir(projectRoot), name), "utf8");
5821
+ const txt = await fs4.readFile(path5.join(designProjectDir(projectRoot), name), "utf8");
4603
5822
  if (txt.trim()) {
4604
5823
  rules = txt.trim();
4605
5824
  break;
@@ -4620,7 +5839,7 @@ function parseOverrides(value) {
4620
5839
  }
4621
5840
  async function loadActiveKit(projectRoot) {
4622
5841
  try {
4623
- const raw = await fs3.readFile(path5.join(designProjectDir(projectRoot), "active.json"), "utf8");
5842
+ const raw = await fs4.readFile(path5.join(designProjectDir(projectRoot), "active.json"), "utf8");
4624
5843
  const parsed = JSON.parse(raw);
4625
5844
  if (parsed && typeof parsed.kit === "string") {
4626
5845
  return {
@@ -4653,11 +5872,11 @@ function applyTokenOverrides(tokens, overrides) {
4653
5872
  }
4654
5873
  async function ensureDesignDir(projectRoot) {
4655
5874
  const dir = designProjectDir(projectRoot);
4656
- await fs3.mkdir(dir, { recursive: true });
5875
+ await fs4.mkdir(dir, { recursive: true });
4657
5876
  const gi = path5.join(dir, ".gitignore");
4658
5877
  if (!existsSync2(gi)) {
4659
5878
  try {
4660
- await fs3.writeFile(gi, "*\n");
5879
+ await fs4.writeFile(gi, "*\n");
4661
5880
  } catch {
4662
5881
  }
4663
5882
  }
@@ -4668,17 +5887,17 @@ async function recordKitChoice(projectRoot, kit, stack, source, isoTime, overrid
4668
5887
  const dir = await ensureDesignDir(projectRoot);
4669
5888
  const record = { kit, stack: stack ?? null };
4670
5889
  if (overrides && Object.keys(overrides).length > 0) record.overrides = overrides;
4671
- await fs3.writeFile(path5.join(dir, "active.json"), `${JSON.stringify(record, null, 2)}
5890
+ await fs4.writeFile(path5.join(dir, "active.json"), `${JSON.stringify(record, null, 2)}
4672
5891
  `);
4673
5892
  const line = `- ${isoTime} \xB7 kit=${kit}${stack ? ` stack=${stack}` : ""} \xB7 via=${source}
4674
5893
  `;
4675
- await fs3.appendFile(path5.join(dir, "decisions.md"), line);
5894
+ await fs4.appendFile(path5.join(dir, "decisions.md"), line);
4676
5895
  } catch {
4677
5896
  }
4678
5897
  }
4679
5898
  async function clearPersistedActiveKit(projectRoot) {
4680
5899
  try {
4681
- await fs3.rm(path5.join(designProjectDir(projectRoot), "active.json"), { force: true });
5900
+ await fs4.rm(path5.join(designProjectDir(projectRoot), "active.json"), { force: true });
4682
5901
  } catch {
4683
5902
  }
4684
5903
  }
@@ -4959,10 +6178,10 @@ function makeDesignVerifyToolCallMiddleware() {
4959
6178
  const p = typeof input?.path === "string" ? input.path : "";
4960
6179
  if (!p || !detectFrontendFile(p)) return out;
4961
6180
  const ctx = out.ctx;
4962
- const { default: fs8 } = await import("node:fs/promises");
6181
+ const { default: fs9 } = await import("node:fs/promises");
4963
6182
  const { default: nodePath } = await import("node:path");
4964
6183
  const abs = nodePath.isAbsolute(p) ? p : nodePath.join(ctx.projectRoot, p);
4965
- const text = await fs8.readFile(abs, "utf8").catch(() => "");
6184
+ const text = await fs9.readFile(abs, "utf8").catch(() => "");
4966
6185
  if (!text) return out;
4967
6186
  const loader = getDesignKitLoader(ctx.projectRoot);
4968
6187
  const rawTokens = await loader.readTokens(state.activeKit);
@@ -5082,11 +6301,25 @@ function buildRecoveryStrategies(opts) {
5082
6301
  },
5083
6302
  {
5084
6303
  label: "rate_limit_backoff",
5085
- async attempt(err) {
6304
+ async attempt(err, ctx) {
5086
6305
  if (!(err instanceof ProviderError) || err.kind !== "rate_limit") return null;
5087
6306
  const delayMs = err.body?.retryAfterMs ?? 5e3;
5088
6307
  const delay = Math.min(6e4, Math.max(1e3, delayMs));
5089
- await new Promise((r) => setTimeout(r, delay));
6308
+ if (ctx?.signal) {
6309
+ await new Promise((resolve4) => {
6310
+ if (ctx.signal.aborted) {
6311
+ resolve4();
6312
+ return;
6313
+ }
6314
+ const timer = setTimeout(resolve4, delay);
6315
+ ctx.signal.addEventListener("abort", () => {
6316
+ clearTimeout(timer);
6317
+ resolve4();
6318
+ }, { once: true });
6319
+ });
6320
+ } else {
6321
+ await new Promise((r) => setTimeout(r, delay));
6322
+ }
5090
6323
  return { action: "retry", reason: "rate_limit_backoff" };
5091
6324
  }
5092
6325
  },
@@ -5952,16 +7185,18 @@ var IntelligentCompactor = class {
5952
7185
  eliseThreshold;
5953
7186
  summarizerPrompt;
5954
7187
  summarizerModel;
7188
+ oneShotOrchestrator;
5955
7189
  constructor(opts) {
5956
7190
  this.provider = opts.provider;
5957
- this.warnThreshold = opts.warnThreshold ?? 0.6;
5958
- this.softThreshold = opts.softThreshold ?? 0.75;
5959
- this.hardThreshold = opts.hardThreshold ?? 0.9;
7191
+ this.warnThreshold = opts.warnThreshold ?? 0.5;
7192
+ this.softThreshold = opts.softThreshold ?? 0.65;
7193
+ this.hardThreshold = opts.hardThreshold ?? 0.8;
5960
7194
  this.maxContext = opts.maxContext ?? 128e3;
5961
7195
  this.preserveK = opts.preserveK ?? 4;
5962
- this.eliseThreshold = opts.eliseThreshold ?? 500;
7196
+ this.eliseThreshold = opts.eliseThreshold ?? 300;
5963
7197
  this.summarizerPrompt = opts.summarizerPrompt ?? readBundledInstructionText("llm/intelligent-compactor-summarizer.md");
5964
7198
  this.summarizerModel = opts.summarizerModel;
7199
+ this.oneShotOrchestrator = opts.oneShotOrchestrator;
5965
7200
  }
5966
7201
  async compact(ctx, opts = {}) {
5967
7202
  const beforeTokens = estimateMessages(ctx.messages);
@@ -5974,7 +7209,7 @@ var IntelligentCompactor = class {
5974
7209
  let collapsedDigest;
5975
7210
  if (aggressive) {
5976
7211
  const phase2 = await this.summarizeAncientTurns(ctx);
5977
- if (phase2.saved > 0) reductions.push({ phase: "summary", saved: phase2.saved });
7212
+ reductions.push({ phase: "summary", saved: Math.max(0, phase2.saved) });
5978
7213
  collapsedDigest = phase2.digest;
5979
7214
  } else if (load >= this.warnThreshold) {
5980
7215
  const saved2 = this.elide(ctx);
@@ -6028,9 +7263,15 @@ var IntelligentCompactor = class {
6028
7263
  } catch {
6029
7264
  summaryText = buildLosslessDigest(toSummarize) || `${toSummarize.length} earlier turns (semantic content preserved)`;
6030
7265
  }
7266
+ const smartDigest = buildSmartDigest(toSummarize);
7267
+ const enriched = smartDigest ? `[CRITICAL]
7268
+ ${smartDigest}
7269
+
7270
+ [SUMMARY]
7271
+ ${summaryText}` : summaryText;
6031
7272
  const summaryMsg = {
6032
7273
  role: "system",
6033
- content: `[prior_turns_summary: ${summaryText}]`
7274
+ content: `[prior_turns_summary: ${enriched}]`
6034
7275
  };
6035
7276
  const summaryTokens = estimateMessages([summaryMsg]);
6036
7277
  const tail = ctx.messages.slice(boundary);
@@ -6038,27 +7279,41 @@ var IntelligentCompactor = class {
6038
7279
  return { saved: Math.max(0, removedTokens - summaryTokens), digest: summaryText };
6039
7280
  }
6040
7281
  async callSummarizer(messages, ctx) {
6041
- const prompt = [
6042
- { type: "text", text: this.summarizerPrompt },
6043
- { type: "text", text: "\n\nConversation to summarize:\n" },
6044
- ...this.messagesToText(messages)
6045
- ];
6046
- const req = {
6047
- model: this.summarizerModel ?? ctx.model,
6048
- system: prompt,
6049
- messages: [],
6050
- maxTokens: 1024
6051
- };
6052
- const ac = ctx.signal ? void 0 : new AbortController();
6053
- const signal = ctx.signal ?? ac?.signal;
6054
- let res;
6055
- try {
6056
- res = await this.provider.complete(req, { signal });
6057
- } finally {
6058
- ac?.abort();
7282
+ if (this.oneShotOrchestrator) {
7283
+ const result = await this.oneShotOrchestrator.call({
7284
+ system: this.summarizerPrompt,
7285
+ messages: [{ role: "user", content: this.messagesToSummary(messages) }],
7286
+ model: this.summarizerModel ?? "deepseek-chat",
7287
+ timeoutMs: 3e4,
7288
+ maxTokens: 1024,
7289
+ signal: ctx.signal,
7290
+ fallbackProfile: "summary"
7291
+ });
7292
+ if (result.text && !result.error) return result.text;
7293
+ } else {
7294
+ const prompt = [
7295
+ { type: "text", text: this.summarizerPrompt },
7296
+ { type: "text", text: "\n\nConversation to summarize:\n" },
7297
+ ...this.messagesToText(messages)
7298
+ ];
7299
+ const req = {
7300
+ model: this.summarizerModel ?? ctx.model,
7301
+ system: prompt,
7302
+ messages: [],
7303
+ maxTokens: 1024
7304
+ };
7305
+ const ac = ctx.signal ? void 0 : new AbortController();
7306
+ const signal = ctx.signal ?? ac?.signal;
7307
+ let res;
7308
+ try {
7309
+ res = await this.provider.complete(req, { signal });
7310
+ } finally {
7311
+ ac?.abort();
7312
+ }
7313
+ const textBlocks = res.content.filter(isTextBlock);
7314
+ return textBlocks.map((b) => b.text).join("\n").trim() || "(empty summary)";
6059
7315
  }
6060
- const textBlocks = res.content.filter(isTextBlock);
6061
- return textBlocks.map((b) => b.text).join("\n").trim() || "(empty summary)";
7316
+ return "(empty summary)";
6062
7317
  }
6063
7318
  messagesToText(messages) {
6064
7319
  const lines = [];
@@ -6075,6 +7330,22 @@ var IntelligentCompactor = class {
6075
7330
  }
6076
7331
  return [{ type: "text", text: lines.join("\n") }];
6077
7332
  }
7333
+ /** Compact string summary of messages (no TextBlock wrapper). */
7334
+ messagesToSummary(messages) {
7335
+ const lines = [];
7336
+ for (const m of messages) {
7337
+ const role = m.role.padEnd(10, " ");
7338
+ if (typeof m.content === "string") {
7339
+ lines.push(`[${role}]: ${m.content.slice(0, 500)}`);
7340
+ } else if (Array.isArray(m.content)) {
7341
+ const textParts = m.content.filter(isTextBlock).map((b) => b.text);
7342
+ if (textParts.length > 0) {
7343
+ lines.push(`[${role}]: ${textParts.join(" ").slice(0, 500)}`);
7344
+ }
7345
+ }
7346
+ }
7347
+ return lines.join("\n");
7348
+ }
6078
7349
  };
6079
7350
 
6080
7351
  // src/execution/model-runtime.ts
@@ -6762,7 +8033,7 @@ function normalizeSubagentStructuredReport(value) {
6762
8033
  const nextSteps = stringArray(value["suggested_next_steps"]);
6763
8034
  const confidence = value["confidence"];
6764
8035
  const completion = value["completion"];
6765
- const remainingWork = optionalString(value["remaining_work"]);
8036
+ const remainingWork = optionalString2(value["remaining_work"]);
6766
8037
  if (!summary || summary.length > MAX_SUMMARY_CHARS || !findings || findings.length > MAX_FINDINGS || !filesExamined || filesExamined.length > MAX_FILES || !nextSteps || nextSteps.length > MAX_NEXT_STEPS || [...findings, ...filesExamined, ...nextSteps].some(
6767
8038
  (item) => item.trim().length > MAX_ITEM_CHARS
6768
8039
  ) || typeof confidence !== "number" || !Number.isFinite(confidence) || confidence < 0 || confidence > 1 || completion !== void 0 && completion !== "complete" && completion !== "partial" || completion === "partial" && !remainingWork || remainingWork !== void 0 && remainingWork.length > MAX_SUMMARY_CHARS) {
@@ -6788,7 +8059,7 @@ function isRecord4(value) {
6788
8059
  function nonEmptyString(value) {
6789
8060
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
6790
8061
  }
6791
- function optionalString(value) {
8062
+ function optionalString2(value) {
6792
8063
  return value === void 0 ? void 0 : nonEmptyString(value);
6793
8064
  }
6794
8065
  function stringArray(value) {
@@ -7073,7 +8344,7 @@ var TOOLS = {
7073
8344
  };
7074
8345
 
7075
8346
  // src/coordination/agents/agent-prompts.ts
7076
- import { readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
8347
+ import { readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
7077
8348
  import * as os2 from "node:os";
7078
8349
  import * as path6 from "node:path";
7079
8350
  import { fileURLToPath as fileURLToPath3 } from "node:url";
@@ -7088,7 +8359,7 @@ function agentPrompt(id) {
7088
8359
  let resolved = "";
7089
8360
  for (const dir of agentPromptDirCandidates(envDir)) {
7090
8361
  try {
7091
- resolved = readFileSync2(path6.join(dir, fileName), "utf8").trimEnd();
8362
+ resolved = readFileSync3(path6.join(dir, fileName), "utf8").trimEnd();
7092
8363
  break;
7093
8364
  } catch {
7094
8365
  }
@@ -7118,7 +8389,7 @@ function agentPromptDirCandidates(envDir) {
7118
8389
  }
7119
8390
  function isDirectory2(candidate) {
7120
8391
  try {
7121
- return statSync2(candidate).isDirectory();
8392
+ return statSync3(candidate).isDirectory();
7122
8393
  } catch {
7123
8394
  return false;
7124
8395
  }
@@ -8806,19 +10077,19 @@ var FLEET_ROSTER = {
8806
10077
  ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
8807
10078
  )
8808
10079
  };
8809
- var DEFAULT_IDLE_TIMEOUT_MS = 15 * 60 * 1e3;
10080
+ var DEFAULT_IDLE_TIMEOUT_MS = 10 * 60 * 1e3;
8810
10081
  var FLEET_ROSTER_BUDGETS = {
8811
- "audit-log": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 5e3, maxToolCalls: 15e3 },
8812
- "bug-hunter": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
8813
- "refactor-planner": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 6e3, maxToolCalls: 18e3 },
8814
- "security-scanner": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
8815
- critic: { timeoutMs: 5 * 60 * 60 * 1e3, maxIterations: 4e3, maxToolCalls: 12e3 },
10082
+ "audit-log": { timeoutMs: 3 * 60 * 60 * 1e3, maxIterations: 2500, maxToolCalls: 7500 },
10083
+ "bug-hunter": { timeoutMs: 5 * 60 * 60 * 1e3, maxIterations: 4e3, maxToolCalls: 1e4 },
10084
+ "refactor-planner": { timeoutMs: 4 * 60 * 60 * 1e3, maxIterations: 3e3, maxToolCalls: 9e3 },
10085
+ "security-scanner": { timeoutMs: 5 * 60 * 60 * 1e3, maxIterations: 4e3, maxToolCalls: 1e4 },
10086
+ critic: { timeoutMs: 3 * 60 * 60 * 1e3, maxIterations: 2e3, maxToolCalls: 6e3 },
8816
10087
  "shadow-agent": {
8817
10088
  idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
8818
- maxIterations: 2e3,
8819
- maxToolCalls: 5e3,
8820
- maxTokens: 6e4,
8821
- maxCostUsd: 1
10089
+ maxIterations: 1e3,
10090
+ maxToolCalls: 2500,
10091
+ maxTokens: 3e4,
10092
+ maxCostUsd: 0.5
8822
10093
  },
8823
10094
  ...Object.fromEntries(
8824
10095
  ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.budget])
@@ -8826,8 +10097,8 @@ var FLEET_ROSTER_BUDGETS = {
8826
10097
  };
8827
10098
  var GENERIC_SUBAGENT_BUDGET = {
8828
10099
  idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
8829
- maxIterations: 5e3,
8830
- maxToolCalls: 15e3
10100
+ maxIterations: 2500,
10101
+ maxToolCalls: 7500
8831
10102
  };
8832
10103
  function applyRosterBudget(cfg) {
8833
10104
  const roleBudget = cfg.role ? FLEET_ROSTER_BUDGETS[cfg.role] : void 0;
@@ -10368,6 +11639,7 @@ var LLMSelector = class {
10368
11639
  maxContextTokens;
10369
11640
  systemPrompt;
10370
11641
  maxOutputTokens;
11642
+ oneShotOrchestrator;
10371
11643
  constructor(opts) {
10372
11644
  this.provider = opts.provider;
10373
11645
  this.model = opts.model ?? "unknown";
@@ -10379,6 +11651,7 @@ var LLMSelector = class {
10379
11651
  this.maxContextTokens = opts.maxContextTokens ?? 4e4;
10380
11652
  this.systemPrompt = opts.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
10381
11653
  this.maxOutputTokens = opts.maxOutputTokens ?? 1024;
11654
+ this.oneShotOrchestrator = opts.oneShotOrchestrator;
10382
11655
  }
10383
11656
  async select(messages, maxToKeep) {
10384
11657
  const effectiveBudget = Math.min(maxToKeep, this.maxContextTokens);
@@ -10402,12 +11675,23 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
10402
11675
  let raw;
10403
11676
  const ac = new AbortController();
10404
11677
  try {
10405
- const timeoutSignal = AbortSignal.timeout(3e4);
10406
- const res = await this.provider.complete(req, {
10407
- signal: AbortSignal.any([ac.signal, timeoutSignal])
10408
- });
10409
- const textBlocks = res.content.filter(isTextBlock);
10410
- raw = textBlocks.map((b) => b.text).join("\n").trim();
11678
+ if (this.oneShotOrchestrator) {
11679
+ const result = await this.oneShotOrchestrator.call({
11680
+ system: systemText + budgetInstruction,
11681
+ userPrompt: historyText,
11682
+ maxTokens: this.maxOutputTokens,
11683
+ timeoutMs: 3e4
11684
+ });
11685
+ if (result.error) throw new Error(result.error);
11686
+ raw = result.text;
11687
+ } else {
11688
+ const timeoutSignal = AbortSignal.timeout(3e4);
11689
+ const res = await this.provider.complete(req, {
11690
+ signal: AbortSignal.any([ac.signal, timeoutSignal])
11691
+ });
11692
+ const textBlocks = res.content.filter(isTextBlock);
11693
+ raw = textBlocks.map((b) => b.text).join("\n").trim();
11694
+ }
10411
11695
  } catch (err) {
10412
11696
  if (err instanceof Error) {
10413
11697
  console.warn(
@@ -10527,12 +11811,12 @@ var SelectiveCompactor = class {
10527
11811
  constructor(opts) {
10528
11812
  this.provider = opts.provider;
10529
11813
  this.selector = opts.selector ?? new LLMSelector({ provider: opts.provider, model: opts.selectorModel, maxOutputTokens: opts.selectorMaxOutputTokens });
10530
- this.warnThreshold = opts.warnThreshold ?? 0.6;
10531
- this.softThreshold = opts.softThreshold ?? 0.75;
10532
- this.hardThreshold = opts.hardThreshold ?? 0.9;
11814
+ this.warnThreshold = opts.warnThreshold ?? 0.5;
11815
+ this.softThreshold = opts.softThreshold ?? 0.65;
11816
+ this.hardThreshold = opts.hardThreshold ?? 0.8;
10533
11817
  this.maxContext = opts.maxContext ?? 128e3;
10534
11818
  this.preserveK = opts.preserveK ?? 4;
10535
- this.eliseThreshold = opts.eliseThreshold ?? 500;
11819
+ this.eliseThreshold = opts.eliseThreshold ?? 300;
10536
11820
  this.summarizerModel = opts.summarizerModel ?? opts.selectorModel;
10537
11821
  if (this.summarizerModel === void 0 && (process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1")) {
10538
11822
  console.warn(
@@ -10725,7 +12009,7 @@ Summarize the following message range:`;
10725
12009
  };
10726
12010
 
10727
12011
  // src/execution/skill-loader.ts
10728
- import * as fs4 from "node:fs/promises";
12012
+ import * as fs5 from "node:fs/promises";
10729
12013
  import * as path7 from "node:path";
10730
12014
 
10731
12015
  // src/skills/foreign-sources.ts
@@ -10956,7 +12240,7 @@ async function entryIsDirectory(dir, entry) {
10956
12240
  if (entry.isDirectory()) return true;
10957
12241
  if (entry.isSymbolicLink()) {
10958
12242
  try {
10959
- return (await fs4.stat(path7.join(dir, entry.name))).isDirectory();
12243
+ return (await fs5.stat(path7.join(dir, entry.name))).isDirectory();
10960
12244
  } catch {
10961
12245
  return false;
10962
12246
  }
@@ -10999,12 +12283,12 @@ var DefaultSkillLoader = class {
10999
12283
  const seen = /* @__PURE__ */ new Set();
11000
12284
  for (const { dir, source, originTool } of this.dirs) {
11001
12285
  try {
11002
- const entries = await fs4.readdir(dir, { withFileTypes: true });
12286
+ const entries = await fs5.readdir(dir, { withFileTypes: true });
11003
12287
  for (const e of entries) {
11004
12288
  if (!await entryIsDirectory(dir, e)) continue;
11005
12289
  const skillFile = path7.join(dir, e.name, "SKILL.md");
11006
12290
  try {
11007
- const raw = await fs4.readFile(skillFile, "utf8");
12291
+ const raw = await fs5.readFile(skillFile, "utf8");
11008
12292
  const fm = parseSkillFrontmatter(raw);
11009
12293
  if (!fm.name || !fm.description) continue;
11010
12294
  if (!isValidSkillNameFormat(fm.name)) continue;
@@ -11076,7 +12360,7 @@ var DefaultSkillLoader = class {
11076
12360
  if (cached !== void 0) return cached;
11077
12361
  const m = await this.find(name);
11078
12362
  if (!m) throw new Error(`Skill "${name}" not found`);
11079
- const body = await fs4.readFile(m.path, "utf8");
12363
+ const body = await fs5.readFile(m.path, "utf8");
11080
12364
  this.bodyCache.set(key, body);
11081
12365
  return body;
11082
12366
  }
@@ -11089,9 +12373,9 @@ var DefaultSkillLoader = class {
11089
12373
  const savePath = path7.join(path7.dirname(m.path), "SKILL.save.md");
11090
12374
  let result;
11091
12375
  try {
11092
- result = await fs4.readFile(savePath, "utf8");
12376
+ result = await fs5.readFile(savePath, "utf8");
11093
12377
  } catch {
11094
- const full = await fs4.readFile(m.path, "utf8");
12378
+ const full = await fs5.readFile(m.path, "utf8");
11095
12379
  const body = stripFrontmatter2(full);
11096
12380
  const compact = compactSkillBody(body);
11097
12381
  if (compact) {
@@ -11119,11 +12403,11 @@ function parseDescriptionFromText(desc) {
11119
12403
  }
11120
12404
 
11121
12405
  // src/execution/prompt-loader.ts
11122
- import * as fs6 from "node:fs/promises";
12406
+ import * as fs7 from "node:fs/promises";
11123
12407
  import * as path9 from "node:path";
11124
12408
 
11125
12409
  // src/storage/prompt-store.ts
11126
- import * as fs5 from "node:fs/promises";
12410
+ import * as fs6 from "node:fs/promises";
11127
12411
  import * as path8 from "node:path";
11128
12412
  var SCHEMA_VERSION = 2;
11129
12413
  function migratePromptEntry(raw) {
@@ -11185,12 +12469,12 @@ var DefaultPromptStore = class {
11185
12469
  await ensureDir(this.dir);
11186
12470
  const entries = [];
11187
12471
  try {
11188
- const files = await fs5.readdir(this.dir);
12472
+ const files = await fs6.readdir(this.dir);
11189
12473
  for (const file of files) {
11190
12474
  if (!file.endsWith(".json")) continue;
11191
12475
  try {
11192
12476
  const raw = JSON.parse(
11193
- await fs5.readFile(path8.join(this.dir, file), "utf8")
12477
+ await fs6.readFile(path8.join(this.dir, file), "utf8")
11194
12478
  );
11195
12479
  const migrated = migratePromptEntry(raw.entry);
11196
12480
  if (migrated) entries.push(migrated);
@@ -11206,7 +12490,7 @@ var DefaultPromptStore = class {
11206
12490
  async get(id) {
11207
12491
  const file = path8.join(this.dir, `${id}.json`);
11208
12492
  try {
11209
- const raw = JSON.parse(await fs5.readFile(file, "utf8"));
12493
+ const raw = JSON.parse(await fs6.readFile(file, "utf8"));
11210
12494
  return migratePromptEntry(raw.entry);
11211
12495
  } catch {
11212
12496
  return null;
@@ -11221,7 +12505,7 @@ var DefaultPromptStore = class {
11221
12505
  async delete(id) {
11222
12506
  const file = path8.join(this.dir, `${id}.json`);
11223
12507
  try {
11224
- await fs5.unlink(file);
12508
+ await fs6.unlink(file);
11225
12509
  return true;
11226
12510
  } catch {
11227
12511
  return false;
@@ -11421,7 +12705,7 @@ var DefaultPromptLoader = class {
11421
12705
  const files = await walkJson(dir);
11422
12706
  for (const file of files) {
11423
12707
  try {
11424
- const parsed = JSON.parse(await fs6.readFile(file, "utf8"));
12708
+ const parsed = JSON.parse(await fs7.readFile(file, "utf8"));
11425
12709
  const migrated = migratePromptEntry(parsed);
11426
12710
  if (migrated) out.push({ ...migrated, source: "builtin" });
11427
12711
  } catch {
@@ -11435,7 +12719,7 @@ async function walkJson(dir) {
11435
12719
  const out = [];
11436
12720
  let entries;
11437
12721
  try {
11438
- entries = await fs6.readdir(dir, { withFileTypes: true });
12722
+ entries = await fs7.readdir(dir, { withFileTypes: true });
11439
12723
  } catch {
11440
12724
  return out;
11441
12725
  }
@@ -11612,7 +12896,8 @@ var ProviderBackedCompactor = class {
11612
12896
  }
11613
12897
  return new IntelligentCompactor({
11614
12898
  ...common,
11615
- summarizerModel: this.opts.summarizerModel
12899
+ summarizerModel: this.opts.summarizerModel,
12900
+ oneShotOrchestrator: this.opts.oneShotOrchestrator
11616
12901
  });
11617
12902
  }
11618
12903
  };
@@ -11627,9 +12912,12 @@ function readPolicy(ctx) {
11627
12912
  // src/execution/tool-executor.ts
11628
12913
  import { randomUUID as randomUUID3 } from "node:crypto";
11629
12914
  import { isDeepStrictEqual } from "node:util";
11630
- import * as fs7 from "node:fs/promises";
12915
+ import * as fs8 from "node:fs/promises";
11631
12916
  import * as path10 from "node:path";
11632
12917
 
12918
+ // src/types/tool-executor.ts
12919
+ var GOVERNED_TOOL_EXECUTOR_META_KEY = "toolExecutor.executeGoverned";
12920
+
11633
12921
  // src/types/tool-markers.ts
11634
12922
  var MALFORMED_ARG_MARKERS = ["__raw", "__raw_arguments", "_raw"];
11635
12923
 
@@ -11746,6 +13034,18 @@ var ToolExecutor = class _ToolExecutor {
11746
13034
  * Returns the execution results and the remaining output budget.
11747
13035
  */
11748
13036
  async executeBatch(toolUses, ctx, strategy) {
13037
+ return this.withGovernedExecutionBridge(
13038
+ ctx,
13039
+ () => this.executeBatchInternal(toolUses, ctx, strategy)
13040
+ );
13041
+ }
13042
+ /**
13043
+ * Internal batch implementation. Nested meta-tool calls enter here through
13044
+ * the governed bridge so they traverse the same validation, hooks,
13045
+ * permission/capability, timeout, scrubbing, and audit path without
13046
+ * replacing the bridge that is already active for the outer call.
13047
+ */
13048
+ async executeBatchInternal(toolUses, ctx, strategy) {
11749
13049
  let budget = this.opts.perIterationOutputCapBytes ?? 1e5;
11750
13050
  const runOne = async (use0) => {
11751
13051
  const start = Date.now();
@@ -11916,8 +13216,9 @@ ${errorDetails}`,
11916
13216
  );
11917
13217
  if (post.additionalContext) {
11918
13218
  if (post.contextAs === "separate") {
11919
- const block = { type: "text", text: post.additionalContext };
11920
- ctx.state.appendMessage({ role: "user", content: [block] });
13219
+ ctx.pendingPostToolContext = ctx.pendingPostToolContext ? `${ctx.pendingPostToolContext}
13220
+
13221
+ ${post.additionalContext}` : post.additionalContext;
11921
13222
  } else {
11922
13223
  const appended = `
11923
13224
 
@@ -12017,14 +13318,56 @@ ${post.additionalContext}`;
12017
13318
  };
12018
13319
  }
12019
13320
  /**
12020
- * Execute a single tool with timeout, permission check, and output capping.
12021
- * Emits `tool.started` via the injected EventBus (if any) right before
12022
- * invoking the tool closes the observability gap between "model decided
12023
- * to call a tool" and "tool.executed".
13321
+ * Install the executor-owned bridge used by meta-tools for nested calls.
13322
+ * The bridge deliberately returns the governed, scrubbed ToolResultBlock
13323
+ * content rather than the raw tool value. This prevents a meta-tool from
13324
+ * bypassing output controls while preserving structured success/failure.
13325
+ */
13326
+ async withGovernedExecutionBridge(ctx, run) {
13327
+ const previous = ctx.meta[GOVERNED_TOOL_EXECUTOR_META_KEY];
13328
+ if (typeof previous === "function") return run();
13329
+ const bridge = async (toolName, input) => {
13330
+ const nestedUse = {
13331
+ type: "tool_use",
13332
+ id: `nested-${randomUUID3()}`,
13333
+ name: toolName,
13334
+ input
13335
+ };
13336
+ const batch = await this.executeBatchInternal([nestedUse], ctx, "sequential");
13337
+ const output = batch.outputs[0];
13338
+ if (!output) {
13339
+ return { success: false, error: `tool "${toolName}" produced no result` };
13340
+ }
13341
+ if (output.result.type === "tool_confirm_pending") {
13342
+ return {
13343
+ success: false,
13344
+ error: `tool "${toolName}" requires separate confirmation; call it directly`
13345
+ };
13346
+ }
13347
+ if (output.result.is_error) {
13348
+ return { success: false, error: String(output.result.content) };
13349
+ }
13350
+ return { success: true, result: output.result.content };
13351
+ };
13352
+ ctx.meta[GOVERNED_TOOL_EXECUTOR_META_KEY] = bridge;
13353
+ try {
13354
+ return await run();
13355
+ } finally {
13356
+ if (previous === void 0) delete ctx.meta[GOVERNED_TOOL_EXECUTOR_META_KEY];
13357
+ else ctx.meta[GOVERNED_TOOL_EXECUTOR_META_KEY] = previous;
13358
+ }
13359
+ }
13360
+ /**
13361
+ * Execute a single tool with timeout and output capping after its permission
13362
+ * decision has already been resolved by executeBatch/the confirmation flow.
13363
+ * Also installs the governed bridge because confirmed tools are re-run
13364
+ * through this method after a pending confirmation.
12024
13365
  */
12025
13366
  async executeTool(tool, use, ctx, budget) {
12026
- const text = await this.produceToolOutput(tool, use, ctx, budget);
12027
- return this.settleToolOutput(tool, use, text, budget);
13367
+ return this.withGovernedExecutionBridge(ctx, async () => {
13368
+ const text = await this.produceToolOutput(tool, use, ctx, budget);
13369
+ return this.settleToolOutput(tool, use, text, budget);
13370
+ });
12028
13371
  }
12029
13372
  /**
12030
13373
  * Async "produce" phase: run the tool, serialize, scrub, and spill an
@@ -12379,11 +13722,11 @@ async function maybePersistLargeToolOutput(toolName, content, budget) {
12379
13722
  }
12380
13723
  try {
12381
13724
  const dir = path10.join(wstackGlobalRoot(), "tool-output");
12382
- await fs7.mkdir(dir, { recursive: true });
13725
+ await fs8.mkdir(dir, { recursive: true });
12383
13726
  const safeTool = toolName.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 40) || "tool";
12384
13727
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
12385
13728
  const filePath = path10.join(dir, `${stamp}-${safeTool}-${randomUUID3()}.log`);
12386
- await fs7.writeFile(filePath, content, "utf8");
13729
+ await fs8.writeFile(filePath, content, "utf8");
12387
13730
  const marker = `[full tool output: ${bytes} bytes at ${filePath}; read/grep that file selectively instead of re-running or requesting more output]`;
12388
13731
  const fixedBytes = Buffer.byteLength(marker + TOOL_OUTPUT_ARTIFACT_OMISSION, "utf8");
12389
13732
  const previewBytes = Math.min(
@@ -12431,11 +13774,233 @@ function sliceUtf8Suffix(text, maxBytes) {
12431
13774
  if (first >= 56320 && first <= 57343) start++;
12432
13775
  return text.slice(start);
12433
13776
  }
13777
+
13778
+ // src/execution/one-shot-llm.ts
13779
+ var DEFAULT_TIMEOUT_MS = 3e4;
13780
+ var DEFAULT_MAX_TOKENS = 1024;
13781
+ var OneShotOrchestrator = class {
13782
+ opts;
13783
+ constructor(opts) {
13784
+ this.opts = opts;
13785
+ }
13786
+ /**
13787
+ * Make a one-shot LLM call. Resolves provider+model, applies fallback
13788
+ * chain on transient errors, and returns a structured result.
13789
+ *
13790
+ * Never throws — all errors are captured in `OneShotLLMResult.error`.
13791
+ */
13792
+ async call(input) {
13793
+ const startedAt = performance.now();
13794
+ const config = this.opts.getConfig();
13795
+ const target = this.resolveTarget(input, config);
13796
+ if (!target) {
13797
+ return {
13798
+ text: "",
13799
+ model: input.model ?? config.model ?? "unknown",
13800
+ provider: input.providerId ?? config.provider ?? "unknown",
13801
+ tokens: { input: 0, output: 0, total: 0 },
13802
+ durationMs: Math.round(performance.now() - startedAt),
13803
+ fromFallback: false,
13804
+ error: "No provider or model could be resolved. Check your config."
13805
+ };
13806
+ }
13807
+ let provider;
13808
+ try {
13809
+ provider = await this.opts.buildProvider(target.providerId, target.model);
13810
+ } catch (err) {
13811
+ return {
13812
+ text: "",
13813
+ model: target.model,
13814
+ provider: target.providerId,
13815
+ tokens: { input: 0, output: 0, total: 0 },
13816
+ durationMs: Math.round(performance.now() - startedAt),
13817
+ fromFallback: false,
13818
+ error: `Cannot build provider "${target.providerId}": ${err instanceof Error ? err.message : String(err)}`
13819
+ };
13820
+ }
13821
+ const request = this.buildRequest(input, target.model);
13822
+ const signal = this.resolveSignal(input);
13823
+ const chain = this.resolveFallbackChain(input, config, target);
13824
+ let servingProviderId = provider.id;
13825
+ let servingModel = target.model;
13826
+ let fromFallback = false;
13827
+ const primaryAttempt = await this.tryCall(provider, request, signal);
13828
+ let result = primaryAttempt.response;
13829
+ let lastError = primaryAttempt.error;
13830
+ let fallbackEligible = primaryAttempt.fallbackEligible;
13831
+ if (!result && fallbackEligible && chain.length > 0) {
13832
+ for (const ref of chain) {
13833
+ const parsed = parseModelRef(ref);
13834
+ if (!parsed.model) continue;
13835
+ const fbProviderId = parsed.provider ?? config.provider;
13836
+ if (fbProviderId === provider.id && parsed.model === target.model) continue;
13837
+ servingProviderId = fbProviderId;
13838
+ servingModel = parsed.model;
13839
+ let fbProvider;
13840
+ try {
13841
+ fbProvider = await this.opts.buildProvider(fbProviderId, parsed.model);
13842
+ } catch (err) {
13843
+ lastError = err;
13844
+ continue;
13845
+ }
13846
+ servingProviderId = fbProvider.id;
13847
+ const attempt = await this.tryCall(
13848
+ fbProvider,
13849
+ this.buildRequest(input, parsed.model),
13850
+ signal
13851
+ );
13852
+ if (attempt.response) {
13853
+ result = attempt.response;
13854
+ fromFallback = true;
13855
+ break;
13856
+ }
13857
+ lastError = attempt.error;
13858
+ fallbackEligible = attempt.fallbackEligible;
13859
+ if (!fallbackEligible) break;
13860
+ }
13861
+ }
13862
+ const elapsed = Math.round(performance.now() - startedAt);
13863
+ if (result) {
13864
+ const textBlocks = result.content.filter(isTextBlock);
13865
+ const text = textBlocks.map((b) => b.text).join("\n").trim();
13866
+ return {
13867
+ text: text || "(empty response)",
13868
+ model: result.model ?? servingModel,
13869
+ provider: servingProviderId,
13870
+ tokens: {
13871
+ input: result.usage?.input ?? 0,
13872
+ output: result.usage?.output ?? 0,
13873
+ total: (result.usage?.input ?? 0) + (result.usage?.output ?? 0)
13874
+ },
13875
+ durationMs: elapsed,
13876
+ fromFallback,
13877
+ stopReason: result.stopReason
13878
+ };
13879
+ }
13880
+ return {
13881
+ text: "",
13882
+ model: servingModel,
13883
+ provider: servingProviderId,
13884
+ tokens: { input: 0, output: 0, total: 0 },
13885
+ durationMs: elapsed,
13886
+ fromFallback,
13887
+ error: lastError instanceof Error ? lastError.message : String(lastError ?? "Unknown error")
13888
+ };
13889
+ }
13890
+ // ── Private helpers ─────────────────────────────────────────────
13891
+ /**
13892
+ * Resolve the target provider + model from input + config.
13893
+ * Priority: role-based routing > explicit providerId+model > defaults.
13894
+ */
13895
+ resolveTarget(input, config) {
13896
+ if (input.role && this.opts.modelRouter) {
13897
+ const pick = this.opts.modelRouter.pickForTask(input.role, "");
13898
+ if (pick) {
13899
+ return { providerId: pick.provider, model: pick.model };
13900
+ }
13901
+ }
13902
+ if (input.providerId && input.model) {
13903
+ return { providerId: input.providerId, model: input.model };
13904
+ }
13905
+ if (input.model) {
13906
+ return { providerId: config.provider, model: input.model };
13907
+ }
13908
+ if (input.providerId) {
13909
+ return { providerId: input.providerId, model: config.model };
13910
+ }
13911
+ if (config.provider && config.model) {
13912
+ return { providerId: config.provider, model: config.model };
13913
+ }
13914
+ return void 0;
13915
+ }
13916
+ /**
13917
+ * Build a Provider Request from the input.
13918
+ */
13919
+ buildRequest(input, model) {
13920
+ const messages = [...input.messages ?? []];
13921
+ if (input.userPrompt) {
13922
+ messages.push({ role: "user", content: input.userPrompt });
13923
+ }
13924
+ const system = asTextBlocks(input.system);
13925
+ return {
13926
+ model,
13927
+ ...system.length > 0 ? { system } : {},
13928
+ messages,
13929
+ maxTokens: input.maxTokens ?? DEFAULT_MAX_TOKENS,
13930
+ ...input.temperature !== void 0 ? { temperature: input.temperature } : {},
13931
+ ...input.responseFormat ? { responseFormat: input.responseFormat } : {}
13932
+ };
13933
+ }
13934
+ /**
13935
+ * Resolve the abort signal. Provider calls always receive the per-call
13936
+ * timeout, composed with external cancellation when the caller supplies it.
13937
+ */
13938
+ resolveSignal(input) {
13939
+ const timeoutSignal = AbortSignal.timeout(input.timeoutMs ?? DEFAULT_TIMEOUT_MS);
13940
+ return input.signal ? AbortSignal.any([input.signal, timeoutSignal]) : timeoutSignal;
13941
+ }
13942
+ /**
13943
+ * Build the fallback model chain from input + config + current target.
13944
+ */
13945
+ resolveFallbackChain(input, config, target) {
13946
+ if (input.fallbackModels && input.fallbackModels.length > 0) {
13947
+ return input.fallbackModels;
13948
+ }
13949
+ if (input.fallbackProfile) {
13950
+ const profile = fallbackProfileChain(config, input.fallbackProfile);
13951
+ if (profile.length > 0) return profile;
13952
+ }
13953
+ if (config.fallbackAuto !== false) {
13954
+ return effectiveFallbackChain(config).filter((ref) => {
13955
+ const parsed = parseModelRef(ref);
13956
+ const fbProvider = parsed.provider ?? config.provider;
13957
+ return !(fbProvider === target.providerId && parsed.model === target.model);
13958
+ });
13959
+ }
13960
+ return [];
13961
+ }
13962
+ /** Attempt a provider call while preserving the actual failure for callers. */
13963
+ async tryCall(provider, request, signal) {
13964
+ try {
13965
+ return {
13966
+ response: await provider.complete(request, { signal }),
13967
+ fallbackEligible: false
13968
+ };
13969
+ } catch (err) {
13970
+ return {
13971
+ error: err,
13972
+ fallbackEligible: !signal.aborted && (!(err instanceof ProviderError) || isFallbackWorthy(err.kind))
13973
+ };
13974
+ }
13975
+ }
13976
+ };
13977
+ function asTextBlocks(system) {
13978
+ if (!system) return [];
13979
+ if (Array.isArray(system)) return system;
13980
+ return [{ type: "text", text: system }];
13981
+ }
12434
13982
  export {
12435
13983
  AutoCompactionMiddleware,
12436
13984
  AutonomousRunner,
12437
13985
  BRAIN_EVALUATION_CASE_VERSION,
13986
+ BUILTIN_COUNCIL_PERSONAS,
13987
+ BUILTIN_COUNCIL_PROFILES,
13988
+ COUNCIL_JUDGE_PROMPT_PATH,
13989
+ COUNCIL_REFUSAL_OPTION_ID,
12438
13990
  COUNCIL_REFUSE_OPTION_ID,
13991
+ COUNCIL_VOTER_PROMPT_PATH,
13992
+ CouncilOrchestrator,
13993
+ CouncilPersonaRegistry,
13994
+ CouncilProfileRegistry,
13995
+ DEFAULT_COUNCIL_APPROVAL_FRACTION,
13996
+ DEFAULT_COUNCIL_JUDGE_MAX_TOKENS,
13997
+ DEFAULT_COUNCIL_MAX_CONCURRENCY,
13998
+ DEFAULT_COUNCIL_OVERALL_TIMEOUT_MS,
13999
+ DEFAULT_COUNCIL_PERSONA_REGISTRY,
14000
+ DEFAULT_COUNCIL_PER_CALL_TIMEOUT_MS,
14001
+ DEFAULT_COUNCIL_PROFILE_REGISTRY,
14002
+ DEFAULT_COUNCIL_QUORUM_FRACTION,
14003
+ DEFAULT_COUNCIL_VOTER_MAX_TOKENS,
12439
14004
  DEFAULT_RECOVERY_STRATEGIES,
12440
14005
  DefaultDesignKitLoader,
12441
14006
  DefaultErrorHandler,
@@ -12446,6 +14011,8 @@ export {
12446
14011
  EternalAutonomyEngine,
12447
14012
  HybridCompactor,
12448
14013
  IntelligentCompactor,
14014
+ MAX_COUNCIL_CONCURRENCY,
14015
+ OneShotOrchestrator,
12449
14016
  ParallelEternalEngine,
12450
14017
  SelectiveCompactor,
12451
14018
  ToolExecutor,
@@ -12455,6 +14022,11 @@ export {
12455
14022
  applyModelRuntime,
12456
14023
  assembleBrainTiers,
12457
14024
  buildBrainUserMessage,
14025
+ buildCouncilJudgeSystemPrompt,
14026
+ buildCouncilJudgeUserPrompt,
14027
+ buildCouncilQuestionPrompt,
14028
+ buildCouncilVoterSystemPrompt,
14029
+ buildCouncilVoterUserPrompt,
12458
14030
  buildGoalPreamble,
12459
14031
  buildRecoveryStrategies,
12460
14032
  clearActiveKit,
@@ -12463,6 +14035,8 @@ export {
12463
14035
  createAutonomyBrain,
12464
14036
  createBrainRuntime,
12465
14037
  createCouncilBrainArbiter,
14038
+ createCouncilPersonaRegistry,
14039
+ createCouncilProfileRegistry,
12466
14040
  createStrategyCompactor,
12467
14041
  createTieredBrainArbiter,
12468
14042
  designProjectDir,
@@ -12479,12 +14053,15 @@ export {
12479
14053
  makeDesignDetectUserInputMiddleware,
12480
14054
  makeDesignStudioRequestMiddleware,
12481
14055
  mergeModelRuntime,
14056
+ normalizeCouncilProfile,
12482
14057
  parseOptionDecision,
12483
14058
  recordKitChoice,
12484
14059
  renderPrompt,
12485
14060
  resolveBrainConfigDefaults,
12486
14061
  resolveBundledDesignKitsDir,
12487
14062
  resolveCacheForRequest,
14063
+ resolveCouncilProfile,
14064
+ resolveCouncilVotes,
12488
14065
  resolveModelRuntime,
12489
14066
  resolveReasoningForRequest,
12490
14067
  runBrainEvaluation,