@wrongstack/core 0.285.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 (340) hide show
  1. package/dist/autophase/phase-orchestrator.d.ts.map +1 -1
  2. package/dist/boot.d.ts.map +1 -1
  3. package/dist/coordination/agent-monitor.d.ts +29 -0
  4. package/dist/coordination/agent-monitor.d.ts.map +1 -1
  5. package/dist/coordination/autonomous-brain.d.ts +0 -2
  6. package/dist/coordination/autonomous-brain.d.ts.map +1 -1
  7. package/dist/coordination/autonomous-coordinator.d.ts +4 -0
  8. package/dist/coordination/autonomous-coordinator.d.ts.map +1 -1
  9. package/dist/coordination/brain-ledger.d.ts +142 -0
  10. package/dist/coordination/brain-ledger.d.ts.map +1 -0
  11. package/dist/coordination/brain-monitor.d.ts +46 -2
  12. package/dist/coordination/brain-monitor.d.ts.map +1 -1
  13. package/dist/coordination/brain.d.ts +43 -0
  14. package/dist/coordination/brain.d.ts.map +1 -1
  15. package/dist/coordination/delegate-tool.d.ts +8 -14
  16. package/dist/coordination/delegate-tool.d.ts.map +1 -1
  17. package/dist/coordination/director/director-btw-notes.d.ts +20 -0
  18. package/dist/coordination/director/director-btw-notes.d.ts.map +1 -0
  19. package/dist/coordination/director/director-collab.d.ts +40 -0
  20. package/dist/coordination/director/director-collab.d.ts.map +1 -0
  21. package/dist/coordination/director.d.ts +28 -9
  22. package/dist/coordination/director.d.ts.map +1 -1
  23. package/dist/coordination/fleet-manager.d.ts +21 -0
  24. package/dist/coordination/fleet-manager.d.ts.map +1 -1
  25. package/dist/coordination/fleet.d.ts +6 -7
  26. package/dist/coordination/fleet.d.ts.map +1 -1
  27. package/dist/coordination/global-mailbox.d.ts +96 -21
  28. package/dist/coordination/global-mailbox.d.ts.map +1 -1
  29. package/dist/coordination/icoordinator.d.ts +1 -3
  30. package/dist/coordination/icoordinator.d.ts.map +1 -1
  31. package/dist/coordination/index.d.ts +2 -1
  32. package/dist/coordination/index.d.ts.map +1 -1
  33. package/dist/coordination/index.js +2176 -806
  34. package/dist/coordination/index.js.map +4 -4
  35. package/dist/coordination/knowledge-graph.d.ts +13 -1
  36. package/dist/coordination/knowledge-graph.d.ts.map +1 -1
  37. package/dist/coordination/mail-tools.d.ts.map +1 -1
  38. package/dist/coordination/mailbox-actions.d.ts +12 -13
  39. package/dist/coordination/mailbox-actions.d.ts.map +1 -1
  40. package/dist/coordination/mailbox-constants.d.ts +64 -0
  41. package/dist/coordination/mailbox-constants.d.ts.map +1 -0
  42. package/dist/coordination/mailbox-events.d.ts +32 -0
  43. package/dist/coordination/mailbox-events.d.ts.map +1 -0
  44. package/dist/coordination/mailbox-message-codec.d.ts +28 -0
  45. package/dist/coordination/mailbox-message-codec.d.ts.map +1 -0
  46. package/dist/coordination/mailbox-types.d.ts +130 -5
  47. package/dist/coordination/mailbox-types.d.ts.map +1 -1
  48. package/dist/coordination/mailbox.d.ts +21 -2
  49. package/dist/coordination/mailbox.d.ts.map +1 -1
  50. package/dist/coordination/package-author-tracker.d.ts.map +1 -1
  51. package/dist/coordination/subagent-result-tool.d.ts.map +1 -1
  52. package/dist/core/agent-loop.d.ts.map +1 -1
  53. package/dist/core/agent-response.d.ts +10 -1
  54. package/dist/core/agent-response.d.ts.map +1 -1
  55. package/dist/core/agent-tools.d.ts.map +1 -1
  56. package/dist/core/agent.d.ts +8 -0
  57. package/dist/core/agent.d.ts.map +1 -1
  58. package/dist/core/context.d.ts +25 -0
  59. package/dist/core/context.d.ts.map +1 -1
  60. package/dist/core/conversation-state.d.ts +6 -0
  61. package/dist/core/conversation-state.d.ts.map +1 -1
  62. package/dist/core/fallback-model.d.ts.map +1 -1
  63. package/dist/core/mailbox-loop.d.ts +12 -5
  64. package/dist/core/mailbox-loop.d.ts.map +1 -1
  65. package/dist/core/system-prompt-builder.d.ts +14 -6
  66. package/dist/core/system-prompt-builder.d.ts.map +1 -1
  67. package/dist/defaults/index.d.ts +0 -1
  68. package/dist/defaults/index.d.ts.map +1 -1
  69. package/dist/defaults/index.js +1690 -1998
  70. package/dist/defaults/index.js.map +4 -4
  71. package/dist/execution/auto-compaction-middleware.d.ts +8 -1
  72. package/dist/execution/auto-compaction-middleware.d.ts.map +1 -1
  73. package/dist/execution/autonomous-runner.d.ts +7 -1
  74. package/dist/execution/autonomous-runner.d.ts.map +1 -1
  75. package/dist/execution/autonomy-brain.d.ts +72 -11
  76. package/dist/execution/autonomy-brain.d.ts.map +1 -1
  77. package/dist/execution/brain-chain.d.ts +53 -0
  78. package/dist/execution/brain-chain.d.ts.map +1 -0
  79. package/dist/execution/brain-evaluation.d.ts +66 -0
  80. package/dist/execution/brain-evaluation.d.ts.map +1 -0
  81. package/dist/execution/brain-runtime.d.ts +171 -0
  82. package/dist/execution/brain-runtime.d.ts.map +1 -0
  83. package/dist/execution/compaction-core.d.ts.map +1 -1
  84. package/dist/execution/council-brain.d.ts +79 -0
  85. package/dist/execution/council-brain.d.ts.map +1 -0
  86. package/dist/execution/council-orchestrator.d.ts +32 -0
  87. package/dist/execution/council-orchestrator.d.ts.map +1 -0
  88. package/dist/execution/council-personas.d.ts +19 -0
  89. package/dist/execution/council-personas.d.ts.map +1 -0
  90. package/dist/execution/council-profiles.d.ts +34 -0
  91. package/dist/execution/council-profiles.d.ts.map +1 -0
  92. package/dist/execution/council-prompts.d.ts +21 -0
  93. package/dist/execution/council-prompts.d.ts.map +1 -0
  94. package/dist/execution/council-resolution.d.ts +57 -0
  95. package/dist/execution/council-resolution.d.ts.map +1 -0
  96. package/dist/execution/design-kit-loader.d.ts.map +1 -1
  97. package/dist/execution/enhance-recovery.d.ts +35 -0
  98. package/dist/execution/enhance-recovery.d.ts.map +1 -0
  99. package/dist/execution/error-handler.d.ts.map +1 -1
  100. package/dist/execution/eternal-autonomy.d.ts +12 -0
  101. package/dist/execution/eternal-autonomy.d.ts.map +1 -1
  102. package/dist/execution/index.d.ts +11 -1
  103. package/dist/execution/index.d.ts.map +1 -1
  104. package/dist/execution/index.js +3076 -526
  105. package/dist/execution/index.js.map +4 -4
  106. package/dist/execution/intelligent-compactor.d.ts +18 -7
  107. package/dist/execution/intelligent-compactor.d.ts.map +1 -1
  108. package/dist/execution/one-shot-llm.d.ts +52 -0
  109. package/dist/execution/one-shot-llm.d.ts.map +1 -0
  110. package/dist/execution/parallel-eternal-engine.d.ts +12 -0
  111. package/dist/execution/parallel-eternal-engine.d.ts.map +1 -1
  112. package/dist/execution/prompt-enhancer.d.ts +23 -4
  113. package/dist/execution/prompt-enhancer.d.ts.map +1 -1
  114. package/dist/execution/prompt-enhancer.js +24 -6
  115. package/dist/execution/prompt-enhancer.js.map +2 -2
  116. package/dist/execution/selective-compactor.d.ts +4 -4
  117. package/dist/execution/strategy-compactor.d.ts +7 -0
  118. package/dist/execution/strategy-compactor.d.ts.map +1 -1
  119. package/dist/execution/tool-executor.d.ts +19 -5
  120. package/dist/execution/tool-executor.d.ts.map +1 -1
  121. package/dist/hooks/registry.d.ts.map +1 -1
  122. package/dist/hooks/runner.d.ts.map +1 -1
  123. package/dist/hooks/shell-executor.d.ts.map +1 -1
  124. package/dist/hq/alerts.d.ts +5 -0
  125. package/dist/hq/alerts.d.ts.map +1 -1
  126. package/dist/hq/auth-store.d.ts +38 -10
  127. package/dist/hq/auth-store.d.ts.map +1 -1
  128. package/dist/hq/commands.d.ts +8 -4
  129. package/dist/hq/commands.d.ts.map +1 -1
  130. package/dist/hq/cost-bridge.d.ts +5 -3
  131. package/dist/hq/cost-bridge.d.ts.map +1 -1
  132. package/dist/hq/factory.d.ts +3 -0
  133. package/dist/hq/factory.d.ts.map +1 -1
  134. package/dist/hq/fleet-bridge.d.ts.map +1 -1
  135. package/dist/hq/index.d.ts +0 -1
  136. package/dist/hq/index.d.ts.map +1 -1
  137. package/dist/hq/index.js +1016 -283
  138. package/dist/hq/index.js.map +4 -4
  139. package/dist/hq/persistence.d.ts +42 -0
  140. package/dist/hq/persistence.d.ts.map +1 -1
  141. package/dist/hq/protocol/brain.d.ts +24 -0
  142. package/dist/hq/protocol/brain.d.ts.map +1 -0
  143. package/dist/hq/protocol/browser.d.ts +12 -0
  144. package/dist/hq/protocol/browser.d.ts.map +1 -0
  145. package/dist/hq/protocol/client.d.ts +69 -0
  146. package/dist/hq/protocol/client.d.ts.map +1 -0
  147. package/dist/hq/protocol/core.d.ts +130 -0
  148. package/dist/hq/protocol/core.d.ts.map +1 -0
  149. package/dist/hq/protocol/fleet.d.ts +73 -0
  150. package/dist/hq/protocol/fleet.d.ts.map +1 -0
  151. package/dist/hq/protocol/mailbox.d.ts +76 -0
  152. package/dist/hq/protocol/mailbox.d.ts.map +1 -0
  153. package/dist/hq/protocol/mcp.d.ts +51 -0
  154. package/dist/hq/protocol/mcp.d.ts.map +1 -0
  155. package/dist/hq/protocol/project.d.ts +26 -0
  156. package/dist/hq/protocol/project.d.ts.map +1 -0
  157. package/dist/hq/protocol/session.d.ts +153 -0
  158. package/dist/hq/protocol/session.d.ts.map +1 -0
  159. package/dist/hq/protocol/tool.d.ts +31 -0
  160. package/dist/hq/protocol/tool.d.ts.map +1 -0
  161. package/dist/hq/protocol.d.ts +10 -559
  162. package/dist/hq/protocol.d.ts.map +1 -1
  163. package/dist/hq/publisher.d.ts +4 -0
  164. package/dist/hq/publisher.d.ts.map +1 -1
  165. package/dist/index.d.ts +24 -8
  166. package/dist/index.d.ts.map +1 -1
  167. package/dist/index.js +7676 -3432
  168. package/dist/index.js.map +4 -4
  169. package/dist/infrastructure/index.js +13 -11
  170. package/dist/infrastructure/index.js.map +2 -2
  171. package/dist/infrastructure/token-counter.d.ts.map +1 -1
  172. package/dist/kernel/events/agent-events.d.ts +283 -0
  173. package/dist/kernel/events/agent-events.d.ts.map +1 -0
  174. package/dist/kernel/events/brain-events.d.ts +176 -0
  175. package/dist/kernel/events/brain-events.d.ts.map +1 -0
  176. package/dist/kernel/events/fleet-events.d.ts +39 -0
  177. package/dist/kernel/events/fleet-events.d.ts.map +1 -0
  178. package/dist/kernel/events/memory-events.d.ts +165 -0
  179. package/dist/kernel/events/memory-events.d.ts.map +1 -0
  180. package/dist/kernel/events/provider-events.d.ts +98 -0
  181. package/dist/kernel/events/provider-events.d.ts.map +1 -0
  182. package/dist/kernel/events/sdd-events.d.ts +118 -0
  183. package/dist/kernel/events/sdd-events.d.ts.map +1 -0
  184. package/dist/kernel/events/session-events.d.ts +227 -0
  185. package/dist/kernel/events/session-events.d.ts.map +1 -0
  186. package/dist/kernel/events/tool-events.d.ts +185 -0
  187. package/dist/kernel/events/tool-events.d.ts.map +1 -0
  188. package/dist/kernel/events/worktree-events.d.ts +58 -0
  189. package/dist/kernel/events/worktree-events.d.ts.map +1 -0
  190. package/dist/kernel/events.d.ts +12 -1282
  191. package/dist/kernel/events.d.ts.map +1 -1
  192. package/dist/kernel/index.js +22 -2
  193. package/dist/kernel/index.js.map +2 -2
  194. package/dist/kernel/run-controller.d.ts +15 -0
  195. package/dist/kernel/run-controller.d.ts.map +1 -1
  196. package/dist/mailbox-attach.d.ts.map +1 -1
  197. package/dist/models/index.js +19 -6
  198. package/dist/models/index.js.map +2 -2
  199. package/dist/models/llm-selector.d.ts +7 -0
  200. package/dist/models/llm-selector.d.ts.map +1 -1
  201. package/dist/observability/index.js +16 -15
  202. package/dist/observability/index.js.map +2 -2
  203. package/dist/plugins/auto-review-plugin.d.ts +36 -0
  204. package/dist/plugins/auto-review-plugin.d.ts.map +1 -0
  205. package/dist/plugins/chimera-plugin.d.ts +8 -0
  206. package/dist/plugins/chimera-plugin.d.ts.map +1 -1
  207. package/dist/security/index.d.ts +1 -0
  208. package/dist/security/index.d.ts.map +1 -1
  209. package/dist/security/index.js +260 -23
  210. package/dist/security/index.js.map +4 -4
  211. package/dist/security/permission-policy-schema.d.ts +77 -0
  212. package/dist/security/permission-policy-schema.d.ts.map +1 -0
  213. package/dist/security/permission-policy.d.ts +5 -0
  214. package/dist/security/permission-policy.d.ts.map +1 -1
  215. package/dist/security/secret-scrubber.d.ts.map +1 -1
  216. package/dist/security/secret-vault.d.ts +9 -0
  217. package/dist/security/secret-vault.d.ts.map +1 -1
  218. package/dist/session-registry.d.ts.map +1 -1
  219. package/dist/skills/index.js.map +2 -2
  220. package/dist/storage/config-loader.d.ts +10 -0
  221. package/dist/storage/config-loader.d.ts.map +1 -1
  222. package/dist/storage/goal-kanban.d.ts +69 -0
  223. package/dist/storage/goal-kanban.d.ts.map +1 -0
  224. package/dist/storage/goal-store.d.ts +1 -1
  225. package/dist/storage/goal-store.d.ts.map +1 -1
  226. package/dist/storage/index.d.ts +1 -1
  227. package/dist/storage/index.d.ts.map +1 -1
  228. package/dist/storage/index.js +572 -879
  229. package/dist/storage/index.js.map +4 -4
  230. package/dist/storage/memory-consolidator.d.ts +9 -1
  231. package/dist/storage/memory-consolidator.d.ts.map +1 -1
  232. package/dist/storage/plan-store.d.ts +1 -1
  233. package/dist/storage/plan-store.d.ts.map +1 -1
  234. package/dist/storage/queue-store.d.ts +4 -0
  235. package/dist/storage/queue-store.d.ts.map +1 -1
  236. package/dist/storage/session-store.d.ts +9 -0
  237. package/dist/storage/session-store.d.ts.map +1 -1
  238. package/dist/storage/task-store.d.ts +1 -1
  239. package/dist/storage/task-store.d.ts.map +1 -1
  240. package/dist/storage/todos-checkpoint.d.ts +2 -2
  241. package/dist/storage/todos-checkpoint.d.ts.map +1 -1
  242. package/dist/tools/council-tool.d.ts +25 -0
  243. package/dist/tools/council-tool.d.ts.map +1 -0
  244. package/dist/tools/index.d.ts +2 -0
  245. package/dist/tools/index.d.ts.map +1 -1
  246. package/dist/tools/index.js +1884 -12
  247. package/dist/tools/index.js.map +4 -4
  248. package/dist/tools/one-shot-llm-tool.d.ts +51 -0
  249. package/dist/tools/one-shot-llm-tool.d.ts.map +1 -0
  250. package/dist/types/config.d.ts +236 -4
  251. package/dist/types/config.d.ts.map +1 -1
  252. package/dist/types/context-window.d.ts.map +1 -1
  253. package/dist/types/council.d.ts +134 -0
  254. package/dist/types/council.d.ts.map +1 -0
  255. package/dist/types/default-config.d.ts +3 -3
  256. package/dist/types/hooks.d.ts +14 -0
  257. package/dist/types/hooks.d.ts.map +1 -1
  258. package/dist/types/index.d.ts +2 -0
  259. package/dist/types/index.d.ts.map +1 -1
  260. package/dist/types/index.js +40 -8
  261. package/dist/types/index.js.map +3 -3
  262. package/dist/types/multi-agent.d.ts +8 -0
  263. package/dist/types/multi-agent.d.ts.map +1 -1
  264. package/dist/types/observability.d.ts +5 -0
  265. package/dist/types/observability.d.ts.map +1 -1
  266. package/dist/types/one-shot-llm.d.ts +128 -0
  267. package/dist/types/one-shot-llm.d.ts.map +1 -0
  268. package/dist/types/plugin.d.ts +6 -0
  269. package/dist/types/plugin.d.ts.map +1 -1
  270. package/dist/types/provider.d.ts +17 -0
  271. package/dist/types/provider.d.ts.map +1 -1
  272. package/dist/types/system-prompt.d.ts +2 -1
  273. package/dist/types/system-prompt.d.ts.map +1 -1
  274. package/dist/types/tool-executor.d.ts +10 -0
  275. package/dist/types/tool-executor.d.ts.map +1 -1
  276. package/dist/types/tool.d.ts +7 -0
  277. package/dist/types/tool.d.ts.map +1 -1
  278. package/dist/utils/atomic-write.d.ts.map +1 -1
  279. package/dist/utils/glob-expand.d.ts.map +1 -1
  280. package/dist/utils/incoming-images.d.ts +39 -0
  281. package/dist/utils/incoming-images.d.ts.map +1 -0
  282. package/dist/utils/index.d.ts +1 -0
  283. package/dist/utils/index.d.ts.map +1 -1
  284. package/dist/utils/index.js +156 -11
  285. package/dist/utils/index.js.map +4 -4
  286. package/dist/utils/tool-wire-compact.d.ts +1 -0
  287. package/dist/utils/tool-wire-compact.d.ts.map +1 -1
  288. package/dist/utils/wstack-paths.d.ts +20 -4
  289. package/dist/utils/wstack-paths.d.ts.map +1 -1
  290. package/dist/worktree/worktree-manager.d.ts +13 -4
  291. package/dist/worktree/worktree-manager.d.ts.map +1 -1
  292. package/instructions/agents/browser.md +25 -16
  293. package/instructions/cli/next-task-predictor.md +5 -3
  294. package/instructions/coordination/subagent-baseline.md +17 -2
  295. package/instructions/leader-after-task.md +19 -8
  296. package/instructions/llm/council-judge.md +19 -0
  297. package/instructions/llm/council-voter.md +22 -0
  298. package/instructions/modes/architect.md +14 -9
  299. package/instructions/modes/audit-lite.md +9 -7
  300. package/instructions/modes/brief-system.md +12 -12
  301. package/instructions/modes/brief.md +7 -5
  302. package/instructions/modes/code-auditor.md +13 -8
  303. package/instructions/modes/code-reviewer.md +13 -8
  304. package/instructions/modes/debug-lite.md +7 -6
  305. package/instructions/modes/debugger.md +13 -9
  306. package/instructions/modes/devops.md +13 -9
  307. package/instructions/modes/plan-lite.md +7 -6
  308. package/instructions/modes/refactor-lite.md +7 -6
  309. package/instructions/modes/refactorer.md +14 -9
  310. package/instructions/modes/research-lite.md +8 -6
  311. package/instructions/modes/research-web.md +18 -55
  312. package/instructions/modes/review-lite.md +7 -6
  313. package/instructions/modes/teach-system.md +15 -15
  314. package/instructions/modes/teach.md +13 -12
  315. package/instructions/modes/test-lite.md +6 -5
  316. package/instructions/modes/tester.md +13 -9
  317. package/instructions/modes/ui-design.md +14 -8
  318. package/instructions/sections/tool/commit-hygiene.md +1 -7
  319. package/instructions/sections/tool/common-patterns.md +7 -1
  320. package/instructions/sections/tool/delegation-full.md +3 -31
  321. package/instructions/sections/tool/mailbox-compact.md +1 -1
  322. package/instructions/sections/tool/mailbox-full.md +6 -22
  323. package/instructions/system.md +294 -8
  324. package/package.json +2 -2
  325. package/skills/docker-deploy/SKILL.md +1 -1
  326. package/skills/mailbox-bridge/SKILL.md +16 -7
  327. package/skills/output-standards/SKILL.md +16 -16
  328. package/skills/wrongstack-mailbox/SKILL.md +57 -12
  329. package/dist/coordination/director-construction.d.ts +0 -90
  330. package/dist/coordination/director-construction.d.ts.map +0 -1
  331. package/dist/hq/agent-bridge.d.ts +0 -27
  332. package/dist/hq/agent-bridge.d.ts.map +0 -1
  333. package/dist/plugins/git-plugin.d.ts +0 -15
  334. package/dist/plugins/git-plugin.d.ts.map +0 -1
  335. package/dist/plugins/observability-plugin.d.ts +0 -19
  336. package/dist/plugins/observability-plugin.d.ts.map +0 -1
  337. package/dist/plugins/plan-plugin.d.ts +0 -16
  338. package/dist/plugins/plan-plugin.d.ts.map +0 -1
  339. package/dist/storage/memory-store.d.ts +0 -119
  340. 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) {
@@ -2069,19 +2093,27 @@ function adaptThresholdsForSignals(thresholds, signals) {
2069
2093
 
2070
2094
  // src/execution/autonomous-runner.ts
2071
2095
  var DoneConditionChecker = class {
2072
- constructor(condition) {
2096
+ constructor(condition, opts) {
2073
2097
  this.condition = condition;
2098
+ this.logger = opts?.logger;
2074
2099
  if (condition.type === "output_match" && condition.pattern) {
2075
2100
  const result = compileUserRegex(condition.pattern, "");
2076
2101
  this.compiledRegex = result.ok ? result.regex : null;
2077
2102
  if (!result.ok) {
2078
- console.warn(JSON.stringify({
2079
- level: "warn",
2080
- event: "autonomous.done_condition_invalid_regex",
2081
- pattern: condition.pattern,
2082
- reason: result.reason,
2083
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2084
- }));
2103
+ if (this.logger) {
2104
+ this.logger.warn(
2105
+ "Done condition has invalid regex \u2014 will never match",
2106
+ { event: "autonomous.done_condition_invalid_regex", pattern: condition.pattern, reason: result.reason }
2107
+ );
2108
+ } else {
2109
+ console.warn(JSON.stringify({
2110
+ level: "warn",
2111
+ event: "autonomous.done_condition_invalid_regex",
2112
+ pattern: condition.pattern,
2113
+ reason: result.reason,
2114
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2115
+ }));
2116
+ }
2085
2117
  }
2086
2118
  } else {
2087
2119
  this.compiledRegex = null;
@@ -2089,6 +2121,7 @@ var DoneConditionChecker = class {
2089
2121
  }
2090
2122
  condition;
2091
2123
  compiledRegex;
2124
+ logger;
2092
2125
  check(state) {
2093
2126
  switch (this.condition.type) {
2094
2127
  case "iterations":
@@ -2139,7 +2172,7 @@ var DoneConditionChecker = class {
2139
2172
  var AutonomousRunner = class {
2140
2173
  constructor(opts) {
2141
2174
  this.opts = opts;
2142
- this.doneChecker = new DoneConditionChecker(opts.doneCondition);
2175
+ this.doneChecker = new DoneConditionChecker(opts.doneCondition, { logger: opts.logger });
2143
2176
  }
2144
2177
  opts;
2145
2178
  iterations = 0;
@@ -2245,7 +2278,7 @@ var AutonomousRunner = class {
2245
2278
  };
2246
2279
 
2247
2280
  // src/utils/instruction-file.ts
2248
- import { readFileSync, statSync } from "node:fs";
2281
+ import { readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
2249
2282
  import * as path3 from "node:path";
2250
2283
  import { fileURLToPath } from "node:url";
2251
2284
  var textCache = /* @__PURE__ */ new Map();
@@ -2256,7 +2289,7 @@ function readBundledInstructionText(relativePath) {
2256
2289
  let resolved = "";
2257
2290
  for (const root of instructionRootCandidates()) {
2258
2291
  try {
2259
- resolved = readFileSync(path3.join(root, relativePath), "utf8").trimEnd();
2292
+ resolved = readFileSync2(path3.join(root, relativePath), "utf8").trimEnd();
2260
2293
  break;
2261
2294
  } catch {
2262
2295
  }
@@ -2283,7 +2316,7 @@ function instructionRootCandidates() {
2283
2316
  }
2284
2317
  function isDirectory(candidate) {
2285
2318
  try {
2286
- return statSync(candidate).isDirectory();
2319
+ return statSync2(candidate).isDirectory();
2287
2320
  } catch {
2288
2321
  return false;
2289
2322
  }
@@ -2300,12 +2333,25 @@ function createTieredBrainArbiter(opts) {
2300
2333
  return {
2301
2334
  async decide(request) {
2302
2335
  const policyDecision = await opts.policy.decide(request);
2303
- if (policyDecision.type !== "ask_human") return policyDecision;
2336
+ const provisionalContinue = policyDecision.type === "answer" && policyDecision.optionId === void 0 && request.fallback === "continue";
2337
+ if (policyDecision.type !== "ask_human" && !provisionalContinue) return policyDecision;
2304
2338
  const ceiling = opts.getMaxAutoRisk?.() ?? "medium";
2305
- if (!opts.autonomous || ceiling === "off") return policyDecision;
2339
+ if (ceiling === "off") return policyDecision;
2306
2340
  const ceilingLevel = ceiling === "all" ? 3 : RISK_LEVELS[ceiling] ?? 1;
2307
2341
  const requestLevel = RISK_LEVELS[request.risk] ?? 2;
2308
2342
  if (requestLevel > ceilingLevel) return policyDecision;
2343
+ if (opts.council) {
2344
+ const floor = opts.getCouncilMinRisk?.() ?? "high";
2345
+ const floorLevel = RISK_LEVELS[floor] ?? 2;
2346
+ if (requestLevel >= floorLevel) {
2347
+ try {
2348
+ const councilDecision = await opts.council.decide(request);
2349
+ if (councilDecision.type !== "ask_human") return councilDecision;
2350
+ } catch {
2351
+ }
2352
+ }
2353
+ }
2354
+ if (!opts.autonomous) return policyDecision;
2309
2355
  try {
2310
2356
  const llmDecision = await opts.autonomous.decide(request);
2311
2357
  if (llmDecision.type === "answer") return llmDecision;
@@ -2319,6 +2365,12 @@ function createAutonomyBrain(opts) {
2319
2365
  const maxRisk = opts.maxAutoRisk ?? "high";
2320
2366
  const maxRiskLevel = RISK_LEVELS[maxRisk] ?? 2;
2321
2367
  const timeoutMs = opts.decisionTimeoutMs ?? 15e3;
2368
+ const targets = opts.targets && opts.targets.length > 0 ? opts.targets : opts.provider && opts.model ? [{ provider: opts.provider, model: opts.model }] : [];
2369
+ if (targets.length === 0) {
2370
+ throw new Error("createAutonomyBrain: provide `targets` or `provider` + `model`.");
2371
+ }
2372
+ const strategy = opts.strategy ?? "fallback";
2373
+ let rrCursor = 0;
2322
2374
  return {
2323
2375
  async decide(request) {
2324
2376
  const requestLevel = RISK_LEVELS[request.risk] ?? 2;
@@ -2337,7 +2389,10 @@ function createAutonomyBrain(opts) {
2337
2389
  opts.onDecision?.(formatDecisionSummary(heuristic, request), heuristic, request);
2338
2390
  return heuristic;
2339
2391
  }
2340
- const llmDecision = await llmDecide(request, opts.provider, opts.model, timeoutMs);
2392
+ const start = strategy === "round-robin" ? rrCursor++ % targets.length : 0;
2393
+ const ordered = [...targets.slice(start), ...targets.slice(0, start)];
2394
+ const digest = opts.getDecisionDigest?.(request);
2395
+ const llmDecision = await llmDecide(request, ordered, timeoutMs, digest);
2341
2396
  opts.onDecision?.(formatDecisionSummary(llmDecision, request), llmDecision, request);
2342
2397
  return llmDecision;
2343
2398
  }
@@ -2355,6 +2410,7 @@ function formatDecisionSummary(decision, request) {
2355
2410
  return `\u{1F9E0} Brain: ASKED HUMAN \u2014 "${question}"`;
2356
2411
  }
2357
2412
  function quickDecide(request) {
2413
+ if (request.options?.length) return null;
2358
2414
  const q = request.question.toLowerCase();
2359
2415
  const ctx = request.context?.toLowerCase() ?? "";
2360
2416
  if (q.includes("deadlock") && ctx.includes("failed")) {
@@ -2364,7 +2420,7 @@ function quickDecide(request) {
2364
2420
  rationale: "Heuristic: deadlocked tasks blocked by failed dependencies \u2014 skipping unblocks remaining work."
2365
2421
  };
2366
2422
  }
2367
- if ((q.includes("failed") || q.includes("retry")) && (ctx.includes("3") || ctx.includes("exhausted"))) {
2423
+ if ((q.includes("failed") || q.includes("retry")) && (/\bexhausted\b/.test(ctx) || /\b(?:[3-9]|\d{2,})\s+(?:consecutive\s+)?(?:times|attempts|retries|failures)\b/.test(ctx) || /\b(?:attempt|retr(?:y|ies)|failure)s?\W{0,3}(?:[3-9]|\d{2,})\b/.test(ctx))) {
2368
2424
  return {
2369
2425
  type: "answer",
2370
2426
  text: "Mark as failed and move on. Note the failure for the final report.",
@@ -2374,7 +2430,7 @@ function quickDecide(request) {
2374
2430
  if (q.includes("goal complete") || q.includes("mission complete")) {
2375
2431
  return null;
2376
2432
  }
2377
- if (q.includes("continue") || q.includes("proceed")) {
2433
+ if (request.fallback === "continue" && /\b(?:continue|proceed)\b/.test(q) && !/\b(?:stop|abort|halt|cancel|pause|rollback)\b/.test(q) && !/\bor\b/.test(q)) {
2378
2434
  return {
2379
2435
  type: "answer",
2380
2436
  text: "Continue execution. Do not stop.",
@@ -2383,93 +2439,2355 @@ function quickDecide(request) {
2383
2439
  }
2384
2440
  return null;
2385
2441
  }
2386
- async function llmDecide(request, provider, model, timeoutMs) {
2442
+ function buildBrainUserMessage(request) {
2387
2443
  const optionsText = request.options?.length ? "\nOptions:\n" + request.options.map(
2388
2444
  (o) => ` [${o.id}] ${o.label}${o.consequence ? ` \u2014 ${o.consequence}` : ""}${o.recommended ? " \u2605 recommended" : ""}`
2389
2445
  ).join("\n") : "";
2390
- const systemPrompt = readBundledInstructionText("llm/autonomy-brain.md");
2391
- const userMessage = [
2446
+ return [
2392
2447
  `Question: ${request.question}`,
2393
2448
  request.context ? `
2394
2449
  Context:
2395
2450
  ${request.context}` : "",
2396
2451
  optionsText
2397
2452
  ].filter(Boolean).join("\n");
2398
- try {
2399
- const signal = AbortSignal.timeout(timeoutMs);
2400
- const response = await provider.complete(
2401
- {
2402
- model,
2403
- system: [{ type: "text", text: systemPrompt }],
2404
- messages: [{ role: "user", content: userMessage || "Decide." }],
2405
- maxTokens: 200
2406
- },
2407
- { signal }
2453
+ }
2454
+ function withDecisionDigest(user, digest) {
2455
+ if (!digest?.trim()) return user;
2456
+ return `${user}
2457
+
2458
+ Outcome history of similar past decisions (learn from it):
2459
+ ${digest}`;
2460
+ }
2461
+ async function completeBrainLlm(target, input) {
2462
+ const signal = AbortSignal.timeout(input.timeoutMs);
2463
+ const response = await target.provider.complete(
2464
+ {
2465
+ model: target.model,
2466
+ system: [{ type: "text", text: input.system }],
2467
+ messages: [{ role: "user", content: input.user || "Decide." }],
2468
+ maxTokens: input.maxTokens ?? 200
2469
+ },
2470
+ { signal }
2471
+ );
2472
+ return extractText(response).trim();
2473
+ }
2474
+ async function llmDecide(request, targets, timeoutMs, digest) {
2475
+ const systemPrompt = readBundledInstructionText("llm/autonomy-brain.md");
2476
+ const userMessage = withDecisionDigest(buildBrainUserMessage(request), digest);
2477
+ let text = null;
2478
+ for (const target of targets) {
2479
+ try {
2480
+ text = await completeBrainLlm(target, {
2481
+ system: systemPrompt,
2482
+ user: userMessage,
2483
+ timeoutMs
2484
+ });
2485
+ break;
2486
+ } catch {
2487
+ }
2488
+ }
2489
+ if (text === null) {
2490
+ if (request.fallback === "continue") {
2491
+ return {
2492
+ type: "answer",
2493
+ text: "Continue (Autonomy Brain LLM unavailable \u2014 using fallback)."
2494
+ };
2495
+ }
2496
+ return { type: "deny", reason: "Autonomy Brain LLM unavailable for decision." };
2497
+ }
2498
+ if (request.options?.length) {
2499
+ const parsed = parseOptionDecision(text, request.options);
2500
+ if (parsed) {
2501
+ return parsed;
2502
+ }
2503
+ return {
2504
+ type: "deny",
2505
+ reason: "Autonomy Brain returned no exact valid option id."
2506
+ };
2507
+ }
2508
+ return {
2509
+ type: "answer",
2510
+ text: text || (request.fallback === "continue" ? "Continue execution." : "Denied by autonomy policy."),
2511
+ rationale: text || void 0
2512
+ };
2513
+ }
2514
+ function parseOptionDecision(rawText, options) {
2515
+ const text = rawText.trim();
2516
+ const byId = new Map(options.map((option2) => [option2.id, option2]));
2517
+ const wholeFence = /^```(?:json)?[ \t]*\r?\n([\s\S]*?)\r?\n?```$/i.exec(text);
2518
+ const jsonCandidate = wholeFence ? (wholeFence[1] ?? "").trim() : text;
2519
+ const parsed = safeParse(jsonCandidate, 16384);
2520
+ if (parsed.ok && parsed.value && typeof parsed.value.optionId === "string") {
2521
+ const option2 = byId.get(parsed.value.optionId);
2522
+ if (!option2) return null;
2523
+ return {
2524
+ type: "answer",
2525
+ optionId: option2.id,
2526
+ text: option2.label,
2527
+ rationale: typeof parsed.value.rationale === "string" && parsed.value.rationale.trim() ? parsed.value.rationale.trim() : void 0
2528
+ };
2529
+ }
2530
+ const legacy = /^\s*\[([^\]\r\n]+)\](?:\s*(?:—|-|:)\s*)?([\s\S]*)$/.exec(text);
2531
+ if (!legacy) return null;
2532
+ const option = byId.get((legacy[1] ?? "").trim());
2533
+ if (!option) return null;
2534
+ const rationale = (legacy[2] ?? "").trim();
2535
+ return {
2536
+ type: "answer",
2537
+ optionId: option.id,
2538
+ text: option.label,
2539
+ rationale: rationale || void 0
2540
+ };
2541
+ }
2542
+ function extractText(result) {
2543
+ if (!result || typeof result !== "object") return "";
2544
+ const r = result;
2545
+ if (Array.isArray(r.content)) {
2546
+ return r.content.filter((b) => b.type === "text").map((b) => b.text ?? "").join("");
2547
+ }
2548
+ if (Array.isArray(r.choices)) {
2549
+ return r.choices[0]?.message?.content ?? "";
2550
+ }
2551
+ return typeof r.text === "string" ? r.text : "";
2552
+ }
2553
+
2554
+ // src/types/provider.ts
2555
+ var CONTEXT_OVERFLOW_RE = /context.length|context.window|max.*tokens?.*exceeded|prompt is too long|too long|exceeds the context|\btokens\b.*exceed|context_length_exceeded/i;
2556
+ var CONTENT_FILTER_RE = /content.(filter|policy|moderation)|safety (system|filter)/i;
2557
+ function classifyProviderError(status, body, message) {
2558
+ const type = body?.type;
2559
+ if (status === 0) return "network";
2560
+ if (status === 408) return "timeout";
2561
+ if (status === 599) return "stream_hang";
2562
+ if (type === "rate_limit_error" || status === 429) return "rate_limit";
2563
+ if (type === "overloaded_error" || status === 529) return "overloaded";
2564
+ if (status >= 500) return "server";
2565
+ if (type === "authentication_error" || type === "permission_error" || status === 401 || status === 403) {
2566
+ return "auth";
2567
+ }
2568
+ const text = [message, body?.message, type, body?.raw].filter(Boolean).join("\n");
2569
+ if (type === "content_filter" || CONTENT_FILTER_RE.test(text)) return "content_filter";
2570
+ if (status === 413 || status >= 400 && CONTEXT_OVERFLOW_RE.test(text)) {
2571
+ return "context_overflow";
2572
+ }
2573
+ if (status >= 400) return "invalid_request";
2574
+ return "unknown";
2575
+ }
2576
+ function isRetryableKind(kind) {
2577
+ return RETRYABLE_BY_KIND[kind];
2578
+ }
2579
+ var RETRYABLE_BY_KIND = {
2580
+ rate_limit: true,
2581
+ overloaded: true,
2582
+ server: true,
2583
+ timeout: true,
2584
+ network: true,
2585
+ stream_hang: true,
2586
+ auth: false,
2587
+ context_overflow: false,
2588
+ content_filter: false,
2589
+ invalid_request: false,
2590
+ unknown: false
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
+ };
2608
+ var ProviderError = class extends WrongStackError {
2609
+ status;
2610
+ retryable;
2611
+ providerId;
2612
+ /** Canonical failure classification — see {@link ProviderErrorKind}. */
2613
+ kind;
2614
+ body;
2615
+ constructor(message, status, retryable, providerId, opts = {}) {
2616
+ const kind = opts.kind ?? classifyProviderError(status, opts.body, message);
2617
+ super({
2618
+ message,
2619
+ code: kindToCode(kind),
2620
+ subsystem: "provider",
2621
+ severity: status >= 500 ? "error" : "warning",
2622
+ recoverable: retryable,
2623
+ context: { providerId, status },
2624
+ cause: opts.cause
2625
+ });
2626
+ this.name = "ProviderError";
2627
+ this.status = status;
2628
+ this.retryable = retryable;
2629
+ this.providerId = providerId;
2630
+ this.kind = kind;
2631
+ this.body = opts.body;
2632
+ }
2633
+ /**
2634
+ * Render a one-line, user-facing description. Designed for the CLI/TUI
2635
+ * status line and the agent's retry warning. Avoids dumping raw JSON
2636
+ * (which is what users see today when a 529 lands and the log message
2637
+ * includes the full `{"type":"error",...}` body).
2638
+ *
2639
+ * Examples:
2640
+ * "minimax-coding-plan overloaded (529): High traffic detected. Upgrade for highspeed model. [req 06534785201de9c0…]"
2641
+ * "openai rate limited (429): Retry after 12s"
2642
+ * "anthropic invalid request (400): messages.0.role must be one of 'user'|'assistant'"
2643
+ * "groq HTTP 500 (server error)"
2644
+ */
2645
+ describe() {
2646
+ const kind = describeStatus(this.status, this.body?.type);
2647
+ const head = `${this.providerId} ${kind}`;
2648
+ const detail = this.body?.message?.trim();
2649
+ const reqId = this.body?.requestId ? ` [req ${this.body.requestId.slice(0, 16)}${this.body.requestId.length > 16 ? "\u2026" : ""}]` : "";
2650
+ if (detail && detail.length > 0) {
2651
+ return `${head}: ${truncate(detail, 240)}${reqId}`;
2652
+ }
2653
+ return `${head}${reqId}`;
2654
+ }
2655
+ };
2656
+ function describeStatus(status, type) {
2657
+ if (status === 0) return "network error";
2658
+ if (status === 599) return `stream hang (${status})`;
2659
+ if (type === "overloaded_error" || status === 529) return `overloaded (${status})`;
2660
+ if (type === "rate_limit_error" || status === 429) return `rate limited (${status})`;
2661
+ if (type === "authentication_error" || status === 401) return `auth failed (${status})`;
2662
+ if (type === "permission_error" || status === 403) return `forbidden (${status})`;
2663
+ if (type === "not_found_error" || status === 404) return `not found (${status})`;
2664
+ if (type === "content_filter") return `content filtered (${status})`;
2665
+ if (type === "invalid_request_error" || status === 400) return `invalid request (${status})`;
2666
+ if (status === 408) return `timeout (${status})`;
2667
+ if (status >= 500 && status < 600) return `HTTP ${status} (server error)`;
2668
+ if (type) return `${type} (${status})`;
2669
+ return `HTTP ${status}`;
2670
+ }
2671
+ var KIND_TO_CODE = {
2672
+ network: ERROR_CODES.PROVIDER_NETWORK_ERROR,
2673
+ timeout: ERROR_CODES.PROVIDER_NETWORK_ERROR,
2674
+ rate_limit: ERROR_CODES.PROVIDER_RATE_LIMITED,
2675
+ auth: ERROR_CODES.PROVIDER_AUTH_FAILED,
2676
+ overloaded: ERROR_CODES.PROVIDER_OVERLOADED,
2677
+ context_overflow: ERROR_CODES.PROVIDER_CONTEXT_OVERFLOW,
2678
+ server: ERROR_CODES.PROVIDER_SERVER_ERROR,
2679
+ stream_hang: ERROR_CODES.PROVIDER_SERVER_ERROR,
2680
+ content_filter: ERROR_CODES.PROVIDER_INVALID_REQUEST,
2681
+ invalid_request: ERROR_CODES.PROVIDER_INVALID_REQUEST,
2682
+ unknown: ERROR_CODES.PROVIDER_INVALID_REQUEST
2683
+ };
2684
+ function kindToCode(kind) {
2685
+ return KIND_TO_CODE[kind];
2686
+ }
2687
+
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
+ }
2693
+ function parseModelRef(ref) {
2694
+ const trimmed = ref.trim();
2695
+ const slash = trimmed.indexOf("/");
2696
+ if (slash !== -1) {
2697
+ return {
2698
+ provider: trimmed.slice(0, slash) || void 0,
2699
+ model: trimmed.slice(slash + 1).trim()
2700
+ };
2701
+ }
2702
+ const parts = trimmed.split(/\s+/);
2703
+ if (parts.length >= 2) {
2704
+ return { provider: parts[0], model: parts.slice(1).join(" ") };
2705
+ }
2706
+ return { model: trimmed };
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
+ }
2778
+ var DEFAULT_PRIMARY_COOLDOWN_MAX_MS = 10 * 6e4;
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
+
2876
+ // src/execution/council-brain.ts
2877
+ var COUNCIL_REFUSE_OPTION_ID = "council_refuse";
2878
+ var BUILTIN_PERSONAS = {
2879
+ executor: "PERSONA: You hold the EXECUTOR seat. You are biased toward forward progress \u2014 stalled work is the failure mode you exist to prevent. Favor the option that keeps the system moving, unless doing so is clearly reckless.",
2880
+ skeptic: "PERSONA: You hold the SKEPTIC seat. Your job is to find the concrete reason the proposed action is wrong, unsafe, or based on a false premise. If you find one, vote against it or refuse. Do not oppose for its own sake \u2014 a sound proposal deserves your vote.",
2881
+ auditor: "PERSONA: You hold the AUDITOR seat. You judge through the cost/waste lens: budget already burned, expected cost of each option, and the odds of throwing resources at a dead end. Prefer the option with the best expected value per token spent."
2882
+ };
2883
+ function personaText(persona) {
2884
+ if (!persona) return BUILTIN_PERSONAS["executor"] ?? "";
2885
+ return BUILTIN_PERSONAS[persona] ?? `PERSONA: ${persona}`;
2886
+ }
2887
+ function voterLabel(voter, index) {
2888
+ return voter.label ?? `${voter.persona ?? "voter"}#${index + 1} (${voter.model})`;
2889
+ }
2890
+ function ballotOptions(request) {
2891
+ const options = request.options ?? [];
2892
+ if (options.some((o) => o.id === COUNCIL_REFUSE_OPTION_ID)) return options;
2893
+ return [
2894
+ ...options,
2895
+ {
2896
+ id: COUNCIL_REFUSE_OPTION_ID,
2897
+ label: "Refuse \u2014 none of these options should be taken",
2898
+ consequence: "The proposed action is not taken at all."
2899
+ }
2900
+ ];
2901
+ }
2902
+ function createCouncilBrainArbiter(opts) {
2903
+ if (opts.voters.length === 0) {
2904
+ throw new Error("createCouncilBrainArbiter: at least one voter is required.");
2905
+ }
2906
+ const quorumFraction = opts.quorumFraction ?? 0.5;
2907
+ const approvalFraction = opts.approvalFraction ?? 0.5;
2908
+ const timeoutMs = opts.decisionTimeoutMs ?? 15e3;
2909
+ const baseSystem = readBundledInstructionText("llm/autonomy-brain.md");
2910
+ const abstain = (request, why) => ({
2911
+ type: "ask_human",
2912
+ prompt: `Council abstained (${why}): ${request.question}`,
2913
+ options: request.options,
2914
+ rationale: `Council could not reach a decision: ${why}.`
2915
+ });
2916
+ const finish = (request, decision) => {
2917
+ opts.onDecision?.(formatDecisionSummary(decision, request), decision, request);
2918
+ return decision;
2919
+ };
2920
+ async function collectVotes(request, ballot, digest) {
2921
+ const ballotRequest = { ...request, options: ballot };
2922
+ const user = withDecisionDigest(buildBrainUserMessage(ballotRequest), digest);
2923
+ const settled = await Promise.allSettled(
2924
+ opts.voters.map(
2925
+ (voter) => completeBrainLlm(voter, {
2926
+ system: `${baseSystem}
2927
+
2928
+ ${personaText(voter.persona)}`,
2929
+ user,
2930
+ timeoutMs
2931
+ })
2932
+ )
2933
+ );
2934
+ const votes = [];
2935
+ settled.forEach((result, i) => {
2936
+ const voter = opts.voters[i];
2937
+ if (!voter || result.status !== "fulfilled") return;
2938
+ const parsed = parseOptionDecision(result.value, ballot);
2939
+ if (parsed?.type !== "answer" || !parsed.optionId) return;
2940
+ votes.push({
2941
+ seatId: String(i),
2942
+ voter,
2943
+ optionId: parsed.optionId,
2944
+ rationale: parsed.rationale
2945
+ });
2946
+ });
2947
+ return votes;
2948
+ }
2949
+ async function judgeRound(request, ballot, votes, reason) {
2950
+ if (!opts.judge) return null;
2951
+ const voteLines = votes.map(
2952
+ (v, i) => `- ${voterLabel(v.voter, i)} voted [${v.optionId}]` + (v.rationale ? ` \u2014 ${v.rationale}` : "")
2953
+ ).join("\n");
2954
+ const judgeRequest = {
2955
+ ...request,
2956
+ options: ballot,
2957
+ context: [
2958
+ request.context ?? "",
2959
+ "",
2960
+ `Council votes (${reason} \u2014 you are the judge; issue the final decision):`,
2961
+ voteLines
2962
+ ].join("\n").trim()
2963
+ };
2964
+ try {
2965
+ const text = await completeBrainLlm(opts.judge, {
2966
+ system: `${baseSystem}
2967
+
2968
+ PERSONA: You are the council JUDGE. The voting seats disagreed or were too close to call. Weigh their rationales and issue the single final decision.`,
2969
+ user: buildBrainUserMessage(judgeRequest),
2970
+ timeoutMs,
2971
+ maxTokens: 300
2972
+ });
2973
+ const parsed = parseOptionDecision(text, ballot);
2974
+ if (parsed?.type === "answer" && parsed.optionId) return parsed;
2975
+ return null;
2976
+ } catch {
2977
+ return null;
2978
+ }
2979
+ }
2980
+ return {
2981
+ async decide(request) {
2982
+ const digest = opts.getDecisionDigest?.(request);
2983
+ if (!request.options?.length) {
2984
+ const user = withDecisionDigest(buildBrainUserMessage(request), digest);
2985
+ const settled = await Promise.allSettled(
2986
+ opts.voters.map(
2987
+ (voter) => completeBrainLlm(voter, {
2988
+ system: `${baseSystem}
2989
+
2990
+ ${personaText(voter.persona)}`,
2991
+ user,
2992
+ timeoutMs
2993
+ })
2994
+ )
2995
+ );
2996
+ const stances = [];
2997
+ settled.forEach((result, i) => {
2998
+ const voter = opts.voters[i];
2999
+ if (!voter || result.status !== "fulfilled" || !result.value.trim()) return;
3000
+ stances.push({ voter, text: result.value.trim() });
3001
+ });
3002
+ if (stances.length / opts.voters.length < quorumFraction) {
3003
+ return finish(request, abstain(request, "quorum not met"));
3004
+ }
3005
+ if (opts.judge) {
3006
+ const stanceLines = stances.map((s, i) => `- ${voterLabel(s.voter, i)}: ${s.text}`).join("\n");
3007
+ try {
3008
+ const text = await completeBrainLlm(opts.judge, {
3009
+ system: `${baseSystem}
3010
+
3011
+ PERSONA: You are the council JUDGE. Synthesize the seats\u2019 independent stances below into the single final decision (1-2 sentences).`,
3012
+ user: `${buildBrainUserMessage(request)}
3013
+
3014
+ Council stances:
3015
+ ${stanceLines}`,
3016
+ timeoutMs,
3017
+ maxTokens: 300
3018
+ });
3019
+ if (text) {
3020
+ return finish(request, {
3021
+ type: "answer",
3022
+ text,
3023
+ rationale: `Council synthesis of ${stances.length}/${opts.voters.length} stances.`
3024
+ });
3025
+ }
3026
+ } catch {
3027
+ }
3028
+ }
3029
+ const first = stances[0];
3030
+ if (first) {
3031
+ return finish(request, {
3032
+ type: "answer",
3033
+ text: first.text,
3034
+ rationale: `Council (no judge): stance of ${voterLabel(first.voter, 0)}.`
3035
+ });
3036
+ }
3037
+ return finish(request, abstain(request, "no usable stance"));
3038
+ }
3039
+ const ballot = ballotOptions(request);
3040
+ const votes = await collectVotes(request, ballot, digest);
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
+ );
3060
+ }
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
+ }
3066
+ return finish(request, {
3067
+ type: "deny",
3068
+ reason: `Council veto by ${voterLabel(vetoRefusal.voter, 0)}` + (vetoRefusal.rationale ? `: ${vetoRefusal.rationale}` : ".")
3069
+ });
3070
+ }
3071
+ let finalOptionId = resolution.status === "decided" || resolution.status === "denied" ? resolution.optionId : null;
3072
+ let viaJudge = false;
3073
+ if (resolution.status === "needs_judge") {
3074
+ const judged = await judgeRound(
3075
+ request,
3076
+ ballot,
3077
+ votes,
3078
+ resolution.reason === "tie" ? "tie" : "no option cleared the approval threshold"
3079
+ );
3080
+ if (judged?.type === "answer" && judged.optionId) {
3081
+ finalOptionId = judged.optionId;
3082
+ viaJudge = true;
3083
+ }
3084
+ }
3085
+ if (!finalOptionId) {
3086
+ return finish(request, abstain(request, "split vote and no judge verdict"));
3087
+ }
3088
+ const supporting = votes.filter((v) => v.optionId === finalOptionId);
3089
+ const voteSummary = votes.map((v, i) => `${voterLabel(v.voter, i)}\u2192[${v.optionId}]`).join(", ");
3090
+ const rationale = (supporting.find((v) => v.rationale)?.rationale ?? "") + ` (council${viaJudge ? " via judge" : ""}: ${voteSummary})`;
3091
+ if (finalOptionId === COUNCIL_REFUSE_OPTION_ID) {
3092
+ return finish(request, {
3093
+ type: "deny",
3094
+ reason: `Council refused the proposed action.${rationale}`
3095
+ });
3096
+ }
3097
+ const option = request.options.find((o) => o.id === finalOptionId);
3098
+ return finish(request, {
3099
+ type: "answer",
3100
+ optionId: finalOptionId,
3101
+ text: option?.label ?? finalOptionId,
3102
+ rationale: rationale.trim()
3103
+ });
3104
+ }
3105
+ };
3106
+ }
3107
+
3108
+ // src/execution/brain-chain.ts
3109
+ var DEFAULT_SEATS = [
3110
+ { persona: "executor" },
3111
+ { persona: "skeptic", veto: true },
3112
+ { persona: "auditor" }
3113
+ ];
3114
+ function assembleBrainTiers(opts) {
3115
+ const brainCfg = opts.brainConfig;
3116
+ const toEntry = (entry) => typeof entry === "string" ? parseModelRef(entry) : entry;
3117
+ const buildTarget = (entry) => {
3118
+ if (!entry.model) return null;
3119
+ const providerId = entry.provider ?? opts.defaultProviderId;
3120
+ try {
3121
+ const provider = providerId === opts.defaultProviderId ? opts.sessionProvider() : opts.resolveProvider(providerId, entry.model);
3122
+ if (!provider) return null;
3123
+ return { provider, model: entry.model, label: `${providerId}/${entry.model}` };
3124
+ } catch {
3125
+ return null;
3126
+ }
3127
+ };
3128
+ const poolTargets = (brainCfg?.models ?? []).map(toEntry).map(buildTarget).filter((t) => t !== null);
3129
+ const autonomous = poolTargets.length > 0 ? createAutonomyBrain({
3130
+ targets: poolTargets,
3131
+ strategy: brainCfg?.strategy ?? "fallback",
3132
+ maxAutoRisk: "all",
3133
+ // the tiered ceiling gates risk — keep inner permissive
3134
+ decisionTimeoutMs: brainCfg?.decisionTimeoutMs,
3135
+ getDecisionDigest: opts.getDecisionDigest
3136
+ }) : {
3137
+ decide: (request) => createAutonomyBrain({
3138
+ provider: opts.sessionProvider(),
3139
+ model: opts.sessionModel(),
3140
+ maxAutoRisk: "all",
3141
+ decisionTimeoutMs: brainCfg?.decisionTimeoutMs,
3142
+ getDecisionDigest: opts.getDecisionDigest
3143
+ }).decide(request)
3144
+ };
3145
+ const councilCfg = brainCfg?.council;
3146
+ const explicitVoters = (councilCfg?.voters ?? []).map(
3147
+ (v) => typeof v === "string" ? { entry: toEntry(v), cfg: {} } : { entry: v, cfg: v }
3148
+ );
3149
+ const voters = (explicitVoters.length > 0 ? explicitVoters.map(({ entry, cfg }, i) => {
3150
+ const target = buildTarget(entry);
3151
+ if (!target) return null;
3152
+ const seat = DEFAULT_SEATS[i % DEFAULT_SEATS.length];
3153
+ return {
3154
+ ...target,
3155
+ persona: cfg.persona ?? seat?.persona,
3156
+ weight: cfg.weight,
3157
+ veto: cfg.veto ?? seat?.veto
3158
+ };
3159
+ }) : poolTargets.slice(0, DEFAULT_SEATS.length).map((target, i) => ({
3160
+ ...target,
3161
+ ...DEFAULT_SEATS[i % DEFAULT_SEATS.length]
3162
+ }))).filter((v) => v !== null);
3163
+ const judgeEntry = councilCfg?.judge ? typeof councilCfg.judge === "string" ? toEntry(councilCfg.judge) : councilCfg.judge : void 0;
3164
+ const judge = (judgeEntry ? buildTarget(judgeEntry) : null) ?? poolTargets[0] ?? voters[0];
3165
+ const councilEnabled = councilCfg?.enabled ?? voters.length >= 2;
3166
+ const council = councilEnabled && voters.length >= 2 ? createCouncilBrainArbiter({
3167
+ voters,
3168
+ judge,
3169
+ quorumFraction: councilCfg?.quorum,
3170
+ approvalFraction: councilCfg?.approval,
3171
+ decisionTimeoutMs: brainCfg?.decisionTimeoutMs,
3172
+ getDecisionDigest: opts.getDecisionDigest
3173
+ }) : void 0;
3174
+ return {
3175
+ autonomous,
3176
+ council,
3177
+ getCouncilMinRisk: () => councilCfg?.minRisk ?? "high",
3178
+ poolLabels: poolTargets.map((t) => t.label ?? t.model),
3179
+ councilLabels: council ? voters.map((v) => `${v.label ?? v.model} (${v.persona ?? "voter"}${v.veto ? ", veto" : ""})`) : []
3180
+ };
3181
+ }
3182
+
3183
+ // src/execution/brain-evaluation.ts
3184
+ var BRAIN_EVALUATION_CASE_VERSION = 1;
3185
+ function isRecord3(value) {
3186
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3187
+ }
3188
+ function isStringArray(value) {
3189
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string");
3190
+ }
3191
+ function validateBrainEvaluationCase(value) {
3192
+ const diagnostics = [];
3193
+ if (!isRecord3(value)) return { ok: false, diagnostics: ["case must be an object"] };
3194
+ if (value["version"] !== BRAIN_EVALUATION_CASE_VERSION) {
3195
+ diagnostics.push(`version must be ${BRAIN_EVALUATION_CASE_VERSION}`);
3196
+ }
3197
+ if (typeof value["id"] !== "string" || value["id"].trim().length === 0) {
3198
+ diagnostics.push("id must be a non-empty string");
3199
+ }
3200
+ const request = value["request"];
3201
+ if (!isRecord3(request)) {
3202
+ diagnostics.push("request must be an object");
3203
+ } else {
3204
+ if (typeof request["id"] !== "string" || request["id"].trim().length === 0) {
3205
+ diagnostics.push("request.id must be a non-empty string");
3206
+ }
3207
+ if (!["autophase", "director", "tool", "user", "system"].includes(String(request["source"]))) {
3208
+ diagnostics.push("request.source is invalid");
3209
+ }
3210
+ if (typeof request["question"] !== "string" || request["question"].trim().length === 0) {
3211
+ diagnostics.push("request.question must be a non-empty string");
3212
+ }
3213
+ if (!["low", "medium", "high", "critical"].includes(String(request["risk"]))) {
3214
+ diagnostics.push("request.risk is invalid");
3215
+ }
3216
+ if (!["ask_human", "deny", "continue"].includes(String(request["fallback"]))) {
3217
+ diagnostics.push("request.fallback is invalid");
3218
+ }
3219
+ if (request["options"] !== void 0) {
3220
+ if (!Array.isArray(request["options"])) {
3221
+ diagnostics.push("request.options must be an array");
3222
+ } else {
3223
+ const ids = /* @__PURE__ */ new Set();
3224
+ for (const [index, option] of request["options"].entries()) {
3225
+ if (!isRecord3(option)) {
3226
+ diagnostics.push(`request.options[${index}] must be an object`);
3227
+ continue;
3228
+ }
3229
+ const id = option["id"];
3230
+ if (typeof id !== "string" || id.trim().length === 0) {
3231
+ diagnostics.push(`request.options[${index}].id must be a non-empty string`);
3232
+ } else if (ids.has(id)) {
3233
+ diagnostics.push(`request option id "${id}" is duplicated`);
3234
+ } else {
3235
+ ids.add(id);
3236
+ }
3237
+ if (typeof option["label"] !== "string" || option["label"].trim().length === 0) {
3238
+ diagnostics.push(`request.options[${index}].label must be a non-empty string`);
3239
+ }
3240
+ }
3241
+ }
3242
+ }
3243
+ }
3244
+ const expectations = value["expectations"];
3245
+ if (expectations !== void 0 && !isRecord3(expectations)) {
3246
+ diagnostics.push("expectations must be an object");
3247
+ } else if (isRecord3(expectations)) {
3248
+ const allowedTypes = expectations["allowedDecisionTypes"];
3249
+ if (allowedTypes !== void 0 && (!isStringArray(allowedTypes) || allowedTypes.length === 0 || allowedTypes.some((entry) => !["answer", "ask_human", "deny"].includes(entry)))) {
3250
+ diagnostics.push("expectations.allowedDecisionTypes is invalid");
3251
+ }
3252
+ for (const field of ["allowedOptionIds", "unsafeOptionIds"]) {
3253
+ const entries = expectations[field];
3254
+ if (entries !== void 0 && !isStringArray(entries)) {
3255
+ diagnostics.push(`expectations.${field} must be a string array`);
3256
+ }
3257
+ }
3258
+ if (isRecord3(request) && Array.isArray(request["options"])) {
3259
+ const requestIds = new Set(
3260
+ request["options"].filter(isRecord3).map((option) => option["id"]).filter((id) => typeof id === "string")
3261
+ );
3262
+ for (const field of ["allowedOptionIds", "unsafeOptionIds"]) {
3263
+ const entries = expectations[field];
3264
+ if (!isStringArray(entries)) continue;
3265
+ for (const id of entries) {
3266
+ if (!requestIds.has(id))
3267
+ diagnostics.push(`expectations.${field} contains unknown id "${id}"`);
3268
+ }
3269
+ }
3270
+ }
3271
+ for (const field of ["requireOptionId", "allowEscalation"]) {
3272
+ const setting = expectations[field];
3273
+ if (setting !== void 0 && typeof setting !== "boolean") {
3274
+ diagnostics.push(`expectations.${field} must be a boolean`);
3275
+ }
3276
+ }
3277
+ }
3278
+ return diagnostics.length > 0 ? { ok: false, diagnostics } : { ok: true, value };
3279
+ }
3280
+ function assessDecision(fixture, decision) {
3281
+ const failures = [];
3282
+ const expectations = fixture.expectations;
3283
+ if (expectations?.allowedDecisionTypes && !expectations.allowedDecisionTypes.includes(decision.type)) {
3284
+ failures.push({
3285
+ code: "unexpected_decision_type",
3286
+ message: `decision type "${decision.type}" is not allowed`
3287
+ });
3288
+ }
3289
+ if (decision.type === "ask_human" && expectations?.allowEscalation !== true) {
3290
+ failures.push({
3291
+ code: "unexpected_escalation",
3292
+ message: "case escalated to a human without allowEscalation"
3293
+ });
3294
+ }
3295
+ if (decision.type !== "answer") return failures;
3296
+ const requestOptionIds = new Set(fixture.request.options?.map((option) => option.id) ?? []);
3297
+ if (decision.optionId === void 0) {
3298
+ if (expectations?.requireOptionId || expectations?.allowedOptionIds) {
3299
+ failures.push({
3300
+ code: "missing_option_id",
3301
+ message: "answer did not return the required exact option id"
3302
+ });
3303
+ }
3304
+ return failures;
3305
+ }
3306
+ if (!requestOptionIds.has(decision.optionId)) {
3307
+ failures.push({
3308
+ code: "invalid_option_id",
3309
+ message: `answer returned unknown option id "${decision.optionId}"`
3310
+ });
3311
+ }
3312
+ if (expectations?.allowedOptionIds && !expectations.allowedOptionIds.includes(decision.optionId)) {
3313
+ failures.push({
3314
+ code: "disallowed_option_id",
3315
+ message: `option id "${decision.optionId}" is not allowed for this case`
3316
+ });
3317
+ }
3318
+ if (expectations?.unsafeOptionIds?.includes(decision.optionId)) {
3319
+ failures.push({
3320
+ code: "unsafe_option_id",
3321
+ message: `unsafe option id "${decision.optionId}" was selected`
3322
+ });
3323
+ }
3324
+ return failures;
3325
+ }
3326
+ async function runBrainEvaluation(arbiter, cases) {
3327
+ const results = [];
3328
+ const seenCaseIds = /* @__PURE__ */ new Set();
3329
+ for (const rawCase of cases) {
3330
+ const validation = validateBrainEvaluationCase(rawCase);
3331
+ if (!validation.ok) {
3332
+ const caseId = isRecord3(rawCase) && typeof rawCase["id"] === "string" ? rawCase["id"] : "<invalid>";
3333
+ results.push({
3334
+ caseId,
3335
+ passed: false,
3336
+ failures: validation.diagnostics.map((message) => ({ code: "invalid_fixture", message })),
3337
+ latencyMs: 0
3338
+ });
3339
+ continue;
3340
+ }
3341
+ const fixture = validation.value;
3342
+ if (seenCaseIds.has(fixture.id)) {
3343
+ results.push({
3344
+ caseId: fixture.id,
3345
+ passed: false,
3346
+ failures: [{ code: "invalid_fixture", message: `case id "${fixture.id}" is duplicated` }],
3347
+ latencyMs: 0
3348
+ });
3349
+ continue;
3350
+ }
3351
+ seenCaseIds.add(fixture.id);
3352
+ const startedAt = performance.now();
3353
+ try {
3354
+ const decision = await arbiter.decide(structuredClone(fixture.request));
3355
+ const failures = assessDecision(fixture, decision);
3356
+ results.push({
3357
+ caseId: fixture.id,
3358
+ passed: failures.length === 0,
3359
+ decision,
3360
+ failures,
3361
+ latencyMs: Math.max(0, performance.now() - startedAt)
3362
+ });
3363
+ } catch (error) {
3364
+ results.push({
3365
+ caseId: fixture.id,
3366
+ passed: false,
3367
+ failures: [
3368
+ {
3369
+ code: "arbiter_error",
3370
+ message: error instanceof Error ? error.message : String(error)
3371
+ }
3372
+ ],
3373
+ latencyMs: Math.max(0, performance.now() - startedAt)
3374
+ });
3375
+ }
3376
+ }
3377
+ const optionResults = results.filter(
3378
+ (result) => result.decision?.type === "answer" && result.decision.optionId !== void 0
3379
+ );
3380
+ const invalidOptionCount = results.reduce(
3381
+ (count, result) => count + result.failures.filter((failure) => failure.code === "invalid_option_id").length,
3382
+ 0
3383
+ );
3384
+ const totalLatency = results.reduce((sum, result) => sum + result.latencyMs, 0);
3385
+ const metrics = {
3386
+ totalCases: results.length,
3387
+ passedCases: results.filter((result) => result.passed).length,
3388
+ failedCases: results.filter((result) => !result.passed).length,
3389
+ optionDecisionCount: optionResults.length,
3390
+ invalidOptionCount,
3391
+ validOptionRate: optionResults.length === 0 ? 1 : (optionResults.length - invalidOptionCount) / optionResults.length,
3392
+ unsafeAllowanceCount: results.reduce(
3393
+ (count, result) => count + result.failures.filter((failure) => failure.code === "unsafe_option_id").length,
3394
+ 0
3395
+ ),
3396
+ unnecessaryEscalationCount: results.reduce(
3397
+ (count, result) => count + result.failures.filter((failure) => failure.code === "unexpected_escalation").length,
3398
+ 0
3399
+ ),
3400
+ averageLatencyMs: results.length === 0 ? 0 : totalLatency / results.length
3401
+ };
3402
+ return { version: BRAIN_EVALUATION_CASE_VERSION, results, metrics };
3403
+ }
3404
+
3405
+ // src/coordination/brain.ts
3406
+ var DefaultBrainArbiter = class {
3407
+ allowLowRiskAutoAnswer;
3408
+ constructor(opts = {}) {
3409
+ this.allowLowRiskAutoAnswer = opts.allowLowRiskAutoAnswer ?? true;
3410
+ }
3411
+ async decide(request) {
3412
+ const recommended = request.options?.find((option) => option.recommended);
3413
+ if (this.allowLowRiskAutoAnswer && request.risk === "low" && recommended) {
3414
+ return {
3415
+ type: "answer",
3416
+ optionId: recommended.id,
3417
+ text: recommended.label,
3418
+ rationale: "Low-risk request with an explicit recommended option."
3419
+ };
3420
+ }
3421
+ switch (request.fallback) {
3422
+ case "deny":
3423
+ return {
3424
+ type: "deny",
3425
+ reason: `Brain could not safely decide: ${request.question}`
3426
+ };
3427
+ case "continue":
3428
+ return {
3429
+ type: "answer",
3430
+ text: "Continue with the caller default.",
3431
+ rationale: "No safe Brain decision was available; request fallback is continue."
3432
+ };
3433
+ case "ask_human":
3434
+ return {
3435
+ type: "ask_human",
3436
+ prompt: formatHumanPrompt(request),
3437
+ options: request.options,
3438
+ rationale: "Decision requires human authority or lacks a safe automatic option."
3439
+ };
3440
+ default:
3441
+ return {
3442
+ type: "deny",
3443
+ reason: `Brain could not safely decide (unknown fallback: ${String(request.fallback)}): ${request.question}`
3444
+ };
3445
+ }
3446
+ }
3447
+ };
3448
+ function formatHumanPrompt(request) {
3449
+ const lines = [
3450
+ `Brain requires human decision for ${request.source}:`,
3451
+ `Question: ${request.question}`
3452
+ ];
3453
+ if (request.context?.trim()) {
3454
+ lines.push("", "Context:", request.context.trim());
3455
+ }
3456
+ if (request.options?.length) {
3457
+ lines.push("", "Options:");
3458
+ for (const option of request.options) {
3459
+ const risk = option.risk ? ` [risk: ${option.risk}]` : "";
3460
+ const consequence = option.consequence ? ` \u2014 ${option.consequence}` : "";
3461
+ lines.push(`- ${option.id}: ${option.label}${risk}${consequence}`);
3462
+ }
3463
+ }
3464
+ lines.push("", `Risk: ${request.risk}`);
3465
+ return lines.join("\n");
3466
+ }
3467
+
3468
+ // src/coordination/brain-ledger.ts
3469
+ function createLedgerGuardBrainArbiter(opts) {
3470
+ const denyAfter = opts.denyAfter ?? 3;
3471
+ return {
3472
+ async decide(request) {
3473
+ if (denyAfter > 0) {
3474
+ const streak = opts.failureStreakFor(request);
3475
+ if (streak >= denyAfter) {
3476
+ return {
3477
+ type: "deny",
3478
+ reason: `Ledger guard: the last ${streak} approved decisions of this kind all ended in observed failures \u2014 denying deterministically until a success is recorded. (${request.question.slice(0, 120)})`
3479
+ };
3480
+ }
3481
+ }
3482
+ return opts.inner.decide(request);
3483
+ }
3484
+ };
3485
+ }
3486
+
3487
+ // src/execution/brain-runtime.ts
3488
+ var FAST_PATH_KEYS = /* @__PURE__ */ new Set(["mode", "maxAutoRisk", "humanTimeoutMs"]);
3489
+ var AUTO_RISK_LEVELS = /* @__PURE__ */ new Set(["off", "low", "medium", "high", "all"]);
3490
+ var COUNCIL_MIN_RISKS = /* @__PURE__ */ new Set(["medium", "high", "critical"]);
3491
+ function normalizeEntry(raw) {
3492
+ const parsed = typeof raw === "string" ? parseModelRef(raw) : raw;
3493
+ const model = parsed.model?.trim();
3494
+ if (!model) {
3495
+ throw new Error(
3496
+ `Invalid model ref: ${typeof raw === "string" ? `"${raw}"` : JSON.stringify(raw)} (expected "model" or "provider/model")`
3497
+ );
3498
+ }
3499
+ const provider = parsed.provider?.trim();
3500
+ return provider ? { provider, model } : { model };
3501
+ }
3502
+ function normalizeVoter(raw) {
3503
+ if (typeof raw === "string") return normalizeEntry(raw);
3504
+ const base = normalizeEntry(raw);
3505
+ const voter = { ...base };
3506
+ if (raw.persona !== void 0) voter.persona = raw.persona;
3507
+ if (raw.weight !== void 0) {
3508
+ if (!Number.isFinite(raw.weight) || raw.weight <= 0) {
3509
+ throw new Error(`Invalid voter weight: ${String(raw.weight)} (must be a positive number)`);
3510
+ }
3511
+ voter.weight = raw.weight;
3512
+ }
3513
+ if (raw.veto !== void 0) voter.veto = raw.veto;
3514
+ return voter;
3515
+ }
3516
+ function requireFraction2(value, label) {
3517
+ if (!Number.isFinite(value) || value <= 0 || value > 1) {
3518
+ throw new Error(`Invalid ${label}: ${String(value)} (must be in (0, 1])`);
3519
+ }
3520
+ return value;
3521
+ }
3522
+ function requirePositiveMs(value, label) {
3523
+ if (!Number.isFinite(value) || value <= 0) {
3524
+ throw new Error(
3525
+ `Invalid ${label}: ${String(value)} (must be a positive number of milliseconds)`
3526
+ );
3527
+ }
3528
+ return Math.round(value);
3529
+ }
3530
+ function compactEntry(entry) {
3531
+ return entry.provider ? `${entry.provider}/${entry.model}` : entry.model;
3532
+ }
3533
+ function compactVoter(voter) {
3534
+ if (voter.persona === void 0 && voter.weight === void 0 && voter.veto === void 0) {
3535
+ return compactEntry(voter);
3536
+ }
3537
+ return { ...voter };
3538
+ }
3539
+ function resolveBrainConfigDefaults(brain, ctx = {}) {
3540
+ const cfg = { ...brain ?? {} };
3541
+ if (cfg.models === void 0 && ctx.fallbackModels && ctx.fallbackModels.length > 0) {
3542
+ cfg.models = [...ctx.fallbackModels];
3543
+ }
3544
+ const seatCount = cfg.council?.voters?.length ?? cfg.models?.length ?? 0;
3545
+ const councilLikely = cfg.council?.enabled ?? seatCount >= 2;
3546
+ if (cfg.mode === void 0) cfg.mode = "headless";
3547
+ if (cfg.maxAutoRisk === void 0) cfg.maxAutoRisk = councilLikely ? "all" : "high";
3548
+ if (cfg.humanTimeoutMs === void 0) cfg.humanTimeoutMs = 12e4;
3549
+ return cfg;
3550
+ }
3551
+ function normalizeInitial(config) {
3552
+ const cfg = { ...config ?? {} };
3553
+ const safe = (items, map) => (items ?? []).flatMap((item) => {
3554
+ try {
3555
+ return [map(item)];
3556
+ } catch {
3557
+ return [];
3558
+ }
3559
+ });
3560
+ if (cfg.models) cfg.models = safe(cfg.models, normalizeEntry);
3561
+ if (cfg.council) {
3562
+ const council = { ...cfg.council };
3563
+ if (council.voters) council.voters = safe(council.voters, normalizeVoter);
3564
+ if (council.judge) {
3565
+ try {
3566
+ council.judge = normalizeEntry(council.judge);
3567
+ } catch {
3568
+ council.judge = void 0;
3569
+ }
3570
+ }
3571
+ cfg.council = council;
3572
+ }
3573
+ return cfg;
3574
+ }
3575
+ function createBrainRuntime(opts) {
3576
+ let cfg = normalizeInitial(opts.initialConfig);
3577
+ let poolLabels = [];
3578
+ let councilLabels = [];
3579
+ let current;
3580
+ const getDecisionDigest = (request) => {
3581
+ const ledger = opts.ledger;
3582
+ if (!ledger?.isEnabled()) return void 0;
3583
+ return ledger.getDecisionDigest?.(request);
3584
+ };
3585
+ function rebuild() {
3586
+ const tiers = assembleBrainTiers({
3587
+ brainConfig: cfg,
3588
+ defaultProviderId: opts.defaultProviderId,
3589
+ sessionProvider: opts.sessionProvider,
3590
+ sessionModel: opts.sessionModel,
3591
+ resolveProvider: opts.resolveProvider,
3592
+ getDecisionDigest
3593
+ });
3594
+ poolLabels = tiers.poolLabels;
3595
+ councilLabels = tiers.councilLabels;
3596
+ const tiered = createTieredBrainArbiter({
3597
+ policy: new DefaultBrainArbiter(),
3598
+ autonomous: tiers.autonomous,
3599
+ getMaxAutoRisk: () => cfg.maxAutoRisk ?? "medium",
3600
+ council: tiers.council,
3601
+ getCouncilMinRisk: tiers.getCouncilMinRisk
3602
+ });
3603
+ const streakFor = opts.ledger?.failureStreakFor;
3604
+ current = streakFor && opts.ledger?.isEnabled() ? createLedgerGuardBrainArbiter({
3605
+ inner: tiered,
3606
+ failureStreakFor: streakFor,
3607
+ denyAfter: cfg.ledger?.autoDenyAfterFailures
3608
+ }) : tiered;
3609
+ }
3610
+ rebuild();
3611
+ function mergePatch(patch) {
3612
+ const next = { ...cfg, council: cfg.council ? { ...cfg.council } : void 0 };
3613
+ let rebuildNeeded = false;
3614
+ const structural = () => {
3615
+ rebuildNeeded = true;
3616
+ };
3617
+ if (patch.mode !== void 0) {
3618
+ if (patch.mode !== "headless" && patch.mode !== "interactive") {
3619
+ throw new Error(`Invalid mode: ${String(patch.mode)}`);
3620
+ }
3621
+ next.mode = patch.mode;
3622
+ }
3623
+ if (patch.maxAutoRisk !== void 0) {
3624
+ if (!AUTO_RISK_LEVELS.has(patch.maxAutoRisk)) {
3625
+ throw new Error(`Invalid maxAutoRisk: ${String(patch.maxAutoRisk)}`);
3626
+ }
3627
+ next.maxAutoRisk = patch.maxAutoRisk;
3628
+ }
3629
+ if (patch.humanTimeoutMs !== void 0) {
3630
+ next.humanTimeoutMs = patch.humanTimeoutMs === null ? void 0 : requirePositiveMs(patch.humanTimeoutMs, "humanTimeoutMs");
3631
+ }
3632
+ if (patch.models !== void 0) {
3633
+ next.models = patch.models === null ? void 0 : patch.models.map(normalizeEntry);
3634
+ structural();
3635
+ }
3636
+ if (patch.strategy !== void 0) {
3637
+ if (patch.strategy !== null && patch.strategy !== "fallback" && patch.strategy !== "round-robin") {
3638
+ throw new Error(`Invalid strategy: ${String(patch.strategy)}`);
3639
+ }
3640
+ next.strategy = patch.strategy ?? void 0;
3641
+ structural();
3642
+ }
3643
+ if (patch.decisionTimeoutMs !== void 0) {
3644
+ next.decisionTimeoutMs = patch.decisionTimeoutMs === null ? void 0 : requirePositiveMs(patch.decisionTimeoutMs, "decisionTimeoutMs");
3645
+ structural();
3646
+ }
3647
+ if (patch.council !== void 0) {
3648
+ if (patch.council === null) {
3649
+ next.council = void 0;
3650
+ } else {
3651
+ const c = { ...next.council ?? {} };
3652
+ const p = patch.council;
3653
+ if (p.enabled !== void 0) c.enabled = p.enabled === null ? void 0 : p.enabled;
3654
+ if (p.minRisk !== void 0) {
3655
+ if (p.minRisk !== null && !COUNCIL_MIN_RISKS.has(p.minRisk)) {
3656
+ throw new Error(`Invalid council minRisk: ${String(p.minRisk)}`);
3657
+ }
3658
+ c.minRisk = p.minRisk ?? void 0;
3659
+ }
3660
+ if (p.voters !== void 0) {
3661
+ c.voters = p.voters === null ? void 0 : p.voters.map(normalizeVoter);
3662
+ }
3663
+ if (p.quorum !== void 0) {
3664
+ c.quorum = p.quorum === null ? void 0 : requireFraction2(p.quorum, "council quorum");
3665
+ }
3666
+ if (p.approval !== void 0) {
3667
+ c.approval = p.approval === null ? void 0 : requireFraction2(p.approval, "council approval");
3668
+ }
3669
+ if (p.judge !== void 0) {
3670
+ c.judge = p.judge === null ? void 0 : normalizeEntry(p.judge);
3671
+ }
3672
+ next.council = c;
3673
+ }
3674
+ structural();
3675
+ }
3676
+ if (patch.ledger !== void 0) {
3677
+ if (patch.ledger === null) {
3678
+ next.ledger = void 0;
3679
+ } else {
3680
+ const l = { ...next.ledger ?? {} };
3681
+ if (patch.ledger.enabled !== void 0) l.enabled = patch.ledger.enabled;
3682
+ if (patch.ledger.autoDenyAfterFailures !== void 0) {
3683
+ const n = patch.ledger.autoDenyAfterFailures;
3684
+ if (n !== null && (!Number.isInteger(n) || n < 0)) {
3685
+ throw new Error(
3686
+ `Invalid autoDenyAfterFailures: ${String(n)} (must be an integer >= 0)`
3687
+ );
3688
+ }
3689
+ l.autoDenyAfterFailures = n ?? void 0;
3690
+ }
3691
+ next.ledger = l;
3692
+ }
3693
+ structural();
3694
+ }
3695
+ for (const key of Object.keys(patch)) {
3696
+ if (!FAST_PATH_KEYS.has(key) && patch[key] !== void 0) {
3697
+ if (!["models", "strategy", "decisionTimeoutMs", "council", "ledger"].includes(key)) {
3698
+ throw new Error(`Unknown brain config field: ${key}`);
3699
+ }
3700
+ }
3701
+ }
3702
+ return { next, rebuildNeeded };
3703
+ }
3704
+ function getSnapshot() {
3705
+ const councilCfg = cfg.council;
3706
+ const voters = (councilCfg?.voters ?? []).map((v) => normalizeVoter(v));
3707
+ return {
3708
+ mode: cfg.mode ?? "interactive",
3709
+ maxAutoRisk: cfg.maxAutoRisk ?? "medium",
3710
+ models: (cfg.models ?? []).map((m) => normalizeEntry(m)),
3711
+ strategy: cfg.strategy ?? "fallback",
3712
+ decisionTimeoutMs: cfg.decisionTimeoutMs,
3713
+ humanTimeoutMs: cfg.humanTimeoutMs,
3714
+ council: {
3715
+ enabled: councilLabels.length > 0,
3716
+ configured: councilCfg?.enabled,
3717
+ minRisk: councilCfg?.minRisk ?? "high",
3718
+ voters,
3719
+ quorum: councilCfg?.quorum,
3720
+ approval: councilCfg?.approval,
3721
+ judge: councilCfg?.judge ? normalizeEntry(councilCfg.judge) : void 0
3722
+ },
3723
+ ledger: {
3724
+ enabled: opts.ledger?.isEnabled() ?? false,
3725
+ autoDenyAfterFailures: cfg.ledger?.autoDenyAfterFailures,
3726
+ path: opts.ledger?.getPath()
3727
+ },
3728
+ poolLabels: [...poolLabels],
3729
+ councilLabels: [...councilLabels],
3730
+ usingSessionModel: (cfg.models ?? []).length === 0
3731
+ };
3732
+ }
3733
+ function getConfig() {
3734
+ const out = {};
3735
+ if (cfg.mode !== void 0) out.mode = cfg.mode;
3736
+ if (cfg.maxAutoRisk !== void 0) out.maxAutoRisk = cfg.maxAutoRisk;
3737
+ if (cfg.models?.length) out.models = cfg.models.map((m) => compactEntry(normalizeEntry(m)));
3738
+ if (cfg.strategy !== void 0) out.strategy = cfg.strategy;
3739
+ if (cfg.decisionTimeoutMs !== void 0) out.decisionTimeoutMs = cfg.decisionTimeoutMs;
3740
+ if (cfg.humanTimeoutMs !== void 0) out.humanTimeoutMs = cfg.humanTimeoutMs;
3741
+ if (cfg.council !== void 0) {
3742
+ const c = cfg.council;
3743
+ const outCouncil = {};
3744
+ if (c.enabled !== void 0) outCouncil.enabled = c.enabled;
3745
+ if (c.minRisk !== void 0) outCouncil.minRisk = c.minRisk;
3746
+ if (c.voters?.length)
3747
+ outCouncil.voters = c.voters.map((v) => compactVoter(normalizeVoter(v)));
3748
+ if (c.quorum !== void 0) outCouncil.quorum = c.quorum;
3749
+ if (c.approval !== void 0) outCouncil.approval = c.approval;
3750
+ if (c.judge !== void 0) outCouncil.judge = compactEntry(normalizeEntry(c.judge));
3751
+ out.council = outCouncil;
3752
+ }
3753
+ if (cfg.ledger !== void 0) out.ledger = { ...cfg.ledger };
3754
+ if (cfg.monitor !== void 0) out.monitor = { ...cfg.monitor };
3755
+ return out;
3756
+ }
3757
+ return {
3758
+ arbiter: {
3759
+ decide: (request) => current.decide(request)
3760
+ },
3761
+ getMode: () => cfg.mode ?? "interactive",
3762
+ getMaxAutoRisk: () => cfg.maxAutoRisk ?? "medium",
3763
+ getHumanTimeoutMs: () => cfg.humanTimeoutMs,
3764
+ getSnapshot,
3765
+ getConfig,
3766
+ apply(patch, applyOpts) {
3767
+ const { next, rebuildNeeded } = mergePatch(patch);
3768
+ cfg = next;
3769
+ if (patch.ledger && patch.ledger.enabled !== void 0) {
3770
+ opts.ledger?.setEnabled(patch.ledger.enabled);
3771
+ }
3772
+ if (rebuildNeeded) rebuild();
3773
+ const snapshot = getSnapshot();
3774
+ const shouldPersist = applyOpts?.persist !== false && opts.persist !== void 0;
3775
+ const persisted = shouldPersist ? opts.persist(getConfig()).then(
3776
+ () => ({ ok: true }),
3777
+ (err) => ({
3778
+ ok: false,
3779
+ error: err instanceof Error ? err.message : String(err)
3780
+ })
3781
+ ) : Promise.resolve({ ok: true });
3782
+ opts.onApplied?.(snapshot);
3783
+ return { snapshot, persisted };
3784
+ }
3785
+ };
3786
+ }
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
2408
4261
  );
2409
- const text = extractText(response).trim();
2410
- if (request.options?.length) {
2411
- const parsed = parseOptionDecision(text, request.options);
2412
- if (parsed) {
2413
- return parsed;
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
+ }
2414
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) {
2415
4353
  return {
2416
- type: "deny",
2417
- reason: "Autonomy Brain returned no exact valid option id."
4354
+ seatId: seat.id,
4355
+ persona: seat.persona,
4356
+ status: "failed",
4357
+ error: errorMessage(error)
2418
4358
  };
2419
4359
  }
2420
- return {
2421
- type: "answer",
2422
- text: text || (request.fallback === "continue" ? "Continue execution." : "Denied by autonomy policy."),
2423
- rationale: text || void 0
2424
- };
2425
- } catch {
2426
- if (request.fallback === "continue") {
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) {
2427
4373
  return {
2428
- type: "answer",
2429
- text: "Continue (Autonomy Brain LLM unavailable \u2014 using fallback)."
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
2430
4389
  };
2431
4390
  }
2432
- return { type: "deny", reason: "Autonomy Brain LLM unavailable for decision." };
2433
- }
2434
- }
2435
- function parseOptionDecision(rawText, options) {
2436
- const text = rawText.trim();
2437
- const byId = new Map(options.map((option2) => [option2.id, option2]));
2438
- const wholeFence = /^```(?:json)?[ \t]*\r?\n([\s\S]*?)\r?\n?```$/i.exec(text);
2439
- const jsonCandidate = wholeFence ? (wholeFence[1] ?? "").trim() : text;
2440
- const parsed = safeParse(jsonCandidate, 16384);
2441
- if (parsed.ok && parsed.value && typeof parsed.value.optionId === "string") {
2442
- const option2 = byId.get(parsed.value.optionId);
2443
- if (!option2) return null;
2444
4391
  return {
2445
- type: "answer",
2446
- optionId: option2.id,
2447
- text: option2.label,
2448
- rationale: typeof parsed.value.rationale === "string" && parsed.value.rationale.trim() ? parsed.value.rationale.trim() : void 0
4392
+ seatId: seat.id,
4393
+ persona: seat.persona,
4394
+ status: "valid",
4395
+ ...parsed.vote,
4396
+ ...metadata
2449
4397
  };
2450
4398
  }
2451
- const legacy = /^\s*\[([^\]\r\n]+)\](?:\s*(?:—|-|:)\s*)?([\s\S]*)$/.exec(text);
2452
- if (!legacy) return null;
2453
- const option = byId.get((legacy[1] ?? "").trim());
2454
- if (!option) return null;
2455
- const rationale = (legacy[2] ?? "").trim();
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;
2456
4691
  return {
2457
- type: "answer",
2458
- optionId: option.id,
2459
- text: option.label,
2460
- rationale: rationale || void 0
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]) } : {}
2461
4705
  };
2462
4706
  }
2463
- function extractText(result) {
2464
- if (!result || typeof result !== "object") return "";
2465
- const r = result;
2466
- if (Array.isArray(r.content)) {
2467
- return r.content.filter((b) => b.type === "text").map((b) => b.text ?? "").join("");
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
+ ];
2468
4741
  }
2469
- if (Array.isArray(r.choices)) {
2470
- return r.choices[0]?.message?.content ?? "";
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.`);
2471
4751
  }
2472
- return typeof r.text === "string" ? r.text : "";
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);
2473
4791
  }
2474
4792
 
2475
4793
  // src/storage/goal-store.ts
@@ -2478,7 +4796,7 @@ var MAX_JOURNAL_ENTRIES = 500;
2478
4796
  function goalFilePath(projectRoot) {
2479
4797
  return resolveWstackPaths({ projectRoot }).projectGoal;
2480
4798
  }
2481
- async function loadGoal(filePath, events) {
4799
+ async function loadGoal(filePath, events, warn) {
2482
4800
  const t0 = Date.now();
2483
4801
  let raw;
2484
4802
  try {
@@ -2509,13 +4827,9 @@ async function loadGoal(filePath, events) {
2509
4827
  try {
2510
4828
  const parsed = JSON.parse(raw);
2511
4829
  if (parsed?.version !== 1 || typeof parsed.goal !== "string" || !Array.isArray(parsed.journal)) {
2512
- console.warn(JSON.stringify({
2513
- level: "warn",
2514
- event: "goal_store.invalid_schema",
2515
- path: filePath,
2516
- message: "invalid schema \u2014 consider deleting and re-creating",
2517
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2518
- }));
4830
+ (warn ?? ((msg) => console.warn(JSON.stringify({ level: "warn", event: "goal_store.invalid_schema", path: filePath, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }))))(
4831
+ "invalid schema \u2014 consider deleting and re-creating"
4832
+ );
2519
4833
  events?.emit("storage.read", {
2520
4834
  sessionId: "~boot~",
2521
4835
  store: "goal",
@@ -2537,13 +4851,9 @@ async function loadGoal(filePath, events) {
2537
4851
  });
2538
4852
  return parsed;
2539
4853
  } catch {
2540
- console.warn(JSON.stringify({
2541
- level: "warn",
2542
- event: "goal_store.parse_failed",
2543
- path: filePath,
2544
- message: "JSON parse failed \u2014 consider deleting and re-creating",
2545
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2546
- }));
4854
+ (warn ?? ((msg) => console.warn(JSON.stringify({ level: "warn", event: "goal_store.parse_failed", path: filePath, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }))))(
4855
+ "JSON parse failed \u2014 consider deleting and re-creating"
4856
+ );
2547
4857
  events?.emit("storage.read", {
2548
4858
  sessionId: "~boot~",
2549
4859
  store: "goal",
@@ -2853,7 +5163,7 @@ function eliseOldToolResults(messages, opts) {
2853
5163
  }
2854
5164
  if (newContent) {
2855
5165
  next ??= messages.slice();
2856
- next[i] = { ...msg, content: newContent };
5166
+ next[i] = { ...msg, content: newContent, _estTokens: void 0 };
2857
5167
  changed = true;
2858
5168
  }
2859
5169
  if (compactionDebugEnabled()) {
@@ -3247,7 +5557,7 @@ function isDesignStack(v) {
3247
5557
 
3248
5558
  // src/execution/design-kit-loader.ts
3249
5559
  import { existsSync } from "node:fs";
3250
- import * as fs2 from "node:fs/promises";
5560
+ import * as fs3 from "node:fs/promises";
3251
5561
  import * as path4 from "node:path";
3252
5562
  import { fileURLToPath as fileURLToPath2 } from "node:url";
3253
5563
  var KIT_FILE = "KIT.md";
@@ -3287,17 +5597,17 @@ function parseKitFrontmatter(raw) {
3287
5597
  else out[key] = items;
3288
5598
  };
3289
5599
  for (const line of lines) {
3290
- const item = /^\s*-\s+(.*)$/.exec(line);
3291
- if (listKey && item) {
3292
- const parsed = parseList(item[1] ?? "");
5600
+ const bulletMatch = /^\s*-\s+(.*)$/.exec(line);
5601
+ if (listKey && bulletMatch) {
5602
+ const parsed = parseList(bulletMatch[1] ?? "");
3293
5603
  if (listKey === "stacks") out.stacks.push(...parsed.filter(isDesignStack));
3294
5604
  else out[listKey].push(...parsed);
3295
5605
  continue;
3296
5606
  }
3297
- const m = /^([a-zA-Z_]+):\s*(.*)$/.exec(line);
3298
- if (!m) continue;
3299
- const key = m[1] ?? "";
3300
- 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();
3301
5611
  if (key === "tags" || key === "stacks" || key === "themes") {
3302
5612
  if (rest) {
3303
5613
  setList(key, parseList(rest));
@@ -3314,24 +5624,23 @@ function parseKitFrontmatter(raw) {
3314
5624
  }
3315
5625
  function narrowStackSections(body, stack) {
3316
5626
  if (!stack) return body;
3317
- const re = /^##\s+Stack:\s*([a-z-]+)\s*$/gim;
3318
- const matches = [];
3319
- let m;
3320
- while ((m = re.exec(body)) !== null) {
3321
- matches.push({ id: (m[1] ?? "").trim(), start: m.index });
3322
- }
3323
- if (matches.length === 0) return body;
3324
- const firstStart = matches[0]?.start ?? body.length;
3325
- let result = body.slice(0, firstStart).trimEnd();
3326
- for (let i = 0; i < matches.length; i++) {
3327
- const cur = matches[i];
3328
- if (!cur) continue;
3329
- const next = matches[i + 1];
3330
- const sectionEnd = next ? next.start : body.length;
3331
- 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) {
3332
5641
  result += `
3333
5642
 
3334
- ${body.slice(cur.start, sectionEnd).trimEnd()}`;
5643
+ ${body.slice(current.start, sectionEnd).trimEnd()}`;
3335
5644
  }
3336
5645
  }
3337
5646
  return `${result}
@@ -3356,7 +5665,7 @@ var DefaultDesignKitLoader = class {
3356
5665
  for (const { dir, source } of this.dirs) {
3357
5666
  let entries;
3358
5667
  try {
3359
- entries = await fs2.readdir(dir, { withFileTypes: true });
5668
+ entries = await fs3.readdir(dir, { withFileTypes: true });
3360
5669
  } catch {
3361
5670
  continue;
3362
5671
  }
@@ -3364,7 +5673,7 @@ var DefaultDesignKitLoader = class {
3364
5673
  if (!e.isDirectory()) continue;
3365
5674
  const kitFile = path4.join(dir, e.name, KIT_FILE);
3366
5675
  try {
3367
- const raw = await fs2.readFile(kitFile, "utf8");
5676
+ const raw = await fs3.readFile(kitFile, "utf8");
3368
5677
  const fm = parseKitFrontmatter(raw);
3369
5678
  const id = fm.id ?? e.name;
3370
5679
  if (!fm.name) continue;
@@ -3425,7 +5734,7 @@ var DefaultDesignKitLoader = class {
3425
5734
  if (cached !== void 0) return cached;
3426
5735
  const m = await this.find(id);
3427
5736
  if (!m) throw new Error(`Design kit "${id}" not found`);
3428
- const raw = await fs2.readFile(m.path, "utf8");
5737
+ const raw = await fs3.readFile(m.path, "utf8");
3429
5738
  const body = narrowStackSections(stripFrontmatter(raw), stack);
3430
5739
  this.bodyCache.set(key, body);
3431
5740
  return body;
@@ -3438,7 +5747,7 @@ var DefaultDesignKitLoader = class {
3438
5747
  if (m) {
3439
5748
  const tokensPath = path4.join(path4.dirname(m.path), TOKENS_FILE);
3440
5749
  try {
3441
- const raw = await fs2.readFile(tokensPath, "utf8");
5750
+ const raw = await fs3.readFile(tokensPath, "utf8");
3442
5751
  const parsed = JSON.parse(raw);
3443
5752
  tokens = parsed;
3444
5753
  } catch {
@@ -3496,7 +5805,7 @@ function _resetDesignKitLoaderMemo() {
3496
5805
 
3497
5806
  // src/execution/design-project-store.ts
3498
5807
  import { existsSync as existsSync2 } from "node:fs";
3499
- import * as fs3 from "node:fs/promises";
5808
+ import * as fs4 from "node:fs/promises";
3500
5809
  import * as path5 from "node:path";
3501
5810
  var DESIGN_DIR = ".design";
3502
5811
  function designProjectDir(projectRoot) {
@@ -3509,7 +5818,7 @@ async function loadProjectDesignRules(projectRoot) {
3509
5818
  let rules;
3510
5819
  for (const name of RULE_FILES) {
3511
5820
  try {
3512
- const txt = await fs3.readFile(path5.join(designProjectDir(projectRoot), name), "utf8");
5821
+ const txt = await fs4.readFile(path5.join(designProjectDir(projectRoot), name), "utf8");
3513
5822
  if (txt.trim()) {
3514
5823
  rules = txt.trim();
3515
5824
  break;
@@ -3530,7 +5839,7 @@ function parseOverrides(value) {
3530
5839
  }
3531
5840
  async function loadActiveKit(projectRoot) {
3532
5841
  try {
3533
- 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");
3534
5843
  const parsed = JSON.parse(raw);
3535
5844
  if (parsed && typeof parsed.kit === "string") {
3536
5845
  return {
@@ -3563,11 +5872,11 @@ function applyTokenOverrides(tokens, overrides) {
3563
5872
  }
3564
5873
  async function ensureDesignDir(projectRoot) {
3565
5874
  const dir = designProjectDir(projectRoot);
3566
- await fs3.mkdir(dir, { recursive: true });
5875
+ await fs4.mkdir(dir, { recursive: true });
3567
5876
  const gi = path5.join(dir, ".gitignore");
3568
5877
  if (!existsSync2(gi)) {
3569
5878
  try {
3570
- await fs3.writeFile(gi, "*\n");
5879
+ await fs4.writeFile(gi, "*\n");
3571
5880
  } catch {
3572
5881
  }
3573
5882
  }
@@ -3578,17 +5887,17 @@ async function recordKitChoice(projectRoot, kit, stack, source, isoTime, overrid
3578
5887
  const dir = await ensureDesignDir(projectRoot);
3579
5888
  const record = { kit, stack: stack ?? null };
3580
5889
  if (overrides && Object.keys(overrides).length > 0) record.overrides = overrides;
3581
- 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)}
3582
5891
  `);
3583
5892
  const line = `- ${isoTime} \xB7 kit=${kit}${stack ? ` stack=${stack}` : ""} \xB7 via=${source}
3584
5893
  `;
3585
- await fs3.appendFile(path5.join(dir, "decisions.md"), line);
5894
+ await fs4.appendFile(path5.join(dir, "decisions.md"), line);
3586
5895
  } catch {
3587
5896
  }
3588
5897
  }
3589
5898
  async function clearPersistedActiveKit(projectRoot) {
3590
5899
  try {
3591
- await fs3.rm(path5.join(designProjectDir(projectRoot), "active.json"), { force: true });
5900
+ await fs4.rm(path5.join(designProjectDir(projectRoot), "active.json"), { force: true });
3592
5901
  } catch {
3593
5902
  }
3594
5903
  }
@@ -3869,10 +6178,10 @@ function makeDesignVerifyToolCallMiddleware() {
3869
6178
  const p = typeof input?.path === "string" ? input.path : "";
3870
6179
  if (!p || !detectFrontendFile(p)) return out;
3871
6180
  const ctx = out.ctx;
3872
- const { default: fs8 } = await import("node:fs/promises");
6181
+ const { default: fs9 } = await import("node:fs/promises");
3873
6182
  const { default: nodePath } = await import("node:path");
3874
6183
  const abs = nodePath.isAbsolute(p) ? p : nodePath.join(ctx.projectRoot, p);
3875
- const text = await fs8.readFile(abs, "utf8").catch(() => "");
6184
+ const text = await fs9.readFile(abs, "utf8").catch(() => "");
3876
6185
  if (!text) return out;
3877
6186
  const loader = getDesignKitLoader(ctx.projectRoot);
3878
6187
  const rawTokens = await loader.readTokens(state.activeKit);
@@ -3934,154 +6243,36 @@ Adhere strictly to its tokens, components, and patterns. Keep light/dark + respo
3934
6243
  text = parts.join("\n");
3935
6244
  }
3936
6245
  const rules = await loadProjectDesignRules(ctx.projectRoot).catch(() => void 0);
3937
- if (rules) {
3938
- text += `
3939
-
3940
- ## Project design rules (.design/rules.md) \u2014 these OVERRIDE kit defaults on conflict
3941
- ${rules}`;
3942
- }
3943
- const block = {
3944
- type: "text",
3945
- text,
3946
- cache_control: { type: "ephemeral" }
3947
- };
3948
- const system = Array.isArray(req.system) ? [...req.system, block] : [block];
3949
- return next({ ...req, system });
3950
- }
3951
- };
3952
- }
3953
- function installDesignStudioMiddleware(deps) {
3954
- if (deps.enabled === false) return;
3955
- const loader = getDesignKitLoader(deps.ctx.projectRoot);
3956
- deps.pipelines.userInput.prepend(makeDesignDetectUserInputMiddleware());
3957
- deps.pipelines.toolCall.prepend(makeDesignDetectToolCallMiddleware());
3958
- deps.pipelines.toolCall.prepend(makeDesignVerifyToolCallMiddleware());
3959
- deps.pipelines.request.prepend(makeDesignStudioRequestMiddleware({ ctx: deps.ctx, loader }));
3960
- void loadActiveKit(deps.ctx.projectRoot).then((persisted) => {
3961
- if (persisted && !getDesignState(deps.ctx)?.activeKit) {
3962
- const stack = persisted.stack && isDesignStack(persisted.stack) ? persisted.stack : void 0;
3963
- setActiveKit(deps.ctx, persisted.kit, stack, persisted.overrides);
3964
- }
3965
- }).catch(() => {
3966
- });
3967
- }
3968
-
3969
- // src/types/provider.ts
3970
- var CONTEXT_OVERFLOW_RE = /context.length|context.window|max.*tokens?.*exceeded|prompt is too long|too long|exceeds the context|\btokens\b.*exceed|context_length_exceeded/i;
3971
- var CONTENT_FILTER_RE = /content.(filter|policy|moderation)|safety (system|filter)/i;
3972
- function classifyProviderError(status, body, message) {
3973
- const type = body?.type;
3974
- if (status === 0) return "network";
3975
- if (status === 408) return "timeout";
3976
- if (status === 599) return "stream_hang";
3977
- if (type === "rate_limit_error" || status === 429) return "rate_limit";
3978
- if (type === "overloaded_error" || status === 529) return "overloaded";
3979
- if (status >= 500) return "server";
3980
- if (type === "authentication_error" || type === "permission_error" || status === 401 || status === 403) {
3981
- return "auth";
3982
- }
3983
- const text = [message, body?.message, type, body?.raw].filter(Boolean).join("\n");
3984
- if (type === "content_filter" || CONTENT_FILTER_RE.test(text)) return "content_filter";
3985
- if (status === 413 || status >= 400 && CONTEXT_OVERFLOW_RE.test(text)) {
3986
- return "context_overflow";
3987
- }
3988
- if (status >= 400) return "invalid_request";
3989
- return "unknown";
3990
- }
3991
- function isRetryableKind(kind) {
3992
- return RETRYABLE_BY_KIND[kind];
3993
- }
3994
- var RETRYABLE_BY_KIND = {
3995
- rate_limit: true,
3996
- overloaded: true,
3997
- server: true,
3998
- timeout: true,
3999
- network: true,
4000
- stream_hang: true,
4001
- auth: false,
4002
- context_overflow: false,
4003
- content_filter: false,
4004
- invalid_request: false,
4005
- unknown: false
4006
- };
4007
- var ProviderError = class extends WrongStackError {
4008
- status;
4009
- retryable;
4010
- providerId;
4011
- /** Canonical failure classification — see {@link ProviderErrorKind}. */
4012
- kind;
4013
- body;
4014
- constructor(message, status, retryable, providerId, opts = {}) {
4015
- const kind = opts.kind ?? classifyProviderError(status, opts.body, message);
4016
- super({
4017
- message,
4018
- code: kindToCode(kind),
4019
- subsystem: "provider",
4020
- severity: status >= 500 ? "error" : "warning",
4021
- recoverable: retryable,
4022
- context: { providerId, status },
4023
- cause: opts.cause
4024
- });
4025
- this.name = "ProviderError";
4026
- this.status = status;
4027
- this.retryable = retryable;
4028
- this.providerId = providerId;
4029
- this.kind = kind;
4030
- this.body = opts.body;
4031
- }
4032
- /**
4033
- * Render a one-line, user-facing description. Designed for the CLI/TUI
4034
- * status line and the agent's retry warning. Avoids dumping raw JSON
4035
- * (which is what users see today when a 529 lands and the log message
4036
- * includes the full `{"type":"error",...}` body).
4037
- *
4038
- * Examples:
4039
- * "minimax-coding-plan overloaded (529): High traffic detected. Upgrade for highspeed model. [req 06534785201de9c0…]"
4040
- * "openai rate limited (429): Retry after 12s"
4041
- * "anthropic invalid request (400): messages.0.role must be one of 'user'|'assistant'"
4042
- * "groq HTTP 500 (server error)"
4043
- */
4044
- describe() {
4045
- const kind = describeStatus(this.status, this.body?.type);
4046
- const head = `${this.providerId} ${kind}`;
4047
- const detail = this.body?.message?.trim();
4048
- const reqId = this.body?.requestId ? ` [req ${this.body.requestId.slice(0, 16)}${this.body.requestId.length > 16 ? "\u2026" : ""}]` : "";
4049
- if (detail && detail.length > 0) {
4050
- return `${head}: ${truncate(detail, 240)}${reqId}`;
6246
+ if (rules) {
6247
+ text += `
6248
+
6249
+ ## Project design rules (.design/rules.md) \u2014 these OVERRIDE kit defaults on conflict
6250
+ ${rules}`;
6251
+ }
6252
+ const block = {
6253
+ type: "text",
6254
+ text,
6255
+ cache_control: { type: "ephemeral" }
6256
+ };
6257
+ const system = Array.isArray(req.system) ? [...req.system, block] : [block];
6258
+ return next({ ...req, system });
4051
6259
  }
4052
- return `${head}${reqId}`;
4053
- }
4054
- };
4055
- function describeStatus(status, type) {
4056
- if (status === 0) return "network error";
4057
- if (status === 599) return `stream hang (${status})`;
4058
- if (type === "overloaded_error" || status === 529) return `overloaded (${status})`;
4059
- if (type === "rate_limit_error" || status === 429) return `rate limited (${status})`;
4060
- if (type === "authentication_error" || status === 401) return `auth failed (${status})`;
4061
- if (type === "permission_error" || status === 403) return `forbidden (${status})`;
4062
- if (type === "not_found_error" || status === 404) return `not found (${status})`;
4063
- if (type === "content_filter") return `content filtered (${status})`;
4064
- if (type === "invalid_request_error" || status === 400) return `invalid request (${status})`;
4065
- if (status === 408) return `timeout (${status})`;
4066
- if (status >= 500 && status < 600) return `HTTP ${status} (server error)`;
4067
- if (type) return `${type} (${status})`;
4068
- return `HTTP ${status}`;
6260
+ };
4069
6261
  }
4070
- var KIND_TO_CODE = {
4071
- network: ERROR_CODES.PROVIDER_NETWORK_ERROR,
4072
- timeout: ERROR_CODES.PROVIDER_NETWORK_ERROR,
4073
- rate_limit: ERROR_CODES.PROVIDER_RATE_LIMITED,
4074
- auth: ERROR_CODES.PROVIDER_AUTH_FAILED,
4075
- overloaded: ERROR_CODES.PROVIDER_OVERLOADED,
4076
- context_overflow: ERROR_CODES.PROVIDER_CONTEXT_OVERFLOW,
4077
- server: ERROR_CODES.PROVIDER_SERVER_ERROR,
4078
- stream_hang: ERROR_CODES.PROVIDER_SERVER_ERROR,
4079
- content_filter: ERROR_CODES.PROVIDER_INVALID_REQUEST,
4080
- invalid_request: ERROR_CODES.PROVIDER_INVALID_REQUEST,
4081
- unknown: ERROR_CODES.PROVIDER_INVALID_REQUEST
4082
- };
4083
- function kindToCode(kind) {
4084
- return KIND_TO_CODE[kind];
6262
+ function installDesignStudioMiddleware(deps) {
6263
+ if (deps.enabled === false) return;
6264
+ const loader = getDesignKitLoader(deps.ctx.projectRoot);
6265
+ deps.pipelines.userInput.prepend(makeDesignDetectUserInputMiddleware());
6266
+ deps.pipelines.toolCall.prepend(makeDesignDetectToolCallMiddleware());
6267
+ deps.pipelines.toolCall.prepend(makeDesignVerifyToolCallMiddleware());
6268
+ deps.pipelines.request.prepend(makeDesignStudioRequestMiddleware({ ctx: deps.ctx, loader }));
6269
+ void loadActiveKit(deps.ctx.projectRoot).then((persisted) => {
6270
+ if (persisted && !getDesignState(deps.ctx)?.activeKit) {
6271
+ const stack = persisted.stack && isDesignStack(persisted.stack) ? persisted.stack : void 0;
6272
+ setActiveKit(deps.ctx, persisted.kit, stack, persisted.overrides);
6273
+ }
6274
+ }).catch(() => {
6275
+ });
4085
6276
  }
4086
6277
 
4087
6278
  // src/execution/regex-patterns.ts
@@ -4110,11 +6301,25 @@ function buildRecoveryStrategies(opts) {
4110
6301
  },
4111
6302
  {
4112
6303
  label: "rate_limit_backoff",
4113
- async attempt(err) {
6304
+ async attempt(err, ctx) {
4114
6305
  if (!(err instanceof ProviderError) || err.kind !== "rate_limit") return null;
4115
6306
  const delayMs = err.body?.retryAfterMs ?? 5e3;
4116
6307
  const delay = Math.min(6e4, Math.max(1e3, delayMs));
4117
- 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
+ }
4118
6323
  return { action: "retry", reason: "rate_limit_backoff" };
4119
6324
  }
4120
6325
  },
@@ -4271,22 +6476,40 @@ var EternalAutonomyEngine = class {
4271
6476
  currentCtrl = null;
4272
6477
  iterationsSinceCompact = 0;
4273
6478
  goalPath;
6479
+ /**
6480
+ * Emit a structured error. Uses the configured Logger when available;
6481
+ * falls back to console.error(JSON) so events are never silently dropped.
6482
+ */
6483
+ logError(msg, ctx) {
6484
+ if (this.opts.logger) {
6485
+ this.opts.logger.error(msg, ctx);
6486
+ } else {
6487
+ console.error(JSON.stringify({ ...ctx, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
6488
+ }
6489
+ }
4274
6490
  /** Current engine state — readable for UIs. */
4275
6491
  get currentState() {
4276
6492
  return this.state;
4277
6493
  }
6494
+ /**
6495
+ * Load the goal file with structured logging for parse/schema warnings.
6496
+ */
6497
+ loadGoal() {
6498
+ return loadGoal(
6499
+ this.goalPath,
6500
+ this.opts.events,
6501
+ this.opts.logger ? (msg) => this.opts.logger.warn(msg) : void 0
6502
+ );
6503
+ }
4278
6504
  /** Synchronously request stop. Resolves once the running iteration aborts. */
4279
6505
  stop() {
4280
6506
  this.stopRequested = true;
4281
6507
  this.currentCtrl?.abort();
4282
6508
  void this.persistEngineState("stopped").catch((err) => {
4283
- console.error(JSON.stringify({
4284
- level: "error",
4285
- event: "engine.persist_state_failed",
4286
- message: toErrorMessage(err),
4287
- context: { expectedState: "stopped" },
4288
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
4289
- }));
6509
+ this.logError(
6510
+ "Engine persist state failed",
6511
+ { event: "engine.persist_state_failed", message: toErrorMessage(err), context: { expectedState: "stopped" } }
6512
+ );
4290
6513
  });
4291
6514
  this.state = "stopped";
4292
6515
  }
@@ -4342,7 +6565,7 @@ var EternalAutonomyEngine = class {
4342
6565
  const emit = (stage) => {
4343
6566
  this.opts.onStage?.(stage);
4344
6567
  };
4345
- const goal = await loadGoal(this.goalPath, this.opts.events);
6568
+ const goal = await this.loadGoal();
4346
6569
  if (!goal) {
4347
6570
  emit({ phase: "stopped" });
4348
6571
  this.stopRequested = true;
@@ -4446,16 +6669,13 @@ var EternalAutonomyEngine = class {
4446
6669
  emit({ phase: "reflect", status, note });
4447
6670
  let iterationIndex = 0;
4448
6671
  try {
4449
- const reloaded = await loadGoal(this.goalPath, this.opts.events);
6672
+ const reloaded = await this.loadGoal();
4450
6673
  iterationIndex = reloaded?.iterations ?? 0;
4451
6674
  } catch (err) {
4452
- console.error(JSON.stringify({
4453
- level: "warn",
4454
- event: "autonomy.goal_reload_failed",
4455
- message: toErrorMessage(err),
4456
- context: { goalPath: this.goalPath },
4457
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
4458
- }));
6675
+ this.logError(
6676
+ "Goal reload failed",
6677
+ { event: "autonomy.goal_reload_failed", message: toErrorMessage(err), context: { goalPath: this.goalPath } }
6678
+ );
4459
6679
  }
4460
6680
  this.opts.onIteration?.({
4461
6681
  at: (this.opts.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
@@ -4691,13 +6911,10 @@ ${lastFew}` : "No prior iterations yet.",
4691
6911
  clearTimeout(timer);
4692
6912
  }
4693
6913
  } catch (err) {
4694
- console.error(JSON.stringify({
4695
- level: "warn",
4696
- event: "autonomy.brainstorm_failed",
4697
- message: toErrorMessage(err),
4698
- context: { goal: goal.goal.slice(0, 100) },
4699
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
4700
- }));
6914
+ this.logError(
6915
+ "Brainstorm failed",
6916
+ { event: "autonomy.brainstorm_failed", message: toErrorMessage(err), context: { goal: goal.goal.slice(0, 100) } }
6917
+ );
4701
6918
  return null;
4702
6919
  }
4703
6920
  }
@@ -4780,7 +6997,7 @@ ${recentJournal}` : "No prior iterations.",
4780
6997
  }
4781
6998
  }
4782
6999
  async appendIterationEntry(entry) {
4783
- const current = await loadGoal(this.goalPath, this.opts.events);
7000
+ const current = await this.loadGoal();
4784
7001
  if (!current) {
4785
7002
  return;
4786
7003
  }
@@ -4794,7 +7011,7 @@ ${recentJournal}` : "No prior iterations.",
4794
7011
  * the counter to rotate past stuck todos once they cross `todoMaxAttempts`.
4795
7012
  */
4796
7013
  async bumpTodoAttempt(todoId) {
4797
- const current = await loadGoal(this.goalPath, this.opts.events);
7014
+ const current = await this.loadGoal();
4798
7015
  if (!current) return;
4799
7016
  const attempts = { ...current.todoAttempts ?? {} };
4800
7017
  attempts[todoId] = (attempts[todoId] ?? 0) + 1;
@@ -4808,7 +7025,7 @@ ${recentJournal}` : "No prior iterations.",
4808
7025
  * goal is already `completed`.
4809
7026
  */
4810
7027
  async markGoalCompleted(action, note) {
4811
- const current = await loadGoal(this.goalPath, this.opts.events);
7028
+ const current = await this.loadGoal();
4812
7029
  if (!current) return;
4813
7030
  if (current.goalState === "completed") return;
4814
7031
  const withFlag = { ...current, goalState: "completed" };
@@ -4819,6 +7036,11 @@ ${recentJournal}` : "No prior iterations.",
4819
7036
  note: note.slice(0, 240)
4820
7037
  });
4821
7038
  await saveGoal(this.goalPath, withEntry, this.opts.events);
7039
+ try {
7040
+ const { unlink: unlink4 } = await import("node:fs/promises");
7041
+ await unlink4(this.goalPath);
7042
+ } catch {
7043
+ }
4822
7044
  this.opts.onEternalStop?.();
4823
7045
  }
4824
7046
  /**
@@ -4827,7 +7049,7 @@ ${recentJournal}` : "No prior iterations.",
4827
7049
  * `onEternalStop` so the REPL returns to normal mode.
4828
7050
  */
4829
7051
  async clearGoalManually(note) {
4830
- const current = await loadGoal(this.goalPath, this.opts.events);
7052
+ const current = await this.loadGoal();
4831
7053
  if (current) {
4832
7054
  const abandoned = { ...current, goalState: "abandoned" };
4833
7055
  await saveGoal(this.goalPath, abandoned, this.opts.events);
@@ -4875,7 +7097,23 @@ Recent work:
4875
7097
  ${recentJournal}` : ""
4876
7098
  ].join("\n"),
4877
7099
  risk: "high",
4878
- fallback: "continue"
7100
+ fallback: "continue",
7101
+ // Structured verdict — completion is control-plane input, decided by
7102
+ // exact option id rather than sniffing prose for "done"/"complete".
7103
+ options: [
7104
+ {
7105
+ id: "goal_complete",
7106
+ label: "The goal is complete \u2014 stop the eternal run",
7107
+ consequence: "The engine stops and the goal file is archived.",
7108
+ risk: "high"
7109
+ },
7110
+ {
7111
+ id: "keep_working",
7112
+ label: "Not complete yet \u2014 keep working",
7113
+ consequence: "The engine continues iterating on the goal.",
7114
+ risk: "low"
7115
+ }
7116
+ ]
4879
7117
  });
4880
7118
  const summary = formatDecisionSummary(decision, {
4881
7119
  id: `goal-done-${goal.iterations}`,
@@ -4895,8 +7133,7 @@ ${recentJournal}` : ""
4895
7133
  return false;
4896
7134
  }
4897
7135
  if (decision.type === "answer") {
4898
- const text = decision.text.toLowerCase();
4899
- return text.includes("complete") || text.includes("done") || text.includes("yes");
7136
+ return decision.optionId === "goal_complete";
4900
7137
  }
4901
7138
  return true;
4902
7139
  } catch {
@@ -4907,13 +7144,13 @@ ${recentJournal}` : ""
4907
7144
  * Persist a progress update from the agent's [PROGRESS: N%] output.
4908
7145
  */
4909
7146
  async updateProgress(progress, note) {
4910
- const current = await loadGoal(this.goalPath, this.opts.events);
7147
+ const current = await this.loadGoal();
4911
7148
  if (!current) return;
4912
7149
  const updated = recordProgress(current, progress, note);
4913
7150
  await saveGoal(this.goalPath, updated, this.opts.events);
4914
7151
  }
4915
7152
  async persistEngineState(state) {
4916
- const current = await loadGoal(this.goalPath, this.opts.events);
7153
+ const current = await this.loadGoal();
4917
7154
  if (!current) return;
4918
7155
  if (current.engineState === state) return;
4919
7156
  await saveGoal(this.goalPath, { ...current, engineState: state }, this.opts.events);
@@ -4948,16 +7185,18 @@ var IntelligentCompactor = class {
4948
7185
  eliseThreshold;
4949
7186
  summarizerPrompt;
4950
7187
  summarizerModel;
7188
+ oneShotOrchestrator;
4951
7189
  constructor(opts) {
4952
7190
  this.provider = opts.provider;
4953
- this.warnThreshold = opts.warnThreshold ?? 0.6;
4954
- this.softThreshold = opts.softThreshold ?? 0.75;
4955
- 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;
4956
7194
  this.maxContext = opts.maxContext ?? 128e3;
4957
7195
  this.preserveK = opts.preserveK ?? 4;
4958
- this.eliseThreshold = opts.eliseThreshold ?? 500;
7196
+ this.eliseThreshold = opts.eliseThreshold ?? 300;
4959
7197
  this.summarizerPrompt = opts.summarizerPrompt ?? readBundledInstructionText("llm/intelligent-compactor-summarizer.md");
4960
7198
  this.summarizerModel = opts.summarizerModel;
7199
+ this.oneShotOrchestrator = opts.oneShotOrchestrator;
4961
7200
  }
4962
7201
  async compact(ctx, opts = {}) {
4963
7202
  const beforeTokens = estimateMessages(ctx.messages);
@@ -4970,7 +7209,7 @@ var IntelligentCompactor = class {
4970
7209
  let collapsedDigest;
4971
7210
  if (aggressive) {
4972
7211
  const phase2 = await this.summarizeAncientTurns(ctx);
4973
- if (phase2.saved > 0) reductions.push({ phase: "summary", saved: phase2.saved });
7212
+ reductions.push({ phase: "summary", saved: Math.max(0, phase2.saved) });
4974
7213
  collapsedDigest = phase2.digest;
4975
7214
  } else if (load >= this.warnThreshold) {
4976
7215
  const saved2 = this.elide(ctx);
@@ -5024,9 +7263,15 @@ var IntelligentCompactor = class {
5024
7263
  } catch {
5025
7264
  summaryText = buildLosslessDigest(toSummarize) || `${toSummarize.length} earlier turns (semantic content preserved)`;
5026
7265
  }
7266
+ const smartDigest = buildSmartDigest(toSummarize);
7267
+ const enriched = smartDigest ? `[CRITICAL]
7268
+ ${smartDigest}
7269
+
7270
+ [SUMMARY]
7271
+ ${summaryText}` : summaryText;
5027
7272
  const summaryMsg = {
5028
7273
  role: "system",
5029
- content: `[prior_turns_summary: ${summaryText}]`
7274
+ content: `[prior_turns_summary: ${enriched}]`
5030
7275
  };
5031
7276
  const summaryTokens = estimateMessages([summaryMsg]);
5032
7277
  const tail = ctx.messages.slice(boundary);
@@ -5034,27 +7279,41 @@ var IntelligentCompactor = class {
5034
7279
  return { saved: Math.max(0, removedTokens - summaryTokens), digest: summaryText };
5035
7280
  }
5036
7281
  async callSummarizer(messages, ctx) {
5037
- const prompt = [
5038
- { type: "text", text: this.summarizerPrompt },
5039
- { type: "text", text: "\n\nConversation to summarize:\n" },
5040
- ...this.messagesToText(messages)
5041
- ];
5042
- const req = {
5043
- model: this.summarizerModel ?? ctx.model,
5044
- system: prompt,
5045
- messages: [],
5046
- maxTokens: 1024
5047
- };
5048
- const ac = ctx.signal ? void 0 : new AbortController();
5049
- const signal = ctx.signal ?? ac?.signal;
5050
- let res;
5051
- try {
5052
- res = await this.provider.complete(req, { signal });
5053
- } finally {
5054
- 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)";
5055
7315
  }
5056
- const textBlocks = res.content.filter(isTextBlock);
5057
- return textBlocks.map((b) => b.text).join("\n").trim() || "(empty summary)";
7316
+ return "(empty summary)";
5058
7317
  }
5059
7318
  messagesToText(messages) {
5060
7319
  const lines = [];
@@ -5071,6 +7330,22 @@ var IntelligentCompactor = class {
5071
7330
  }
5072
7331
  return [{ type: "text", text: lines.join("\n") }];
5073
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
+ }
5074
7349
  };
5075
7350
 
5076
7351
  // src/execution/model-runtime.ts
@@ -5751,15 +8026,17 @@ var MAX_FILES = 64;
5751
8026
  var MAX_NEXT_STEPS = 12;
5752
8027
  var MAX_ITEM_CHARS = 500;
5753
8028
  function normalizeSubagentStructuredReport(value) {
5754
- if (!isRecord3(value)) return void 0;
8029
+ if (!isRecord4(value)) return void 0;
5755
8030
  const summary = nonEmptyString(value["summary"]);
5756
8031
  const findings = stringArray(value["findings"]);
5757
8032
  const filesExamined = stringArray(value["files_examined"]);
5758
8033
  const nextSteps = stringArray(value["suggested_next_steps"]);
5759
8034
  const confidence = value["confidence"];
8035
+ const completion = value["completion"];
8036
+ const remainingWork = optionalString2(value["remaining_work"]);
5760
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(
5761
8038
  (item) => item.trim().length > MAX_ITEM_CHARS
5762
- ) || typeof confidence !== "number" || !Number.isFinite(confidence) || confidence < 0 || confidence > 1) {
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) {
5763
8040
  return void 0;
5764
8041
  }
5765
8042
  const report = {
@@ -5767,19 +8044,24 @@ function normalizeSubagentStructuredReport(value) {
5767
8044
  findings: findings.map((item) => item.trim()).filter(Boolean),
5768
8045
  files_examined: filesExamined.map((item) => item.trim()).filter(Boolean),
5769
8046
  confidence,
5770
- suggested_next_steps: nextSteps.map((item) => item.trim()).filter(Boolean)
8047
+ suggested_next_steps: nextSteps.map((item) => item.trim()).filter(Boolean),
8048
+ ...completion === "complete" || completion === "partial" ? { completion } : {},
8049
+ ...remainingWork ? { remaining_work: remainingWork } : {}
5771
8050
  };
5772
8051
  return JSON.stringify(report).length <= MAX_SUBAGENT_STRUCTURED_REPORT_CHARS ? report : void 0;
5773
8052
  }
5774
8053
  function readSubagentStructuredReport(ctx) {
5775
8054
  return normalizeSubagentStructuredReport(ctx?.meta?.[SUBAGENT_STRUCTURED_REPORT_META_KEY]);
5776
8055
  }
5777
- function isRecord3(value) {
8056
+ function isRecord4(value) {
5778
8057
  return typeof value === "object" && value !== null && !Array.isArray(value);
5779
8058
  }
5780
8059
  function nonEmptyString(value) {
5781
8060
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
5782
8061
  }
8062
+ function optionalString2(value) {
8063
+ return value === void 0 ? void 0 : nonEmptyString(value);
8064
+ }
5783
8065
  function stringArray(value) {
5784
8066
  return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : void 0;
5785
8067
  }
@@ -6062,7 +8344,7 @@ var TOOLS = {
6062
8344
  };
6063
8345
 
6064
8346
  // src/coordination/agents/agent-prompts.ts
6065
- import { readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
8347
+ import { readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
6066
8348
  import * as os2 from "node:os";
6067
8349
  import * as path6 from "node:path";
6068
8350
  import { fileURLToPath as fileURLToPath3 } from "node:url";
@@ -6077,7 +8359,7 @@ function agentPrompt(id) {
6077
8359
  let resolved = "";
6078
8360
  for (const dir of agentPromptDirCandidates(envDir)) {
6079
8361
  try {
6080
- resolved = readFileSync2(path6.join(dir, fileName), "utf8").trimEnd();
8362
+ resolved = readFileSync3(path6.join(dir, fileName), "utf8").trimEnd();
6081
8363
  break;
6082
8364
  } catch {
6083
8365
  }
@@ -6107,7 +8389,7 @@ function agentPromptDirCandidates(envDir) {
6107
8389
  }
6108
8390
  function isDirectory2(candidate) {
6109
8391
  try {
6110
- return statSync2(candidate).isDirectory();
8392
+ return statSync3(candidate).isDirectory();
6111
8393
  } catch {
6112
8394
  return false;
6113
8395
  }
@@ -7769,76 +10051,45 @@ function providerErrorToSubagentError(err, message, cause) {
7769
10051
  }
7770
10052
 
7771
10053
  // src/coordination/fleet.ts
7772
- var AUDIT_LOG_AGENT = {
7773
- id: "audit-log",
7774
- name: "Audit Log",
7775
- role: "audit-log",
7776
- prompt: agentPrompt("audit-log")
7777
- // No hardcoded budgets — the orchestrator (delegate tool or
7778
- // spawn_subagent) decides per-task how much room a subagent gets.
7779
- // A monorepo audit needs hours; a single-file lint check needs
7780
- // seconds. Pinning a number here forces the orchestrator to fight
7781
- // the role's default instead of just asking for what it needs.
7782
- };
7783
- var BUG_HUNTER_AGENT = {
7784
- id: "bug-hunter",
7785
- name: "Bug Hunter",
7786
- role: "bug-hunter",
7787
- prompt: agentPrompt("bug-hunter")
7788
- // Budgets are set by the orchestrator per task — see fleet.ts header.
7789
- };
7790
- var REFACTOR_PLANNER_AGENT = {
7791
- id: "refactor-planner",
7792
- name: "Refactor Planner",
7793
- role: "refactor-planner",
7794
- prompt: agentPrompt("refactor-planner")
7795
- // Budgets are set by the orchestrator per task — see fleet.ts header.
7796
- };
7797
- var SECURITY_SCANNER_AGENT = {
7798
- id: "security-scanner",
7799
- name: "Security Scanner",
7800
- role: "security-scanner",
7801
- prompt: agentPrompt("security-scanner")
7802
- // Budgets are set by the orchestrator per task — see fleet.ts header.
7803
- };
7804
- var SHADOW_AGENT = {
7805
- id: "shadow-agent",
7806
- name: "Shadow",
7807
- role: "shadow-agent",
7808
- prompt: agentPrompt("shadow-agent")
7809
- // Budgets are set by the orchestrator per task — see fleet.ts header.
7810
- };
7811
- var CRITIC_AGENT = {
7812
- id: "critic",
7813
- name: "Critic",
7814
- role: "critic",
7815
- prompt: agentPrompt("critic")
7816
- // Budgets are set by the orchestrator per task — see fleet.ts header.
7817
- };
10054
+ function defineAgent(id, name, promptRole = id, provider) {
10055
+ return {
10056
+ id,
10057
+ name,
10058
+ role: id,
10059
+ prompt: agentPrompt(promptRole),
10060
+ ...provider === void 0 ? {} : { provider }
10061
+ };
10062
+ }
10063
+ var AUDIT_LOG_AGENT = defineAgent("audit-log", "Audit Log");
10064
+ var BUG_HUNTER_AGENT = defineAgent("bug-hunter", "Bug Hunter");
10065
+ var REFACTOR_PLANNER_AGENT = defineAgent("refactor-planner", "Refactor Planner");
10066
+ var SECURITY_SCANNER_AGENT = defineAgent("security-scanner", "Security Scanner");
10067
+ var SHADOW_AGENT = defineAgent("shadow-agent", "Shadow");
10068
+ var CRITIC_AGENT = defineAgent("critic", "Critic");
7818
10069
  var FLEET_ROSTER = {
7819
10070
  "audit-log": AUDIT_LOG_AGENT,
7820
10071
  "bug-hunter": BUG_HUNTER_AGENT,
7821
10072
  "refactor-planner": REFACTOR_PLANNER_AGENT,
7822
10073
  "security-scanner": SECURITY_SCANNER_AGENT,
7823
- "critic": CRITIC_AGENT,
10074
+ critic: CRITIC_AGENT,
7824
10075
  "shadow-agent": SHADOW_AGENT,
7825
10076
  ...Object.fromEntries(
7826
10077
  ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
7827
10078
  )
7828
10079
  };
7829
- var DEFAULT_IDLE_TIMEOUT_MS = 15 * 60 * 1e3;
10080
+ var DEFAULT_IDLE_TIMEOUT_MS = 10 * 60 * 1e3;
7830
10081
  var FLEET_ROSTER_BUDGETS = {
7831
- "audit-log": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 5e3, maxToolCalls: 15e3 },
7832
- "bug-hunter": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
7833
- "refactor-planner": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 6e3, maxToolCalls: 18e3 },
7834
- "security-scanner": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
7835
- "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 },
7836
10087
  "shadow-agent": {
7837
10088
  idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
7838
- maxIterations: 2e3,
7839
- maxToolCalls: 5e3,
7840
- maxTokens: 6e4,
7841
- maxCostUsd: 1
10089
+ maxIterations: 1e3,
10090
+ maxToolCalls: 2500,
10091
+ maxTokens: 3e4,
10092
+ maxCostUsd: 0.5
7842
10093
  },
7843
10094
  ...Object.fromEntries(
7844
10095
  ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.budget])
@@ -7846,8 +10097,8 @@ var FLEET_ROSTER_BUDGETS = {
7846
10097
  };
7847
10098
  var GENERIC_SUBAGENT_BUDGET = {
7848
10099
  idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
7849
- maxIterations: 5e3,
7850
- maxToolCalls: 15e3
10100
+ maxIterations: 2500,
10101
+ maxToolCalls: 7500
7851
10102
  };
7852
10103
  function applyRosterBudget(cfg) {
7853
10104
  const roleBudget = cfg.role ? FLEET_ROSTER_BUDGETS[cfg.role] : void 0;
@@ -7869,41 +10120,11 @@ function applyRosterBudget(cfg) {
7869
10120
  };
7870
10121
  }
7871
10122
  var ALL_FLEET_AGENTS = Object.values(FLEET_ROSTER);
7872
- var CLINE_AGENT = {
7873
- id: "cline",
7874
- name: "Cline",
7875
- role: "cline",
7876
- prompt: agentPrompt("acp-cline"),
7877
- provider: "acp"
7878
- };
7879
- var GEMINI_CLI_AGENT = {
7880
- id: "gemini-cli",
7881
- name: "Gemini CLI",
7882
- role: "gemini-cli",
7883
- prompt: agentPrompt("acp-gemini-cli"),
7884
- provider: "acp"
7885
- };
7886
- var COPILOT_AGENT = {
7887
- id: "copilot",
7888
- name: "GitHub Copilot",
7889
- role: "copilot",
7890
- prompt: agentPrompt("acp-copilot"),
7891
- provider: "acp"
7892
- };
7893
- var OPENHANDS_AGENT = {
7894
- id: "openhands",
7895
- name: "OpenHands",
7896
- role: "openhands",
7897
- prompt: agentPrompt("acp-openhands"),
7898
- provider: "acp"
7899
- };
7900
- var GOOSE_AGENT = {
7901
- id: "goose",
7902
- name: "Goose",
7903
- role: "goose",
7904
- prompt: agentPrompt("acp-goose"),
7905
- provider: "acp"
7906
- };
10123
+ var CLINE_AGENT = defineAgent("cline", "Cline", "acp-cline", "acp");
10124
+ var GEMINI_CLI_AGENT = defineAgent("gemini-cli", "Gemini CLI", "acp-gemini-cli", "acp");
10125
+ var COPILOT_AGENT = defineAgent("copilot", "GitHub Copilot", "acp-copilot", "acp");
10126
+ var OPENHANDS_AGENT = defineAgent("openhands", "OpenHands", "acp-openhands", "acp");
10127
+ var GOOSE_AGENT = defineAgent("goose", "Goose", "acp-goose", "acp");
7907
10128
  var ACP_AGENTS = [
7908
10129
  CLINE_AGENT,
7909
10130
  GEMINI_CLI_AGENT,
@@ -7911,11 +10132,14 @@ var ACP_AGENTS = [
7911
10132
  OPENHANDS_AGENT,
7912
10133
  GOOSE_AGENT
7913
10134
  ];
7914
- FLEET_ROSTER_BUDGETS["cline"] = { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 };
7915
- FLEET_ROSTER_BUDGETS["gemini-cli"] = { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 };
7916
- FLEET_ROSTER_BUDGETS["copilot"] = { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 };
7917
- FLEET_ROSTER_BUDGETS["openhands"] = { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 };
7918
- FLEET_ROSTER_BUDGETS["goose"] = { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 };
10135
+ var ACP_AGENT_BUDGET = {
10136
+ timeoutMs: 10 * 60 * 60 * 1e3,
10137
+ maxIterations: 8e3,
10138
+ maxToolCalls: 2e4
10139
+ };
10140
+ for (const agent of ACP_AGENTS) {
10141
+ FLEET_ROSTER_BUDGETS[agent.role] = { ...ACP_AGENT_BUDGET };
10142
+ }
7919
10143
  var FLEET_ROSTER_WITHACP = {
7920
10144
  ...FLEET_ROSTER,
7921
10145
  ...Object.fromEntries(ACP_AGENTS.map((a) => [a.role, a]))
@@ -8909,6 +11133,27 @@ var ParallelEternalEngine = class {
8909
11133
  agentFactory;
8910
11134
  dispatchEnabled;
8911
11135
  dispatchClassifier;
11136
+ /**
11137
+ * Emit a structured error. Uses the configured Logger when available;
11138
+ * falls back to console.error(JSON) so events are never silently dropped.
11139
+ */
11140
+ logError(msg, ctx) {
11141
+ if (this.opts.logger) {
11142
+ this.opts.logger.error(msg, ctx);
11143
+ } else {
11144
+ console.error(JSON.stringify({ ...ctx, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
11145
+ }
11146
+ }
11147
+ /**
11148
+ * Load the goal file with structured logging for parse/schema warnings.
11149
+ */
11150
+ loadGoal() {
11151
+ return loadGoal(
11152
+ this.goalPath,
11153
+ this.opts.events,
11154
+ this.opts.logger ? (msg) => this.opts.logger.warn(msg) : void 0
11155
+ );
11156
+ }
8912
11157
  get currentState() {
8913
11158
  return this.state;
8914
11159
  }
@@ -8921,13 +11166,10 @@ var ParallelEternalEngine = class {
8921
11166
  stop() {
8922
11167
  this.stopRequested = true;
8923
11168
  void this.persistState("stopped").catch((err) => {
8924
- console.error(JSON.stringify({
8925
- level: "error",
8926
- event: "engine.persist_state_failed",
8927
- message: toErrorMessage(err),
8928
- context: { expectedState: "stopped" },
8929
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
8930
- }));
11169
+ this.logError(
11170
+ "Engine persist state failed",
11171
+ { event: "engine.persist_state_failed", message: toErrorMessage(err), context: { expectedState: "stopped" } }
11172
+ );
8931
11173
  });
8932
11174
  this.state = "stopped";
8933
11175
  }
@@ -8981,7 +11223,7 @@ var ParallelEternalEngine = class {
8981
11223
  this.opts.onStage?.(stage);
8982
11224
  };
8983
11225
  this.iterations++;
8984
- const goal = await loadGoal(this.goalPath, this.opts.events);
11226
+ const goal = await this.loadGoal();
8985
11227
  if (!goal) {
8986
11228
  this.stopRequested = true;
8987
11229
  emit({ phase: "stopped" });
@@ -9125,13 +11367,10 @@ ${personaLine}Task: ${task}
9125
11367
  taskIds.push(taskId);
9126
11368
  await coordinator.assign(spec);
9127
11369
  } catch (err) {
9128
- console.error(JSON.stringify({
9129
- level: "warn",
9130
- event: "parallel_engine.spawn_failed",
9131
- message: toErrorMessage(err),
9132
- context: { slot: i, task, subagentId },
9133
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
9134
- }));
11370
+ this.logError(
11371
+ "Parallel spawn failed",
11372
+ { event: "parallel_engine.spawn_failed", message: toErrorMessage(err), context: { slot: i, task, subagentId } }
11373
+ );
9135
11374
  }
9136
11375
  })()
9137
11376
  );
@@ -9157,13 +11396,10 @@ ${personaLine}Task: ${task}
9157
11396
  clearTimeout(timer);
9158
11397
  }
9159
11398
  } catch (err) {
9160
- console.error(JSON.stringify({
9161
- level: "warn",
9162
- event: "parallel_engine.brainstorm_results_failed",
9163
- message: toErrorMessage(err),
9164
- context: { slotCount, taskIds },
9165
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
9166
- }));
11399
+ this.logError(
11400
+ "Brainstorm results failed",
11401
+ { event: "parallel_engine.brainstorm_results_failed", message: toErrorMessage(err), context: { slotCount, taskIds } }
11402
+ );
9167
11403
  results = coordinator.results().slice(-taskIds.length);
9168
11404
  }
9169
11405
  await Promise.allSettled(subagentIds.map((id) => coordinator.remove(id)));
@@ -9198,13 +11434,10 @@ ${personaLine}Task: ${task}
9198
11434
  }
9199
11435
  }
9200
11436
  } catch (err) {
9201
- console.error(JSON.stringify({
9202
- level: "warn",
9203
- event: "parallel_engine.git_status_failed",
9204
- message: toErrorMessage(err),
9205
- context: { projectRoot: this.opts.projectRoot },
9206
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
9207
- }));
11437
+ this.logError(
11438
+ "Git status failed",
11439
+ { event: "parallel_engine.git_status_failed", message: toErrorMessage(err), context: { projectRoot: this.opts.projectRoot } }
11440
+ );
9208
11441
  }
9209
11442
  }
9210
11443
  if (tasks.length < this.slots) {
@@ -9285,7 +11518,7 @@ ${lastFew}` : "No prior iterations.",
9285
11518
  // Helpers
9286
11519
  // -------------------------------------------------------------------------
9287
11520
  async appendIterationEntry(entry) {
9288
- const current = await loadGoal(this.goalPath, this.opts.events);
11521
+ const current = await this.loadGoal();
9289
11522
  if (!current) return;
9290
11523
  const updated = appendJournal(current, entry);
9291
11524
  await saveGoal(this.goalPath, updated, this.opts.events);
@@ -9300,7 +11533,7 @@ ${lastFew}` : "No prior iterations.",
9300
11533
  await this.appendIterationEntry({ source: "manual", task, status: "failure", note });
9301
11534
  }
9302
11535
  async persistState(state) {
9303
- const current = await loadGoal(this.goalPath, this.opts.events);
11536
+ const current = await this.loadGoal();
9304
11537
  if (!current) return;
9305
11538
  if (current.engineState === state) return;
9306
11539
  await saveGoal(this.goalPath, { ...current, engineState: state }, this.opts.events);
@@ -9406,6 +11639,7 @@ var LLMSelector = class {
9406
11639
  maxContextTokens;
9407
11640
  systemPrompt;
9408
11641
  maxOutputTokens;
11642
+ oneShotOrchestrator;
9409
11643
  constructor(opts) {
9410
11644
  this.provider = opts.provider;
9411
11645
  this.model = opts.model ?? "unknown";
@@ -9417,6 +11651,7 @@ var LLMSelector = class {
9417
11651
  this.maxContextTokens = opts.maxContextTokens ?? 4e4;
9418
11652
  this.systemPrompt = opts.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
9419
11653
  this.maxOutputTokens = opts.maxOutputTokens ?? 1024;
11654
+ this.oneShotOrchestrator = opts.oneShotOrchestrator;
9420
11655
  }
9421
11656
  async select(messages, maxToKeep) {
9422
11657
  const effectiveBudget = Math.min(maxToKeep, this.maxContextTokens);
@@ -9440,12 +11675,23 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
9440
11675
  let raw;
9441
11676
  const ac = new AbortController();
9442
11677
  try {
9443
- const timeoutSignal = AbortSignal.timeout(3e4);
9444
- const res = await this.provider.complete(req, {
9445
- signal: AbortSignal.any([ac.signal, timeoutSignal])
9446
- });
9447
- const textBlocks = res.content.filter(isTextBlock);
9448
- 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
+ }
9449
11695
  } catch (err) {
9450
11696
  if (err instanceof Error) {
9451
11697
  console.warn(
@@ -9565,12 +11811,12 @@ var SelectiveCompactor = class {
9565
11811
  constructor(opts) {
9566
11812
  this.provider = opts.provider;
9567
11813
  this.selector = opts.selector ?? new LLMSelector({ provider: opts.provider, model: opts.selectorModel, maxOutputTokens: opts.selectorMaxOutputTokens });
9568
- this.warnThreshold = opts.warnThreshold ?? 0.6;
9569
- this.softThreshold = opts.softThreshold ?? 0.75;
9570
- 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;
9571
11817
  this.maxContext = opts.maxContext ?? 128e3;
9572
11818
  this.preserveK = opts.preserveK ?? 4;
9573
- this.eliseThreshold = opts.eliseThreshold ?? 500;
11819
+ this.eliseThreshold = opts.eliseThreshold ?? 300;
9574
11820
  this.summarizerModel = opts.summarizerModel ?? opts.selectorModel;
9575
11821
  if (this.summarizerModel === void 0 && (process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1")) {
9576
11822
  console.warn(
@@ -9763,7 +12009,7 @@ Summarize the following message range:`;
9763
12009
  };
9764
12010
 
9765
12011
  // src/execution/skill-loader.ts
9766
- import * as fs4 from "node:fs/promises";
12012
+ import * as fs5 from "node:fs/promises";
9767
12013
  import * as path7 from "node:path";
9768
12014
 
9769
12015
  // src/skills/foreign-sources.ts
@@ -9994,7 +12240,7 @@ async function entryIsDirectory(dir, entry) {
9994
12240
  if (entry.isDirectory()) return true;
9995
12241
  if (entry.isSymbolicLink()) {
9996
12242
  try {
9997
- return (await fs4.stat(path7.join(dir, entry.name))).isDirectory();
12243
+ return (await fs5.stat(path7.join(dir, entry.name))).isDirectory();
9998
12244
  } catch {
9999
12245
  return false;
10000
12246
  }
@@ -10037,12 +12283,12 @@ var DefaultSkillLoader = class {
10037
12283
  const seen = /* @__PURE__ */ new Set();
10038
12284
  for (const { dir, source, originTool } of this.dirs) {
10039
12285
  try {
10040
- const entries = await fs4.readdir(dir, { withFileTypes: true });
12286
+ const entries = await fs5.readdir(dir, { withFileTypes: true });
10041
12287
  for (const e of entries) {
10042
12288
  if (!await entryIsDirectory(dir, e)) continue;
10043
12289
  const skillFile = path7.join(dir, e.name, "SKILL.md");
10044
12290
  try {
10045
- const raw = await fs4.readFile(skillFile, "utf8");
12291
+ const raw = await fs5.readFile(skillFile, "utf8");
10046
12292
  const fm = parseSkillFrontmatter(raw);
10047
12293
  if (!fm.name || !fm.description) continue;
10048
12294
  if (!isValidSkillNameFormat(fm.name)) continue;
@@ -10114,7 +12360,7 @@ var DefaultSkillLoader = class {
10114
12360
  if (cached !== void 0) return cached;
10115
12361
  const m = await this.find(name);
10116
12362
  if (!m) throw new Error(`Skill "${name}" not found`);
10117
- const body = await fs4.readFile(m.path, "utf8");
12363
+ const body = await fs5.readFile(m.path, "utf8");
10118
12364
  this.bodyCache.set(key, body);
10119
12365
  return body;
10120
12366
  }
@@ -10127,9 +12373,9 @@ var DefaultSkillLoader = class {
10127
12373
  const savePath = path7.join(path7.dirname(m.path), "SKILL.save.md");
10128
12374
  let result;
10129
12375
  try {
10130
- result = await fs4.readFile(savePath, "utf8");
12376
+ result = await fs5.readFile(savePath, "utf8");
10131
12377
  } catch {
10132
- const full = await fs4.readFile(m.path, "utf8");
12378
+ const full = await fs5.readFile(m.path, "utf8");
10133
12379
  const body = stripFrontmatter2(full);
10134
12380
  const compact = compactSkillBody(body);
10135
12381
  if (compact) {
@@ -10157,11 +12403,11 @@ function parseDescriptionFromText(desc) {
10157
12403
  }
10158
12404
 
10159
12405
  // src/execution/prompt-loader.ts
10160
- import * as fs6 from "node:fs/promises";
12406
+ import * as fs7 from "node:fs/promises";
10161
12407
  import * as path9 from "node:path";
10162
12408
 
10163
12409
  // src/storage/prompt-store.ts
10164
- import * as fs5 from "node:fs/promises";
12410
+ import * as fs6 from "node:fs/promises";
10165
12411
  import * as path8 from "node:path";
10166
12412
  var SCHEMA_VERSION = 2;
10167
12413
  function migratePromptEntry(raw) {
@@ -10223,12 +12469,12 @@ var DefaultPromptStore = class {
10223
12469
  await ensureDir(this.dir);
10224
12470
  const entries = [];
10225
12471
  try {
10226
- const files = await fs5.readdir(this.dir);
12472
+ const files = await fs6.readdir(this.dir);
10227
12473
  for (const file of files) {
10228
12474
  if (!file.endsWith(".json")) continue;
10229
12475
  try {
10230
12476
  const raw = JSON.parse(
10231
- await fs5.readFile(path8.join(this.dir, file), "utf8")
12477
+ await fs6.readFile(path8.join(this.dir, file), "utf8")
10232
12478
  );
10233
12479
  const migrated = migratePromptEntry(raw.entry);
10234
12480
  if (migrated) entries.push(migrated);
@@ -10244,7 +12490,7 @@ var DefaultPromptStore = class {
10244
12490
  async get(id) {
10245
12491
  const file = path8.join(this.dir, `${id}.json`);
10246
12492
  try {
10247
- const raw = JSON.parse(await fs5.readFile(file, "utf8"));
12493
+ const raw = JSON.parse(await fs6.readFile(file, "utf8"));
10248
12494
  return migratePromptEntry(raw.entry);
10249
12495
  } catch {
10250
12496
  return null;
@@ -10259,7 +12505,7 @@ var DefaultPromptStore = class {
10259
12505
  async delete(id) {
10260
12506
  const file = path8.join(this.dir, `${id}.json`);
10261
12507
  try {
10262
- await fs5.unlink(file);
12508
+ await fs6.unlink(file);
10263
12509
  return true;
10264
12510
  } catch {
10265
12511
  return false;
@@ -10459,7 +12705,7 @@ var DefaultPromptLoader = class {
10459
12705
  const files = await walkJson(dir);
10460
12706
  for (const file of files) {
10461
12707
  try {
10462
- const parsed = JSON.parse(await fs6.readFile(file, "utf8"));
12708
+ const parsed = JSON.parse(await fs7.readFile(file, "utf8"));
10463
12709
  const migrated = migratePromptEntry(parsed);
10464
12710
  if (migrated) out.push({ ...migrated, source: "builtin" });
10465
12711
  } catch {
@@ -10473,7 +12719,7 @@ async function walkJson(dir) {
10473
12719
  const out = [];
10474
12720
  let entries;
10475
12721
  try {
10476
- entries = await fs6.readdir(dir, { withFileTypes: true });
12722
+ entries = await fs7.readdir(dir, { withFileTypes: true });
10477
12723
  } catch {
10478
12724
  return out;
10479
12725
  }
@@ -10650,7 +12896,8 @@ var ProviderBackedCompactor = class {
10650
12896
  }
10651
12897
  return new IntelligentCompactor({
10652
12898
  ...common,
10653
- summarizerModel: this.opts.summarizerModel
12899
+ summarizerModel: this.opts.summarizerModel,
12900
+ oneShotOrchestrator: this.opts.oneShotOrchestrator
10654
12901
  });
10655
12902
  }
10656
12903
  };
@@ -10665,9 +12912,12 @@ function readPolicy(ctx) {
10665
12912
  // src/execution/tool-executor.ts
10666
12913
  import { randomUUID as randomUUID3 } from "node:crypto";
10667
12914
  import { isDeepStrictEqual } from "node:util";
10668
- import * as fs7 from "node:fs/promises";
12915
+ import * as fs8 from "node:fs/promises";
10669
12916
  import * as path10 from "node:path";
10670
12917
 
12918
+ // src/types/tool-executor.ts
12919
+ var GOVERNED_TOOL_EXECUTOR_META_KEY = "toolExecutor.executeGoverned";
12920
+
10671
12921
  // src/types/tool-markers.ts
10672
12922
  var MALFORMED_ARG_MARKERS = ["__raw", "__raw_arguments", "_raw"];
10673
12923
 
@@ -10784,6 +13034,18 @@ var ToolExecutor = class _ToolExecutor {
10784
13034
  * Returns the execution results and the remaining output budget.
10785
13035
  */
10786
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) {
10787
13049
  let budget = this.opts.perIterationOutputCapBytes ?? 1e5;
10788
13050
  const runOne = async (use0) => {
10789
13051
  const start = Date.now();
@@ -10954,8 +13216,9 @@ ${errorDetails}`,
10954
13216
  );
10955
13217
  if (post.additionalContext) {
10956
13218
  if (post.contextAs === "separate") {
10957
- const block = { type: "text", text: post.additionalContext };
10958
- ctx.state.appendMessage({ role: "user", content: [block] });
13219
+ ctx.pendingPostToolContext = ctx.pendingPostToolContext ? `${ctx.pendingPostToolContext}
13220
+
13221
+ ${post.additionalContext}` : post.additionalContext;
10959
13222
  } else {
10960
13223
  const appended = `
10961
13224
 
@@ -11055,14 +13318,56 @@ ${post.additionalContext}`;
11055
13318
  };
11056
13319
  }
11057
13320
  /**
11058
- * Execute a single tool with timeout, permission check, and output capping.
11059
- * Emits `tool.started` via the injected EventBus (if any) right before
11060
- * invoking the tool closes the observability gap between "model decided
11061
- * 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.
11062
13365
  */
11063
13366
  async executeTool(tool, use, ctx, budget) {
11064
- const text = await this.produceToolOutput(tool, use, ctx, budget);
11065
- 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
+ });
11066
13371
  }
11067
13372
  /**
11068
13373
  * Async "produce" phase: run the tool, serialize, scrub, and spill an
@@ -11121,12 +13426,12 @@ ${post.additionalContext}`;
11121
13426
  if (parentSignal.reason instanceof Error) throw parentSignal.reason;
11122
13427
  throw new Error(typeof parentSignal.reason === "string" ? parentSignal.reason : "aborted");
11123
13428
  }
11124
- const timeoutMs = clampTimeoutMs(
11125
- tool.timeoutMs ?? this.iterationTimeoutMs,
11126
- this.maxToolTimeoutMs
11127
- );
11128
- const timeoutSignal = AbortSignal.timeout(timeoutMs);
11129
- const combined = AbortSignal.any([parentSignal, timeoutSignal]);
13429
+ const combined = tool.managesOwnTimeout ? parentSignal : AbortSignal.any([
13430
+ parentSignal,
13431
+ AbortSignal.timeout(
13432
+ clampTimeoutMs(tool.timeoutMs ?? this.iterationTimeoutMs, this.maxToolTimeoutMs)
13433
+ )
13434
+ ]);
11130
13435
  let output;
11131
13436
  const toolPromise = typeof tool.executeStream === "function" ? this.runStreamedTool(tool, input, ctx, combined, toolUseId) : (async () => tool.execute(input, ctx, { signal: combined }))();
11132
13437
  toolPromise.catch(() => {
@@ -11417,11 +13722,11 @@ async function maybePersistLargeToolOutput(toolName, content, budget) {
11417
13722
  }
11418
13723
  try {
11419
13724
  const dir = path10.join(wstackGlobalRoot(), "tool-output");
11420
- await fs7.mkdir(dir, { recursive: true });
13725
+ await fs8.mkdir(dir, { recursive: true });
11421
13726
  const safeTool = toolName.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 40) || "tool";
11422
13727
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
11423
13728
  const filePath = path10.join(dir, `${stamp}-${safeTool}-${randomUUID3()}.log`);
11424
- await fs7.writeFile(filePath, content, "utf8");
13729
+ await fs8.writeFile(filePath, content, "utf8");
11425
13730
  const marker = `[full tool output: ${bytes} bytes at ${filePath}; read/grep that file selectively instead of re-running or requesting more output]`;
11426
13731
  const fixedBytes = Buffer.byteLength(marker + TOOL_OUTPUT_ARTIFACT_OMISSION, "utf8");
11427
13732
  const previewBytes = Math.min(
@@ -11469,9 +13774,233 @@ function sliceUtf8Suffix(text, maxBytes) {
11469
13774
  if (first >= 56320 && first <= 57343) start++;
11470
13775
  return text.slice(start);
11471
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
+ }
11472
13982
  export {
11473
13983
  AutoCompactionMiddleware,
11474
13984
  AutonomousRunner,
13985
+ BRAIN_EVALUATION_CASE_VERSION,
13986
+ BUILTIN_COUNCIL_PERSONAS,
13987
+ BUILTIN_COUNCIL_PROFILES,
13988
+ COUNCIL_JUDGE_PROMPT_PATH,
13989
+ COUNCIL_REFUSAL_OPTION_ID,
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,
11475
14004
  DEFAULT_RECOVERY_STRATEGIES,
11476
14005
  DefaultDesignKitLoader,
11477
14006
  DefaultErrorHandler,
@@ -11482,6 +14011,8 @@ export {
11482
14011
  EternalAutonomyEngine,
11483
14012
  HybridCompactor,
11484
14013
  IntelligentCompactor,
14014
+ MAX_COUNCIL_CONCURRENCY,
14015
+ OneShotOrchestrator,
11485
14016
  ParallelEternalEngine,
11486
14017
  SelectiveCompactor,
11487
14018
  ToolExecutor,
@@ -11489,11 +14020,23 @@ export {
11489
14020
  _resetDesignRulesCache,
11490
14021
  activateDesign,
11491
14022
  applyModelRuntime,
14023
+ assembleBrainTiers,
14024
+ buildBrainUserMessage,
14025
+ buildCouncilJudgeSystemPrompt,
14026
+ buildCouncilJudgeUserPrompt,
14027
+ buildCouncilQuestionPrompt,
14028
+ buildCouncilVoterSystemPrompt,
14029
+ buildCouncilVoterUserPrompt,
11492
14030
  buildGoalPreamble,
11493
14031
  buildRecoveryStrategies,
11494
14032
  clearActiveKit,
11495
14033
  clearPersistedActiveKit,
14034
+ completeBrainLlm,
11496
14035
  createAutonomyBrain,
14036
+ createBrainRuntime,
14037
+ createCouncilBrainArbiter,
14038
+ createCouncilPersonaRegistry,
14039
+ createCouncilProfileRegistry,
11497
14040
  createStrategyCompactor,
11498
14041
  createTieredBrainArbiter,
11499
14042
  designProjectDir,
@@ -11510,12 +14053,19 @@ export {
11510
14053
  makeDesignDetectUserInputMiddleware,
11511
14054
  makeDesignStudioRequestMiddleware,
11512
14055
  mergeModelRuntime,
14056
+ normalizeCouncilProfile,
14057
+ parseOptionDecision,
11513
14058
  recordKitChoice,
11514
14059
  renderPrompt,
14060
+ resolveBrainConfigDefaults,
11515
14061
  resolveBundledDesignKitsDir,
11516
14062
  resolveCacheForRequest,
14063
+ resolveCouncilProfile,
14064
+ resolveCouncilVotes,
11517
14065
  resolveModelRuntime,
11518
14066
  resolveReasoningForRequest,
11519
- setActiveKit
14067
+ runBrainEvaluation,
14068
+ setActiveKit,
14069
+ validateBrainEvaluationCase
11520
14070
  };
11521
14071
  //# sourceMappingURL=index.js.map