@wrongstack/core 0.286.0 → 0.287.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (251) hide show
  1. package/dist/boot.d.ts.map +1 -1
  2. package/dist/coordination/brain-monitor.d.ts +15 -0
  3. package/dist/coordination/brain-monitor.d.ts.map +1 -1
  4. package/dist/coordination/delegate-tool.d.ts +8 -14
  5. package/dist/coordination/delegate-tool.d.ts.map +1 -1
  6. package/dist/coordination/director/director-btw-notes.d.ts +20 -0
  7. package/dist/coordination/director/director-btw-notes.d.ts.map +1 -0
  8. package/dist/coordination/director/director-collab.d.ts +40 -0
  9. package/dist/coordination/director/director-collab.d.ts.map +1 -0
  10. package/dist/coordination/director.d.ts +27 -8
  11. package/dist/coordination/director.d.ts.map +1 -1
  12. package/dist/coordination/fleet-manager.d.ts +21 -0
  13. package/dist/coordination/fleet-manager.d.ts.map +1 -1
  14. package/dist/coordination/fleet.d.ts +2 -3
  15. package/dist/coordination/fleet.d.ts.map +1 -1
  16. package/dist/coordination/global-mailbox.d.ts +36 -22
  17. package/dist/coordination/global-mailbox.d.ts.map +1 -1
  18. package/dist/coordination/icoordinator.d.ts +1 -3
  19. package/dist/coordination/icoordinator.d.ts.map +1 -1
  20. package/dist/coordination/index.js +708 -442
  21. package/dist/coordination/index.js.map +4 -4
  22. package/dist/coordination/mail-tools.d.ts.map +1 -1
  23. package/dist/coordination/mailbox-message-codec.d.ts +21 -1
  24. package/dist/coordination/mailbox-message-codec.d.ts.map +1 -1
  25. package/dist/coordination/mailbox-types.d.ts +42 -1
  26. package/dist/coordination/mailbox-types.d.ts.map +1 -1
  27. package/dist/core/agent-loop.d.ts.map +1 -1
  28. package/dist/core/agent-response.d.ts +10 -1
  29. package/dist/core/agent-response.d.ts.map +1 -1
  30. package/dist/core/agent-tools.d.ts.map +1 -1
  31. package/dist/core/agent.d.ts +8 -0
  32. package/dist/core/agent.d.ts.map +1 -1
  33. package/dist/core/context.d.ts +25 -0
  34. package/dist/core/context.d.ts.map +1 -1
  35. package/dist/core/conversation-state.d.ts +6 -0
  36. package/dist/core/conversation-state.d.ts.map +1 -1
  37. package/dist/core/fallback-model.d.ts.map +1 -1
  38. package/dist/core/mailbox-loop.d.ts +12 -5
  39. package/dist/core/mailbox-loop.d.ts.map +1 -1
  40. package/dist/core/system-prompt-builder.d.ts +14 -6
  41. package/dist/core/system-prompt-builder.d.ts.map +1 -1
  42. package/dist/defaults/index.d.ts +0 -1
  43. package/dist/defaults/index.d.ts.map +1 -1
  44. package/dist/defaults/index.js +834 -1465
  45. package/dist/defaults/index.js.map +4 -4
  46. package/dist/execution/auto-compaction-middleware.d.ts +8 -1
  47. package/dist/execution/auto-compaction-middleware.d.ts.map +1 -1
  48. package/dist/execution/compaction-core.d.ts.map +1 -1
  49. package/dist/execution/council-brain.d.ts.map +1 -1
  50. package/dist/execution/council-orchestrator.d.ts +32 -0
  51. package/dist/execution/council-orchestrator.d.ts.map +1 -0
  52. package/dist/execution/council-personas.d.ts +19 -0
  53. package/dist/execution/council-personas.d.ts.map +1 -0
  54. package/dist/execution/council-profiles.d.ts +34 -0
  55. package/dist/execution/council-profiles.d.ts.map +1 -0
  56. package/dist/execution/council-prompts.d.ts +21 -0
  57. package/dist/execution/council-prompts.d.ts.map +1 -0
  58. package/dist/execution/council-resolution.d.ts +57 -0
  59. package/dist/execution/council-resolution.d.ts.map +1 -0
  60. package/dist/execution/design-kit-loader.d.ts.map +1 -1
  61. package/dist/execution/enhance-recovery.d.ts +35 -0
  62. package/dist/execution/enhance-recovery.d.ts.map +1 -0
  63. package/dist/execution/error-handler.d.ts.map +1 -1
  64. package/dist/execution/index.d.ts +6 -0
  65. package/dist/execution/index.d.ts.map +1 -1
  66. package/dist/execution/index.js +1738 -161
  67. package/dist/execution/index.js.map +4 -4
  68. package/dist/execution/intelligent-compactor.d.ts +18 -7
  69. package/dist/execution/intelligent-compactor.d.ts.map +1 -1
  70. package/dist/execution/one-shot-llm.d.ts +52 -0
  71. package/dist/execution/one-shot-llm.d.ts.map +1 -0
  72. package/dist/execution/prompt-enhancer.d.ts +23 -4
  73. package/dist/execution/prompt-enhancer.d.ts.map +1 -1
  74. package/dist/execution/prompt-enhancer.js +24 -6
  75. package/dist/execution/prompt-enhancer.js.map +2 -2
  76. package/dist/execution/selective-compactor.d.ts +4 -4
  77. package/dist/execution/strategy-compactor.d.ts +7 -0
  78. package/dist/execution/strategy-compactor.d.ts.map +1 -1
  79. package/dist/execution/tool-executor.d.ts +19 -5
  80. package/dist/execution/tool-executor.d.ts.map +1 -1
  81. package/dist/hooks/registry.d.ts.map +1 -1
  82. package/dist/hooks/runner.d.ts.map +1 -1
  83. package/dist/hooks/shell-executor.d.ts.map +1 -1
  84. package/dist/hq/alerts.d.ts +5 -0
  85. package/dist/hq/alerts.d.ts.map +1 -1
  86. package/dist/hq/auth-store.d.ts +17 -7
  87. package/dist/hq/auth-store.d.ts.map +1 -1
  88. package/dist/hq/commands.d.ts +8 -4
  89. package/dist/hq/commands.d.ts.map +1 -1
  90. package/dist/hq/cost-bridge.d.ts +5 -3
  91. package/dist/hq/cost-bridge.d.ts.map +1 -1
  92. package/dist/hq/fleet-bridge.d.ts.map +1 -1
  93. package/dist/hq/index.d.ts +0 -1
  94. package/dist/hq/index.d.ts.map +1 -1
  95. package/dist/hq/index.js +471 -294
  96. package/dist/hq/index.js.map +4 -4
  97. package/dist/hq/persistence.d.ts +42 -0
  98. package/dist/hq/persistence.d.ts.map +1 -1
  99. package/dist/hq/protocol/brain.d.ts +24 -0
  100. package/dist/hq/protocol/brain.d.ts.map +1 -0
  101. package/dist/hq/protocol/browser.d.ts +12 -0
  102. package/dist/hq/protocol/browser.d.ts.map +1 -0
  103. package/dist/hq/protocol/client.d.ts +69 -0
  104. package/dist/hq/protocol/client.d.ts.map +1 -0
  105. package/dist/hq/protocol/core.d.ts +130 -0
  106. package/dist/hq/protocol/core.d.ts.map +1 -0
  107. package/dist/hq/protocol/fleet.d.ts +73 -0
  108. package/dist/hq/protocol/fleet.d.ts.map +1 -0
  109. package/dist/hq/protocol/mailbox.d.ts +76 -0
  110. package/dist/hq/protocol/mailbox.d.ts.map +1 -0
  111. package/dist/hq/protocol/mcp.d.ts +51 -0
  112. package/dist/hq/protocol/mcp.d.ts.map +1 -0
  113. package/dist/hq/protocol/project.d.ts +26 -0
  114. package/dist/hq/protocol/project.d.ts.map +1 -0
  115. package/dist/hq/protocol/session.d.ts +153 -0
  116. package/dist/hq/protocol/session.d.ts.map +1 -0
  117. package/dist/hq/protocol/tool.d.ts +31 -0
  118. package/dist/hq/protocol/tool.d.ts.map +1 -0
  119. package/dist/hq/protocol.d.ts +10 -611
  120. package/dist/hq/protocol.d.ts.map +1 -1
  121. package/dist/index.d.ts +12 -2
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/index.js +4540 -2283
  124. package/dist/index.js.map +4 -4
  125. package/dist/infrastructure/index.js +13 -11
  126. package/dist/infrastructure/index.js.map +2 -2
  127. package/dist/infrastructure/token-counter.d.ts.map +1 -1
  128. package/dist/kernel/events/agent-events.d.ts +283 -0
  129. package/dist/kernel/events/agent-events.d.ts.map +1 -0
  130. package/dist/kernel/events/brain-events.d.ts +176 -0
  131. package/dist/kernel/events/brain-events.d.ts.map +1 -0
  132. package/dist/kernel/events/fleet-events.d.ts +39 -0
  133. package/dist/kernel/events/fleet-events.d.ts.map +1 -0
  134. package/dist/kernel/events/memory-events.d.ts +165 -0
  135. package/dist/kernel/events/memory-events.d.ts.map +1 -0
  136. package/dist/kernel/events/provider-events.d.ts +98 -0
  137. package/dist/kernel/events/provider-events.d.ts.map +1 -0
  138. package/dist/kernel/events/sdd-events.d.ts +118 -0
  139. package/dist/kernel/events/sdd-events.d.ts.map +1 -0
  140. package/dist/kernel/events/session-events.d.ts +227 -0
  141. package/dist/kernel/events/session-events.d.ts.map +1 -0
  142. package/dist/kernel/events/tool-events.d.ts +185 -0
  143. package/dist/kernel/events/tool-events.d.ts.map +1 -0
  144. package/dist/kernel/events/worktree-events.d.ts +58 -0
  145. package/dist/kernel/events/worktree-events.d.ts.map +1 -0
  146. package/dist/kernel/events.d.ts +10 -1298
  147. package/dist/kernel/events.d.ts.map +1 -1
  148. package/dist/kernel/index.js +22 -2
  149. package/dist/kernel/index.js.map +2 -2
  150. package/dist/kernel/run-controller.d.ts +15 -0
  151. package/dist/kernel/run-controller.d.ts.map +1 -1
  152. package/dist/mailbox-attach.d.ts.map +1 -1
  153. package/dist/models/index.js +19 -6
  154. package/dist/models/index.js.map +2 -2
  155. package/dist/models/llm-selector.d.ts +7 -0
  156. package/dist/models/llm-selector.d.ts.map +1 -1
  157. package/dist/plugins/auto-review-plugin.d.ts +36 -0
  158. package/dist/plugins/auto-review-plugin.d.ts.map +1 -0
  159. package/dist/plugins/chimera-plugin.d.ts +8 -0
  160. package/dist/plugins/chimera-plugin.d.ts.map +1 -1
  161. package/dist/security/index.js +7 -2
  162. package/dist/security/index.js.map +2 -2
  163. package/dist/security/secret-scrubber.d.ts.map +1 -1
  164. package/dist/skills/index.js.map +2 -2
  165. package/dist/storage/config-loader.d.ts.map +1 -1
  166. package/dist/storage/goal-kanban.d.ts +69 -0
  167. package/dist/storage/goal-kanban.d.ts.map +1 -0
  168. package/dist/storage/index.d.ts +1 -1
  169. package/dist/storage/index.d.ts.map +1 -1
  170. package/dist/storage/index.js +425 -717
  171. package/dist/storage/index.js.map +4 -4
  172. package/dist/storage/memory-consolidator.d.ts +9 -1
  173. package/dist/storage/memory-consolidator.d.ts.map +1 -1
  174. package/dist/tools/council-tool.d.ts +25 -0
  175. package/dist/tools/council-tool.d.ts.map +1 -0
  176. package/dist/tools/index.d.ts +2 -0
  177. package/dist/tools/index.d.ts.map +1 -1
  178. package/dist/tools/index.js +1884 -12
  179. package/dist/tools/index.js.map +4 -4
  180. package/dist/tools/one-shot-llm-tool.d.ts +51 -0
  181. package/dist/tools/one-shot-llm-tool.d.ts.map +1 -0
  182. package/dist/types/config.d.ts +27 -3
  183. package/dist/types/config.d.ts.map +1 -1
  184. package/dist/types/context-window.d.ts.map +1 -1
  185. package/dist/types/council.d.ts +134 -0
  186. package/dist/types/council.d.ts.map +1 -0
  187. package/dist/types/default-config.d.ts +3 -3
  188. package/dist/types/hooks.d.ts +14 -0
  189. package/dist/types/hooks.d.ts.map +1 -1
  190. package/dist/types/index.d.ts +2 -0
  191. package/dist/types/index.d.ts.map +1 -1
  192. package/dist/types/index.js +29 -8
  193. package/dist/types/index.js.map +3 -3
  194. package/dist/types/one-shot-llm.d.ts +128 -0
  195. package/dist/types/one-shot-llm.d.ts.map +1 -0
  196. package/dist/types/plugin.d.ts +6 -0
  197. package/dist/types/plugin.d.ts.map +1 -1
  198. package/dist/types/provider.d.ts +17 -0
  199. package/dist/types/provider.d.ts.map +1 -1
  200. package/dist/types/system-prompt.d.ts +2 -1
  201. package/dist/types/system-prompt.d.ts.map +1 -1
  202. package/dist/types/tool-executor.d.ts +10 -0
  203. package/dist/types/tool-executor.d.ts.map +1 -1
  204. package/dist/utils/atomic-write.d.ts.map +1 -1
  205. package/dist/utils/index.js +75 -8
  206. package/dist/utils/index.js.map +3 -3
  207. package/dist/utils/tool-wire-compact.d.ts +1 -0
  208. package/dist/utils/tool-wire-compact.d.ts.map +1 -1
  209. package/dist/utils/wstack-paths.d.ts +20 -4
  210. package/dist/utils/wstack-paths.d.ts.map +1 -1
  211. package/instructions/cli/next-task-predictor.md +5 -3
  212. package/instructions/coordination/subagent-baseline.md +8 -7
  213. package/instructions/leader-after-task.md +18 -9
  214. package/instructions/llm/council-judge.md +19 -0
  215. package/instructions/llm/council-voter.md +22 -0
  216. package/instructions/modes/architect.md +14 -9
  217. package/instructions/modes/audit-lite.md +9 -7
  218. package/instructions/modes/brief-system.md +12 -12
  219. package/instructions/modes/brief.md +7 -5
  220. package/instructions/modes/code-auditor.md +13 -8
  221. package/instructions/modes/code-reviewer.md +13 -8
  222. package/instructions/modes/debug-lite.md +7 -6
  223. package/instructions/modes/debugger.md +13 -9
  224. package/instructions/modes/devops.md +13 -9
  225. package/instructions/modes/plan-lite.md +7 -6
  226. package/instructions/modes/refactor-lite.md +7 -6
  227. package/instructions/modes/refactorer.md +14 -9
  228. package/instructions/modes/research-lite.md +8 -6
  229. package/instructions/modes/research-web.md +18 -55
  230. package/instructions/modes/review-lite.md +7 -6
  231. package/instructions/modes/teach-system.md +15 -15
  232. package/instructions/modes/teach.md +13 -12
  233. package/instructions/modes/test-lite.md +6 -5
  234. package/instructions/modes/tester.md +13 -9
  235. package/instructions/modes/ui-design.md +14 -8
  236. package/instructions/sections/tool/commit-hygiene.md +1 -7
  237. package/instructions/sections/tool/common-patterns.md +5 -5
  238. package/instructions/sections/tool/delegation-full.md +3 -31
  239. package/instructions/sections/tool/mailbox-compact.md +1 -1
  240. package/instructions/sections/tool/mailbox-full.md +6 -22
  241. package/instructions/system.md +278 -39
  242. package/package.json +2 -2
  243. package/skills/mailbox-bridge/SKILL.md +16 -7
  244. package/skills/output-standards/SKILL.md +16 -16
  245. package/skills/wrongstack-mailbox/SKILL.md +6 -0
  246. package/dist/coordination/director-construction.d.ts +0 -90
  247. package/dist/coordination/director-construction.d.ts.map +0 -1
  248. package/dist/hq/agent-bridge.d.ts +0 -27
  249. package/dist/hq/agent-bridge.d.ts.map +0 -1
  250. package/dist/storage/memory-store.d.ts +0 -119
  251. package/dist/storage/memory-store.d.ts.map +0 -1
@@ -257,6 +257,99 @@ import * as fs2 from "node:fs/promises";
257
257
  import { randomBytes } from "node:crypto";
258
258
  import * as fs from "node:fs/promises";
259
259
  import * as path from "node:path";
260
+
261
+ // src/utils/string.ts
262
+ function truncate(s, max) {
263
+ return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
264
+ }
265
+
266
+ // src/types/errors.ts
267
+ var ERROR_CODES = {
268
+ // Provider
269
+ PROVIDER_RATE_LIMITED: "PROVIDER_RATE_LIMITED",
270
+ PROVIDER_AUTH_FAILED: "PROVIDER_AUTH_FAILED",
271
+ PROVIDER_OVERLOADED: "PROVIDER_OVERLOADED",
272
+ PROVIDER_INVALID_REQUEST: "PROVIDER_INVALID_REQUEST",
273
+ PROVIDER_SERVER_ERROR: "PROVIDER_SERVER_ERROR",
274
+ PROVIDER_NETWORK_ERROR: "PROVIDER_NETWORK_ERROR",
275
+ PROVIDER_CONTEXT_OVERFLOW: "PROVIDER_CONTEXT_OVERFLOW",
276
+ // Tool
277
+ TOOL_NOT_FOUND: "TOOL_NOT_FOUND",
278
+ TOOL_PERMISSION_DENIED: "TOOL_PERMISSION_DENIED",
279
+ TOOL_EXECUTION_FAILED: "TOOL_EXECUTION_FAILED",
280
+ TOOL_TIMEOUT: "TOOL_TIMEOUT",
281
+ TOOL_INPUT_INVALID: "TOOL_INPUT_INVALID",
282
+ // Config
283
+ CONFIG_INVALID: "CONFIG_INVALID",
284
+ CONFIG_NOT_FOUND: "CONFIG_NOT_FOUND",
285
+ CONFIG_PARSE_FAILED: "CONFIG_PARSE_FAILED",
286
+ CONFIG_MIGRATION_NEEDED: "CONFIG_MIGRATION_NEEDED",
287
+ // Plugin
288
+ PLUGIN_LOAD_FAILED: "PLUGIN_LOAD_FAILED",
289
+ PLUGIN_API_MISMATCH: "PLUGIN_API_MISMATCH",
290
+ PLUGIN_MISSING_DEPENDENCY: "PLUGIN_MISSING_DEPENDENCY",
291
+ // Agent
292
+ AGENT_ITERATION_LIMIT: "AGENT_ITERATION_LIMIT",
293
+ AGENT_CONTEXT_OVERFLOW: "AGENT_CONTEXT_OVERFLOW",
294
+ AGENT_ABORTED: "AGENT_ABORTED",
295
+ AGENT_RUN_FAILED: "AGENT_RUN_FAILED",
296
+ // Session
297
+ SESSION_NOT_FOUND: "SESSION_NOT_FOUND",
298
+ SESSION_CORRUPTED: "SESSION_CORRUPTED",
299
+ SESSION_WRITE_FAILED: "SESSION_WRITE_FAILED",
300
+ // Container / Registry
301
+ CONTAINER_TOKEN_ALREADY_BOUND: "CONTAINER_TOKEN_ALREADY_BOUND",
302
+ CONTAINER_TOKEN_NOT_BOUND: "CONTAINER_TOKEN_NOT_BOUND",
303
+ CONTAINER_CIRCULAR_DEPENDENCY: "CONTAINER_CIRCULAR_DEPENDENCY",
304
+ REGISTRY_DUPLICATE: "REGISTRY_DUPLICATE",
305
+ REGISTRY_NOT_FOUND: "REGISTRY_NOT_FOUND",
306
+ REGISTRY_INVALID: "REGISTRY_INVALID",
307
+ // File system
308
+ FS_READ_FAILED: "FS_READ_FAILED",
309
+ FS_WRITE_FAILED: "FS_WRITE_FAILED",
310
+ FS_MKDIR_FAILED: "FS_MKDIR_FAILED",
311
+ FS_DELETE_FAILED: "FS_DELETE_FAILED",
312
+ FS_ATOMIC_WRITE_FAILED: "FS_ATOMIC_WRITE_FAILED",
313
+ // SDD (Spec-Driven Development)
314
+ SDD_VALIDATION_FAILED: "SDD_VALIDATION_FAILED",
315
+ SDD_PARSE_FAILED: "SDD_PARSE_FAILED",
316
+ SDD_INVALID_STATE: "SDD_INVALID_STATE",
317
+ SDD_NOT_READY: "SDD_NOT_READY",
318
+ // General
319
+ VALIDATION_ERROR: "VALIDATION_ERROR",
320
+ PARSE_FAILED: "PARSE_FAILED",
321
+ UNKNOWN: "UNKNOWN"
322
+ };
323
+ var WrongStackError = class extends Error {
324
+ code;
325
+ subsystem;
326
+ severity;
327
+ recoverable;
328
+ context;
329
+ constructor(opts) {
330
+ super(opts.message, { cause: opts.cause });
331
+ this.name = "WrongStackError";
332
+ this.code = opts.code;
333
+ this.subsystem = opts.subsystem;
334
+ this.severity = opts.severity ?? "error";
335
+ this.recoverable = opts.recoverable ?? false;
336
+ this.context = opts.context;
337
+ }
338
+ /**
339
+ * Render a one-line user-facing description.
340
+ * Subclasses should override for domain-specific formatting.
341
+ */
342
+ describe() {
343
+ const ctx = this.context ? ` ${formatContext(this.context)}` : "";
344
+ return `${this.code}: ${this.message}${ctx}`;
345
+ }
346
+ };
347
+ function formatContext(ctx) {
348
+ const parts = Object.entries(ctx).filter(([, v]) => v !== void 0).slice(0, 3).map(([k, v]) => `${k}=${String(v)}`);
349
+ return parts.length > 0 ? `[${parts.join(" ")}]` : "";
350
+ }
351
+
352
+ // src/utils/atomic-write.ts
260
353
  async function atomicWrite(targetPath, content, opts = {}) {
261
354
  const dir = path.dirname(targetPath);
262
355
  await fs.mkdir(dir, { recursive: true });
@@ -319,7 +412,7 @@ async function renameWithRetry(from, to) {
319
412
  if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
320
413
  throw err;
321
414
  }
322
- await new Promise((resolve) => setTimeout(resolve, delays[i]));
415
+ await new Promise((resolve2) => setTimeout(resolve2, delays[i]));
323
416
  }
324
417
  }
325
418
  throw lastErr;
@@ -344,13 +437,13 @@ async function updateJsonObjectFile(filePath, mutator) {
344
437
  await writeJsonObjectFile(filePath, next);
345
438
  return next;
346
439
  }
347
- function setJsonPath(root, path2, value) {
348
- if (path2.length === 0) {
440
+ function setJsonPath(root, path3, value) {
441
+ if (path3.length === 0) {
349
442
  if (!isJsonObject(value)) throw new Error("Root config value must be an object");
350
443
  return value;
351
444
  }
352
- const parent = ensureJsonParent(root, path2);
353
- const leaf = lastPathSegment(path2);
445
+ const parent = ensureJsonParent(root, path3);
446
+ const leaf = lastPathSegment(path3);
354
447
  if (typeof leaf === "number") {
355
448
  if (!Array.isArray(parent)) throw new Error(`Cannot set numeric segment ${leaf} on non-array parent`);
356
449
  parent[leaf] = value;
@@ -363,16 +456,16 @@ function setJsonPath(root, path2, value) {
363
456
  function isJsonObject(value) {
364
457
  return typeof value === "object" && value !== null && !Array.isArray(value);
365
458
  }
366
- function lastPathSegment(path2) {
367
- const segment = path2[path2.length - 1];
459
+ function lastPathSegment(path3) {
460
+ const segment = path3[path3.length - 1];
368
461
  if (segment === void 0) throw new Error("Invalid empty JSON path");
369
462
  return segment;
370
463
  }
371
- function ensureJsonParent(root, path2) {
464
+ function ensureJsonParent(root, path3) {
372
465
  let current = root;
373
- for (let i = 0; i < path2.length - 1; i += 1) {
374
- const segment = path2[i];
375
- const nextSegment = path2[i + 1];
466
+ for (let i = 0; i < path3.length - 1; i += 1) {
467
+ const segment = path3[i];
468
+ const nextSegment = path3[i + 1];
376
469
  if (segment === void 0) throw new Error("Invalid empty JSON path segment");
377
470
  const nextContainer = typeof nextSegment === "number" ? [] : {};
378
471
  if (typeof segment === "number") {
@@ -639,7 +732,1786 @@ function badge(state) {
639
732
  return dim(state);
640
733
  }
641
734
  }
735
+
736
+ // src/execution/council-personas.ts
737
+ var PERSONA_ID_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
738
+ var BUILTIN_COUNCIL_PERSONAS = Object.freeze([
739
+ freezePersona({
740
+ id: "executor",
741
+ name: "Executor",
742
+ description: "Tests whether a proposal is practical and keeps useful work moving.",
743
+ 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.",
744
+ tags: ["delivery", "feasibility", "progress"]
745
+ }),
746
+ freezePersona({
747
+ id: "skeptic",
748
+ name: "Skeptic",
749
+ description: "Challenges assumptions and looks for concrete failure modes.",
750
+ instruction: "Identify unsupported assumptions, unsafe premises, irreversible consequences, and credible failure modes. Oppose or refuse only when you can name a concrete reason.",
751
+ defaultVeto: true,
752
+ tags: ["risk", "assumptions", "failure-modes"]
753
+ }),
754
+ freezePersona({
755
+ id: "auditor",
756
+ name: "Auditor",
757
+ description: "Evaluates cost, waste, evidence quality, and expected value.",
758
+ instruction: "Compare resource cost, opportunity cost, evidence quality, reversibility, and expected value. Prefer the option that achieves the objective with the least avoidable waste.",
759
+ tags: ["cost", "evidence", "efficiency"]
760
+ }),
761
+ freezePersona({
762
+ id: "security",
763
+ name: "Security Reviewer",
764
+ description: "Examines trust boundaries, abuse cases, and security impact.",
765
+ 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.",
766
+ defaultVeto: true,
767
+ tags: ["security", "trust", "abuse-cases"]
768
+ }),
769
+ freezePersona({
770
+ id: "maintainer",
771
+ name: "Maintainer",
772
+ description: "Evaluates complexity, compatibility, and long-term ownership.",
773
+ instruction: "Evaluate behavioral compatibility, conceptual complexity, testability, migration cost, and future maintenance. Prefer the smallest design that remains clear and extensible.",
774
+ tags: ["maintenance", "compatibility", "simplicity"]
775
+ }),
776
+ freezePersona({
777
+ id: "user-advocate",
778
+ name: "User Advocate",
779
+ description: "Evaluates the decision from the affected user\u2019s perspective.",
780
+ instruction: "Evaluate usability, surprise, accessibility, failure recovery, and whether the outcome solves the user\u2019s stated need. Prefer understandable behavior with safe recovery paths.",
781
+ tags: ["users", "usability", "accessibility"]
782
+ })
783
+ ]);
784
+ var CouncilPersonaRegistry = class _CouncilPersonaRegistry {
785
+ byId;
786
+ constructor(personas = []) {
787
+ const entries = /* @__PURE__ */ new Map();
788
+ for (const persona of personas) {
789
+ const normalized = freezePersona(persona);
790
+ if (entries.has(normalized.id)) {
791
+ throw new Error(`CouncilPersonaRegistry: duplicate persona id "${normalized.id}".`);
792
+ }
793
+ entries.set(normalized.id, normalized);
794
+ }
795
+ this.byId = entries;
796
+ }
797
+ has(id) {
798
+ return this.byId.has(id);
799
+ }
800
+ get(id) {
801
+ return this.byId.get(id);
802
+ }
803
+ require(id) {
804
+ const persona = this.get(id);
805
+ if (!persona) throw new Error(`CouncilPersonaRegistry: unknown persona "${id}".`);
806
+ return persona;
807
+ }
808
+ list() {
809
+ return Object.freeze([...this.byId.values()]);
810
+ }
811
+ /** Return a new registry; the current registry is never mutated. */
812
+ with(persona, opts = {}) {
813
+ const normalized = freezePersona(persona);
814
+ if (this.has(normalized.id) && opts.replace !== true) {
815
+ throw new Error(`CouncilPersonaRegistry: persona "${normalized.id}" already exists.`);
816
+ }
817
+ return new _CouncilPersonaRegistry([
818
+ ...this.list().filter((entry) => entry.id !== normalized.id),
819
+ normalized
820
+ ]);
821
+ }
822
+ };
823
+ var DEFAULT_COUNCIL_PERSONA_REGISTRY = new CouncilPersonaRegistry(
824
+ BUILTIN_COUNCIL_PERSONAS
825
+ );
826
+ function freezePersona(persona) {
827
+ const id = persona.id.trim();
828
+ const name = persona.name.trim();
829
+ const description = persona.description.trim();
830
+ const instruction = persona.instruction.trim();
831
+ if (!PERSONA_ID_RE.test(id)) {
832
+ throw new Error(`CouncilPersonaRegistry: invalid persona id "${persona.id}".`);
833
+ }
834
+ if (!name) throw new Error(`CouncilPersonaRegistry: persona "${id}" requires a name.`);
835
+ if (!description) {
836
+ throw new Error(`CouncilPersonaRegistry: persona "${id}" requires a description.`);
837
+ }
838
+ if (!instruction) {
839
+ throw new Error(`CouncilPersonaRegistry: persona "${id}" requires an instruction.`);
840
+ }
841
+ if (persona.defaultWeight !== void 0 && (!Number.isFinite(persona.defaultWeight) || persona.defaultWeight <= 0)) {
842
+ throw new Error(`CouncilPersonaRegistry: persona "${id}" has an invalid default weight.`);
843
+ }
844
+ const tags = Object.freeze(
845
+ [...new Set((persona.tags ?? []).map((tag) => tag.trim()).filter(Boolean))]
846
+ );
847
+ return Object.freeze({
848
+ id,
849
+ name,
850
+ description,
851
+ instruction,
852
+ ...persona.defaultWeight !== void 0 ? { defaultWeight: persona.defaultWeight } : {},
853
+ ...persona.defaultVeto !== void 0 ? { defaultVeto: persona.defaultVeto } : {},
854
+ ...tags.length > 0 ? { tags } : {}
855
+ });
856
+ }
857
+
858
+ // src/execution/council-profiles.ts
859
+ var PROFILE_ID_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
860
+ var DISTINCTNESS_VALUES = /* @__PURE__ */ new Set([
861
+ "none",
862
+ "model",
863
+ "provider"
864
+ ]);
865
+ var DEFAULT_COUNCIL_QUORUM_FRACTION = 0.5;
866
+ var DEFAULT_COUNCIL_APPROVAL_FRACTION = 0.5;
867
+ var DEFAULT_COUNCIL_VOTER_MAX_TOKENS = 300;
868
+ var DEFAULT_COUNCIL_JUDGE_MAX_TOKENS = 500;
869
+ var DEFAULT_COUNCIL_PER_CALL_TIMEOUT_MS = 3e4;
870
+ var DEFAULT_COUNCIL_OVERALL_TIMEOUT_MS = 9e4;
871
+ var BUILTIN_COUNCIL_PROFILES = Object.freeze([
872
+ Object.freeze({
873
+ id: "balanced",
874
+ name: "Balanced",
875
+ description: "Three complementary lenses plus an independent judge for general decisions.",
876
+ seats: Object.freeze([
877
+ Object.freeze({ persona: "executor", target: Object.freeze({ role: "planner" }) }),
878
+ Object.freeze({ persona: "skeptic", target: Object.freeze({ role: "critic" }) }),
879
+ Object.freeze({ persona: "auditor", target: Object.freeze({ role: "analyst" }) })
880
+ ]),
881
+ judge: Object.freeze({ role: "reviewer" }),
882
+ quorumFraction: 0.5,
883
+ approvalFraction: 0.5,
884
+ distinctness: "model"
885
+ }),
886
+ Object.freeze({
887
+ id: "fast",
888
+ name: "Fast",
889
+ description: "Two-seat panel without a judge for quick, low-cost decisions.",
890
+ seats: Object.freeze([
891
+ Object.freeze({ persona: "executor", target: Object.freeze({ role: "planner" }) }),
892
+ Object.freeze({ persona: "skeptic", target: Object.freeze({ role: "critic" }) })
893
+ ]),
894
+ judge: false,
895
+ quorumFraction: 0.5,
896
+ approvalFraction: 0.5,
897
+ distinctness: "none",
898
+ voterMaxTokens: 200,
899
+ perCallTimeoutMs: 2e4,
900
+ overallTimeoutMs: 3e4
901
+ }),
902
+ Object.freeze({
903
+ id: "risk-review",
904
+ name: "Risk Review",
905
+ description: "Security, assumptions, maintenance, and cost review for high-impact choices.",
906
+ seats: Object.freeze([
907
+ Object.freeze({ persona: "skeptic", target: Object.freeze({ role: "critic" }) }),
908
+ Object.freeze({ persona: "security", target: Object.freeze({ role: "security-reviewer" }) }),
909
+ Object.freeze({ persona: "maintainer", target: Object.freeze({ role: "reviewer" }) }),
910
+ Object.freeze({ persona: "auditor", target: Object.freeze({ role: "analyst" }) })
911
+ ]),
912
+ judge: Object.freeze({ role: "architect" }),
913
+ quorumFraction: 0.75,
914
+ approvalFraction: 0.5,
915
+ distinctness: "provider",
916
+ judgeMaxTokens: 700,
917
+ overallTimeoutMs: 12e4
918
+ })
919
+ ]);
920
+ var CouncilProfileRegistry = class _CouncilProfileRegistry {
921
+ byId;
922
+ personas;
923
+ constructor(profiles = [], personas = DEFAULT_COUNCIL_PERSONA_REGISTRY) {
924
+ this.personas = personas;
925
+ const entries = /* @__PURE__ */ new Map();
926
+ for (const profile of profiles) {
927
+ const normalized = normalizeCouncilProfile(profile, personas);
928
+ if (entries.has(normalized.id)) {
929
+ throw new Error(`CouncilProfileRegistry: duplicate profile id "${normalized.id}".`);
930
+ }
931
+ entries.set(normalized.id, normalized);
932
+ }
933
+ this.byId = entries;
934
+ }
935
+ has(id) {
936
+ return this.byId.has(id);
937
+ }
938
+ get(id) {
939
+ return this.byId.get(id);
940
+ }
941
+ require(id) {
942
+ const profile = this.get(id);
943
+ if (!profile) throw new Error(`CouncilProfileRegistry: unknown profile "${id}".`);
944
+ return profile;
945
+ }
946
+ list() {
947
+ return Object.freeze([...this.byId.values()]);
948
+ }
949
+ /** Return a new registry; the current registry is never mutated. */
950
+ with(profile, opts = {}) {
951
+ const personas = opts.personas ?? this.personas;
952
+ const normalized = normalizeCouncilProfile(profile, personas);
953
+ if (this.has(normalized.id) && opts.replace !== true) {
954
+ throw new Error(`CouncilProfileRegistry: profile "${normalized.id}" already exists.`);
955
+ }
956
+ return new _CouncilProfileRegistry(
957
+ [
958
+ ...this.list().filter((entry) => entry.id !== normalized.id),
959
+ profile
960
+ ],
961
+ personas
962
+ );
963
+ }
964
+ };
965
+ var DEFAULT_COUNCIL_PROFILE_REGISTRY = new CouncilProfileRegistry(
966
+ BUILTIN_COUNCIL_PROFILES
967
+ );
968
+ function resolveCouncilProfile(profile, opts = {}) {
969
+ const personas = opts.personas ?? DEFAULT_COUNCIL_PERSONA_REGISTRY;
970
+ if (profile && typeof profile !== "string") return normalizeCouncilProfile(profile, personas);
971
+ const id = profile ?? opts.defaultProfile ?? "balanced";
972
+ return (opts.registry ?? DEFAULT_COUNCIL_PROFILE_REGISTRY).require(id);
973
+ }
974
+ function normalizeCouncilProfile(profile, personas = DEFAULT_COUNCIL_PERSONA_REGISTRY) {
975
+ const id = profile.id.trim();
976
+ if (!PROFILE_ID_RE.test(id)) {
977
+ throw new Error(`CouncilProfileRegistry: invalid profile id "${profile.id}".`);
978
+ }
979
+ if (profile.seats.length === 0) {
980
+ throw new Error(`CouncilProfileRegistry: profile "${id}" requires at least one seat.`);
981
+ }
982
+ const usedIds = /* @__PURE__ */ new Set();
983
+ const seats = profile.seats.map((seat) => {
984
+ const persona = personas.require(seat.persona.trim());
985
+ const explicitSeatId = seat.id?.trim();
986
+ if (explicitSeatId && usedIds.has(explicitSeatId)) {
987
+ throw new Error(`CouncilProfileRegistry: duplicate seat id "${explicitSeatId}".`);
988
+ }
989
+ const seatId = uniqueSeatId(explicitSeatId || persona.id, usedIds);
990
+ const label = seat.label?.trim() || persona.name;
991
+ const weight = seat.weight ?? persona.defaultWeight ?? 1;
992
+ if (!Number.isFinite(weight) || weight <= 0) {
993
+ throw new Error(`CouncilProfileRegistry: seat "${seatId}" has an invalid weight.`);
994
+ }
995
+ return Object.freeze({
996
+ id: seatId,
997
+ label,
998
+ persona: persona.id,
999
+ ...seat.target ? { target: freezeTarget(seat.target, `seat "${seatId}"`) } : {},
1000
+ weight,
1001
+ veto: seat.veto ?? persona.defaultVeto ?? false
1002
+ });
1003
+ });
1004
+ const quorumFraction = fraction(
1005
+ profile.quorumFraction ?? DEFAULT_COUNCIL_QUORUM_FRACTION,
1006
+ "quorumFraction",
1007
+ id
1008
+ );
1009
+ const approvalFraction = fraction(
1010
+ profile.approvalFraction ?? DEFAULT_COUNCIL_APPROVAL_FRACTION,
1011
+ "approvalFraction",
1012
+ id
1013
+ );
1014
+ const distinctness = profile.distinctness ?? "model";
1015
+ if (!DISTINCTNESS_VALUES.has(distinctness)) {
1016
+ throw new Error(`CouncilProfileRegistry: profile "${id}" has invalid distinctness.`);
1017
+ }
1018
+ const voterMaxTokens = positiveInteger(
1019
+ profile.voterMaxTokens ?? DEFAULT_COUNCIL_VOTER_MAX_TOKENS,
1020
+ "voterMaxTokens",
1021
+ id
1022
+ );
1023
+ const judgeMaxTokens = positiveInteger(
1024
+ profile.judgeMaxTokens ?? DEFAULT_COUNCIL_JUDGE_MAX_TOKENS,
1025
+ "judgeMaxTokens",
1026
+ id
1027
+ );
1028
+ const perCallTimeoutMs = positiveInteger(
1029
+ profile.perCallTimeoutMs ?? DEFAULT_COUNCIL_PER_CALL_TIMEOUT_MS,
1030
+ "perCallTimeoutMs",
1031
+ id
1032
+ );
1033
+ const overallTimeoutMs = positiveInteger(
1034
+ profile.overallTimeoutMs ?? DEFAULT_COUNCIL_OVERALL_TIMEOUT_MS,
1035
+ "overallTimeoutMs",
1036
+ id
1037
+ );
1038
+ if (overallTimeoutMs < perCallTimeoutMs) {
1039
+ throw new Error(
1040
+ `CouncilProfileRegistry: profile "${id}" overallTimeoutMs must be at least perCallTimeoutMs.`
1041
+ );
1042
+ }
1043
+ return Object.freeze({
1044
+ id,
1045
+ name: profile.name?.trim() || id,
1046
+ description: profile.description?.trim() || "",
1047
+ seats: Object.freeze(seats),
1048
+ judge: profile.judge ? freezeTarget(profile.judge, "judge") : false,
1049
+ quorumFraction,
1050
+ approvalFraction,
1051
+ distinctness,
1052
+ voterMaxTokens,
1053
+ judgeMaxTokens,
1054
+ perCallTimeoutMs,
1055
+ overallTimeoutMs
1056
+ });
1057
+ }
1058
+ function uniqueSeatId(base, used) {
1059
+ if (!PROFILE_ID_RE.test(base)) {
1060
+ throw new Error(`CouncilProfileRegistry: invalid seat id "${base}".`);
1061
+ }
1062
+ let id = base;
1063
+ let suffix = 2;
1064
+ while (used.has(id)) id = `${base}-${suffix++}`;
1065
+ used.add(id);
1066
+ return id;
1067
+ }
1068
+ function freezeTarget(target, label) {
1069
+ const providerId = optionalText(target.providerId);
1070
+ const model = optionalText(target.model);
1071
+ const role = optionalText(target.role);
1072
+ const fallbackProfile = optionalText(target.fallbackProfile);
1073
+ const fallbackModels = Object.freeze(
1074
+ [...new Set((target.fallbackModels ?? []).map((ref) => ref.trim()).filter(Boolean))]
1075
+ );
1076
+ if (!providerId && !model && !role && !fallbackProfile && fallbackModels.length === 0) {
1077
+ throw new Error(`CouncilProfileRegistry: ${label} target is empty.`);
1078
+ }
1079
+ return Object.freeze({
1080
+ ...providerId ? { providerId } : {},
1081
+ ...model ? { model } : {},
1082
+ ...role ? { role } : {},
1083
+ ...fallbackProfile ? { fallbackProfile } : {},
1084
+ ...fallbackModels.length > 0 ? { fallbackModels } : {}
1085
+ });
1086
+ }
1087
+ function optionalText(value) {
1088
+ const trimmed = value?.trim();
1089
+ return trimmed || void 0;
1090
+ }
1091
+ function fraction(value, field, profileId) {
1092
+ if (!Number.isFinite(value) || value <= 0 || value > 1) {
1093
+ throw new Error(`CouncilProfileRegistry: profile "${profileId}" ${field} must be in (0, 1].`);
1094
+ }
1095
+ return value;
1096
+ }
1097
+ function positiveInteger(value, field, profileId) {
1098
+ if (!Number.isSafeInteger(value) || value <= 0) {
1099
+ throw new Error(
1100
+ `CouncilProfileRegistry: profile "${profileId}" ${field} must be a positive integer.`
1101
+ );
1102
+ }
1103
+ return value;
1104
+ }
1105
+
1106
+ // src/utils/instruction-file.ts
1107
+ import { readFileSync, statSync } from "node:fs";
1108
+ import * as path2 from "node:path";
1109
+ import { fileURLToPath } from "node:url";
1110
+ var textCache = /* @__PURE__ */ new Map();
1111
+ var rootCandidates;
1112
+ function readBundledInstructionText(relativePath) {
1113
+ const cached = textCache.get(relativePath);
1114
+ if (cached !== void 0) return cached;
1115
+ let resolved = "";
1116
+ for (const root of instructionRootCandidates()) {
1117
+ try {
1118
+ resolved = readFileSync(path2.join(root, relativePath), "utf8").trimEnd();
1119
+ break;
1120
+ } catch {
1121
+ }
1122
+ }
1123
+ textCache.set(relativePath, resolved);
1124
+ return resolved;
1125
+ }
1126
+ function renderInstructionTemplate(template, values) {
1127
+ return template.replace(
1128
+ /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g,
1129
+ (match, key) => Object.hasOwn(values, key) ? values[key] ?? "" : match
1130
+ );
1131
+ }
1132
+ function instructionRootCandidates() {
1133
+ if (rootCandidates !== void 0) return rootCandidates;
1134
+ const here = path2.dirname(fileURLToPath(import.meta.url));
1135
+ const candidates = [
1136
+ path2.resolve(here, "../../instructions"),
1137
+ path2.resolve(here, "../instructions"),
1138
+ path2.resolve(here, "instructions")
1139
+ ];
1140
+ rootCandidates = candidates.sort((a, b) => Number(!isDirectory(a)) - Number(!isDirectory(b)));
1141
+ return rootCandidates;
1142
+ }
1143
+ function isDirectory(candidate) {
1144
+ try {
1145
+ return statSync(candidate).isDirectory();
1146
+ } catch {
1147
+ return false;
1148
+ }
1149
+ }
1150
+
1151
+ // src/execution/council-prompts.ts
1152
+ var COUNCIL_VOTER_PROMPT_PATH = "llm/council-voter.md";
1153
+ var COUNCIL_JUDGE_PROMPT_PATH = "llm/council-judge.md";
1154
+ function buildCouncilVoterSystemPrompt(persona) {
1155
+ const template = requiredInstruction(COUNCIL_VOTER_PROMPT_PATH);
1156
+ return renderInstructionTemplate(template, {
1157
+ personaInstruction: persona.instruction
1158
+ });
1159
+ }
1160
+ function buildCouncilJudgeSystemPrompt() {
1161
+ return requiredInstruction(COUNCIL_JUDGE_PROMPT_PATH);
1162
+ }
1163
+ function buildCouncilQuestionPrompt(question, opts = {}) {
1164
+ const text = question.question.trim();
1165
+ if (!text) throw new Error("buildCouncilQuestionPrompt: question must not be empty.");
1166
+ const options = normalizeOptions(question.options);
1167
+ return [
1168
+ "<council-question>",
1169
+ `Question: ${text}`,
1170
+ question.context?.trim() ? `Context:
1171
+ ${question.context.trim()}` : "",
1172
+ options.length > 0 ? `Options (JSON):
1173
+ ${JSON.stringify(options)}` : "Options: none",
1174
+ opts.refusalOptionId ? `Refusal option id: ${opts.refusalOptionId}` : "Refusal option id: not supplied",
1175
+ "</council-question>"
1176
+ ].filter(Boolean).join("\n\n");
1177
+ }
1178
+ function buildCouncilVoterUserPrompt(question, seat, opts = {}) {
1179
+ return [
1180
+ buildCouncilQuestionPrompt(question, opts),
1181
+ "<seat-metadata>",
1182
+ `Seat id: ${seat.id}`,
1183
+ `Seat label: ${seat.label}`,
1184
+ `Persona id: ${seat.persona}`,
1185
+ "</seat-metadata>"
1186
+ ].join("\n");
1187
+ }
1188
+ function buildCouncilJudgeUserPrompt(question, votes, opts = {}) {
1189
+ const ballots = votes.map((vote) => ({
1190
+ seatId: vote.seatId,
1191
+ persona: vote.persona,
1192
+ status: vote.status,
1193
+ ...vote.optionId ? { optionId: vote.optionId } : {},
1194
+ ...vote.stance ? { stance: vote.stance } : {},
1195
+ ...vote.rationale ? { rationale: vote.rationale } : {}
1196
+ }));
1197
+ return [
1198
+ buildCouncilQuestionPrompt(question, { refusalOptionId: opts.refusalOptionId }),
1199
+ "<council-ballots>",
1200
+ opts.reason?.trim() ? `Reason judging is required: ${opts.reason.trim()}` : "",
1201
+ JSON.stringify(ballots),
1202
+ "</council-ballots>"
1203
+ ].filter(Boolean).join("\n\n");
1204
+ }
1205
+ function normalizeOptions(options) {
1206
+ if (!options) return [];
1207
+ const seen = /* @__PURE__ */ new Set();
1208
+ return options.map((option) => {
1209
+ const id = option.id.trim();
1210
+ const label = option.label.trim();
1211
+ if (!id) throw new Error("buildCouncilQuestionPrompt: option id must not be empty.");
1212
+ if (!label) throw new Error(`buildCouncilQuestionPrompt: option "${id}" needs a label.`);
1213
+ if (seen.has(id)) throw new Error(`buildCouncilQuestionPrompt: duplicate option id "${id}".`);
1214
+ seen.add(id);
1215
+ return {
1216
+ id,
1217
+ label,
1218
+ ...option.consequence?.trim() ? { consequence: option.consequence.trim() } : {}
1219
+ };
1220
+ });
1221
+ }
1222
+ function requiredInstruction(path3) {
1223
+ const text = readBundledInstructionText(path3);
1224
+ if (!text) throw new Error(`Council instruction file is unavailable: ${path3}`);
1225
+ return text;
1226
+ }
1227
+
1228
+ // src/execution/council-resolution.ts
1229
+ function resolveCouncilVotes(input) {
1230
+ validateInput(input);
1231
+ const seatById = new Map(input.seats.map((seat) => [seat.id, seat]));
1232
+ const validVotes = [];
1233
+ const votedSeatIds = /* @__PURE__ */ new Set();
1234
+ for (const vote of input.votes) {
1235
+ if (!seatById.has(vote.seatId) || votedSeatIds.has(vote.seatId)) continue;
1236
+ votedSeatIds.add(vote.seatId);
1237
+ validVotes.push(vote);
1238
+ }
1239
+ if (validVotes.length / input.seats.length < input.quorumFraction) {
1240
+ return {
1241
+ status: "abstained",
1242
+ reason: "quorum_not_met",
1243
+ validVoteCount: validVotes.length,
1244
+ seatCount: input.seats.length
1245
+ };
1246
+ }
1247
+ const veto = validVotes.find(
1248
+ (vote) => vote.optionId === input.refusalOptionId && seatById.get(vote.seatId)?.veto === true
1249
+ );
1250
+ if (veto) {
1251
+ return {
1252
+ status: "denied",
1253
+ method: "veto",
1254
+ optionId: veto.optionId,
1255
+ seatId: veto.seatId
1256
+ };
1257
+ }
1258
+ const weightByOption = /* @__PURE__ */ new Map();
1259
+ let castWeight = 0;
1260
+ for (const vote of validVotes) {
1261
+ const weight = seatById.get(vote.seatId)?.weight ?? 1;
1262
+ castWeight += weight;
1263
+ weightByOption.set(vote.optionId, (weightByOption.get(vote.optionId) ?? 0) + weight);
1264
+ }
1265
+ let winner;
1266
+ let contested = false;
1267
+ for (const [optionId, weight] of weightByOption) {
1268
+ if (!winner || weight > winner.weight) {
1269
+ winner = { optionId, weight };
1270
+ contested = false;
1271
+ } else if (weight === winner.weight) {
1272
+ contested = true;
1273
+ }
1274
+ }
1275
+ const decisive = winner !== void 0 && !contested && winner.weight > input.approvalFraction * castWeight;
1276
+ if (!decisive || !winner) {
1277
+ return {
1278
+ status: "needs_judge",
1279
+ reason: contested ? "tie" : "approval_threshold_not_met",
1280
+ castWeight
1281
+ };
1282
+ }
1283
+ if (winner.optionId === input.refusalOptionId) {
1284
+ return {
1285
+ status: "denied",
1286
+ method: "refusal",
1287
+ optionId: winner.optionId,
1288
+ winningWeight: winner.weight,
1289
+ castWeight
1290
+ };
1291
+ }
1292
+ return {
1293
+ status: "decided",
1294
+ method: "majority",
1295
+ optionId: winner.optionId,
1296
+ winningWeight: winner.weight,
1297
+ castWeight
1298
+ };
1299
+ }
1300
+ function validateInput(input) {
1301
+ if (input.seats.length === 0) {
1302
+ throw new Error("resolveCouncilVotes: at least one seat is required.");
1303
+ }
1304
+ requireFraction(input.quorumFraction, "quorumFraction");
1305
+ requireFraction(input.approvalFraction, "approvalFraction");
1306
+ const seatIds = /* @__PURE__ */ new Set();
1307
+ for (const seat of input.seats) {
1308
+ if (!seat.id.trim()) throw new Error("resolveCouncilVotes: seat id must not be empty.");
1309
+ if (seatIds.has(seat.id)) {
1310
+ throw new Error(`resolveCouncilVotes: duplicate seat id "${seat.id}".`);
1311
+ }
1312
+ seatIds.add(seat.id);
1313
+ if (seat.weight !== void 0 && (!Number.isFinite(seat.weight) || seat.weight <= 0)) {
1314
+ throw new Error(`resolveCouncilVotes: invalid weight for seat "${seat.id}".`);
1315
+ }
1316
+ }
1317
+ }
1318
+ function requireFraction(value, label) {
1319
+ if (!Number.isFinite(value) || value <= 0 || value > 1) {
1320
+ throw new Error(`resolveCouncilVotes: ${label} must be in (0, 1].`);
1321
+ }
1322
+ }
1323
+
1324
+ // src/execution/council-orchestrator.ts
1325
+ var COUNCIL_REFUSAL_OPTION_ID = "council_refuse";
1326
+ var DEFAULT_COUNCIL_MAX_CONCURRENCY = 3;
1327
+ var MAX_COUNCIL_CONCURRENCY = 8;
1328
+ var CouncilOrchestrator = class {
1329
+ caller;
1330
+ personas;
1331
+ profiles;
1332
+ defaultProfile;
1333
+ maxConcurrency;
1334
+ refusalOptionId;
1335
+ constructor(opts) {
1336
+ this.caller = opts.caller;
1337
+ this.personas = opts.personas ?? DEFAULT_COUNCIL_PERSONA_REGISTRY;
1338
+ this.profiles = opts.profiles ?? DEFAULT_COUNCIL_PROFILE_REGISTRY;
1339
+ this.defaultProfile = opts.defaultProfile;
1340
+ this.maxConcurrency = validateConcurrency(
1341
+ opts.maxConcurrency ?? DEFAULT_COUNCIL_MAX_CONCURRENCY
1342
+ );
1343
+ this.refusalOptionId = opts.refusalOptionId?.trim() || COUNCIL_REFUSAL_OPTION_ID;
1344
+ }
1345
+ async ask(question) {
1346
+ const startedAt = Date.now();
1347
+ const profile = resolveCouncilProfile(question.profile, {
1348
+ registry: this.profiles,
1349
+ personas: this.personas,
1350
+ defaultProfile: this.defaultProfile
1351
+ });
1352
+ validateRefusalCollision(question, this.refusalOptionId);
1353
+ const timeoutSignal = AbortSignal.timeout(profile.overallTimeoutMs);
1354
+ const signal = question.signal ? AbortSignal.any([question.signal, timeoutSignal]) : timeoutSignal;
1355
+ const usage = {
1356
+ calls: 0,
1357
+ inputTokens: 0,
1358
+ outputTokens: 0,
1359
+ totalTokens: 0
1360
+ };
1361
+ const votes = await mapConcurrent(
1362
+ profile.seats,
1363
+ Math.min(this.maxConcurrency, profile.seats.length),
1364
+ async (seat) => {
1365
+ try {
1366
+ return await this.callSeat(question, profile, seat, signal, usage);
1367
+ } catch (error) {
1368
+ return {
1369
+ seatId: seat.id,
1370
+ persona: seat.persona,
1371
+ status: signal.aborted ? "cancelled" : "failed",
1372
+ error: errorMessage(error)
1373
+ };
1374
+ }
1375
+ }
1376
+ );
1377
+ const warnings = distinctnessWarnings(votes, profile);
1378
+ const errors = votes.filter((vote) => vote.status === "failed" || vote.status === "invalid").map((vote) => `${vote.seatId}: ${vote.error ?? vote.status}`);
1379
+ if (question.signal?.aborted) {
1380
+ return resultEnvelope({
1381
+ status: "cancelled",
1382
+ reason: "Council call cancelled.",
1383
+ resolution: "none",
1384
+ votes,
1385
+ profile,
1386
+ usage,
1387
+ startedAt,
1388
+ warnings,
1389
+ errors
1390
+ });
1391
+ }
1392
+ if (timeoutSignal.aborted) {
1393
+ return resultEnvelope({
1394
+ status: "failed",
1395
+ reason: "Council overall timeout exceeded.",
1396
+ resolution: "none",
1397
+ votes,
1398
+ profile,
1399
+ usage,
1400
+ startedAt,
1401
+ warnings,
1402
+ errors: [...errors, "Council overall timeout exceeded."]
1403
+ });
1404
+ }
1405
+ if (!question.options || question.options.length === 0) {
1406
+ return this.resolveOpenQuestion(
1407
+ question,
1408
+ profile,
1409
+ votes,
1410
+ signal,
1411
+ usage,
1412
+ startedAt,
1413
+ warnings,
1414
+ errors
1415
+ );
1416
+ }
1417
+ return this.resolveOptionQuestion(
1418
+ question,
1419
+ profile,
1420
+ votes,
1421
+ signal,
1422
+ usage,
1423
+ startedAt,
1424
+ warnings,
1425
+ errors
1426
+ );
1427
+ }
1428
+ async callSeat(question, profile, seat, signal, usage) {
1429
+ if (signal.aborted) return cancelledVote(seat);
1430
+ let persona;
1431
+ try {
1432
+ persona = this.personas.require(seat.persona);
1433
+ } catch (error) {
1434
+ return {
1435
+ seatId: seat.id,
1436
+ persona: seat.persona,
1437
+ status: "failed",
1438
+ error: errorMessage(error)
1439
+ };
1440
+ }
1441
+ const result = await this.safeCall({
1442
+ system: buildCouncilVoterSystemPrompt(persona),
1443
+ userPrompt: buildCouncilVoterUserPrompt(question, seat, {
1444
+ refusalOptionId: question.options?.length ? this.refusalOptionId : void 0
1445
+ }),
1446
+ target: seat.target,
1447
+ maxTokens: profile.voterMaxTokens,
1448
+ timeoutMs: profile.perCallTimeoutMs,
1449
+ signal,
1450
+ usage
1451
+ });
1452
+ const metadata = callMetadata(result);
1453
+ if (result.error) {
1454
+ return {
1455
+ seatId: seat.id,
1456
+ persona: seat.persona,
1457
+ status: signal.aborted ? "cancelled" : "failed",
1458
+ ...metadata,
1459
+ error: result.error
1460
+ };
1461
+ }
1462
+ const parsed = parseVote(result.text, question, this.refusalOptionId);
1463
+ if (!parsed.ok) {
1464
+ return {
1465
+ seatId: seat.id,
1466
+ persona: seat.persona,
1467
+ status: "invalid",
1468
+ ...metadata,
1469
+ error: parsed.error
1470
+ };
1471
+ }
1472
+ return {
1473
+ seatId: seat.id,
1474
+ persona: seat.persona,
1475
+ status: "valid",
1476
+ ...parsed.vote,
1477
+ ...metadata
1478
+ };
1479
+ }
1480
+ async resolveOptionQuestion(question, profile, votes, signal, usage, startedAt, warnings, errors) {
1481
+ const validVotes = votes.filter(
1482
+ (vote) => vote.status === "valid" && typeof vote.optionId === "string"
1483
+ );
1484
+ const resolution = resolveCouncilVotes({
1485
+ seats: profile.seats.map((seat) => ({
1486
+ id: seat.id,
1487
+ weight: seat.weight,
1488
+ veto: seat.veto
1489
+ })),
1490
+ votes: validVotes.map((vote) => ({ seatId: vote.seatId, optionId: vote.optionId })),
1491
+ refusalOptionId: this.refusalOptionId,
1492
+ quorumFraction: profile.quorumFraction,
1493
+ approvalFraction: profile.approvalFraction
1494
+ });
1495
+ if (resolution.status === "abstained") {
1496
+ return resultEnvelope({
1497
+ status: "abstained",
1498
+ reason: "Council quorum was not met.",
1499
+ resolution: "none",
1500
+ votes,
1501
+ profile,
1502
+ usage,
1503
+ startedAt,
1504
+ warnings,
1505
+ errors
1506
+ });
1507
+ }
1508
+ if (resolution.status === "denied") {
1509
+ return resultEnvelope({
1510
+ status: "denied",
1511
+ optionId: resolution.optionId,
1512
+ reason: `Council denied the proposal via ${resolution.method}.`,
1513
+ resolution: resolution.method,
1514
+ votes,
1515
+ profile,
1516
+ usage,
1517
+ startedAt,
1518
+ warnings,
1519
+ errors
1520
+ });
1521
+ }
1522
+ if (resolution.status === "decided") {
1523
+ return resultEnvelope({
1524
+ status: "decided",
1525
+ optionId: resolution.optionId,
1526
+ answer: optionLabel(question, resolution.optionId),
1527
+ resolution: "majority",
1528
+ votes,
1529
+ profile,
1530
+ usage,
1531
+ startedAt,
1532
+ warnings,
1533
+ errors
1534
+ });
1535
+ }
1536
+ if (!profile.judge) {
1537
+ return resultEnvelope({
1538
+ status: "abstained",
1539
+ reason: `Council requires a judge (${resolution.reason}), but this profile has none.`,
1540
+ resolution: "none",
1541
+ votes,
1542
+ profile,
1543
+ usage,
1544
+ startedAt,
1545
+ warnings,
1546
+ errors
1547
+ });
1548
+ }
1549
+ const judged = await this.callJudge(
1550
+ question,
1551
+ profile,
1552
+ votes,
1553
+ profile.judge,
1554
+ resolution.reason,
1555
+ signal,
1556
+ usage
1557
+ );
1558
+ if (!judged.ok) {
1559
+ return resultEnvelope({
1560
+ status: signal.aborted ? "cancelled" : "abstained",
1561
+ reason: judged.error,
1562
+ resolution: "none",
1563
+ votes,
1564
+ profile,
1565
+ usage,
1566
+ startedAt,
1567
+ warnings,
1568
+ errors: [...errors, judged.error],
1569
+ judgeUsed: true
1570
+ });
1571
+ }
1572
+ if (judged.value.optionId === this.refusalOptionId) {
1573
+ return resultEnvelope({
1574
+ status: "denied",
1575
+ optionId: this.refusalOptionId,
1576
+ reason: judged.value.rationale ?? "Council judge refused all options.",
1577
+ resolution: "judge",
1578
+ votes,
1579
+ profile,
1580
+ usage,
1581
+ startedAt,
1582
+ warnings,
1583
+ errors,
1584
+ judgeUsed: true
1585
+ });
1586
+ }
1587
+ return resultEnvelope({
1588
+ status: "decided",
1589
+ optionId: judged.value.optionId,
1590
+ answer: optionLabel(question, judged.value.optionId),
1591
+ reason: judged.value.rationale,
1592
+ resolution: "judge",
1593
+ votes,
1594
+ profile,
1595
+ usage,
1596
+ startedAt,
1597
+ warnings,
1598
+ errors,
1599
+ judgeUsed: true
1600
+ });
1601
+ }
1602
+ async resolveOpenQuestion(question, profile, votes, signal, usage, startedAt, warnings, errors) {
1603
+ const valid = votes.filter(
1604
+ (vote) => vote.status === "valid" && typeof vote.stance === "string"
1605
+ );
1606
+ if (valid.length / profile.seats.length < profile.quorumFraction) {
1607
+ return resultEnvelope({
1608
+ status: "abstained",
1609
+ reason: "Council quorum was not met.",
1610
+ resolution: "none",
1611
+ votes,
1612
+ profile,
1613
+ usage,
1614
+ startedAt,
1615
+ warnings,
1616
+ errors
1617
+ });
1618
+ }
1619
+ if (!profile.judge) {
1620
+ const first = valid[0];
1621
+ if (!first) {
1622
+ return resultEnvelope({
1623
+ status: "failed",
1624
+ reason: "Council produced no valid stance.",
1625
+ resolution: "none",
1626
+ votes,
1627
+ profile,
1628
+ usage,
1629
+ startedAt,
1630
+ warnings,
1631
+ errors
1632
+ });
1633
+ }
1634
+ return resultEnvelope({
1635
+ status: "decided",
1636
+ answer: first.stance,
1637
+ reason: first.rationale,
1638
+ resolution: "first_stance",
1639
+ votes,
1640
+ profile,
1641
+ usage,
1642
+ startedAt,
1643
+ warnings,
1644
+ errors
1645
+ });
1646
+ }
1647
+ const judged = await this.callJudge(
1648
+ question,
1649
+ profile,
1650
+ votes,
1651
+ profile.judge,
1652
+ "open_question_synthesis",
1653
+ signal,
1654
+ usage
1655
+ );
1656
+ if (!judged.ok) {
1657
+ return resultEnvelope({
1658
+ status: signal.aborted ? "cancelled" : "failed",
1659
+ reason: judged.error,
1660
+ resolution: "none",
1661
+ votes,
1662
+ profile,
1663
+ usage,
1664
+ startedAt,
1665
+ warnings,
1666
+ errors: [...errors, judged.error],
1667
+ judgeUsed: true
1668
+ });
1669
+ }
1670
+ return resultEnvelope({
1671
+ status: "decided",
1672
+ answer: judged.value.answer,
1673
+ reason: judged.value.rationale,
1674
+ resolution: "judge",
1675
+ votes,
1676
+ profile,
1677
+ usage,
1678
+ startedAt,
1679
+ warnings,
1680
+ errors,
1681
+ judgeUsed: true
1682
+ });
1683
+ }
1684
+ async callJudge(question, profile, votes, target, reason, signal, usage) {
1685
+ const result = await this.safeCall({
1686
+ system: buildCouncilJudgeSystemPrompt(),
1687
+ userPrompt: buildCouncilJudgeUserPrompt(question, votes, {
1688
+ reason,
1689
+ refusalOptionId: question.options?.length ? this.refusalOptionId : void 0
1690
+ }),
1691
+ target,
1692
+ maxTokens: profile.judgeMaxTokens,
1693
+ timeoutMs: profile.perCallTimeoutMs,
1694
+ signal,
1695
+ usage
1696
+ });
1697
+ if (result.error) return { ok: false, error: result.error };
1698
+ return parseJudge(result.text, question, this.refusalOptionId);
1699
+ }
1700
+ async safeCall(input) {
1701
+ try {
1702
+ const result = await this.caller.call({
1703
+ system: input.system,
1704
+ userPrompt: input.userPrompt,
1705
+ responseFormat: { type: "json_object" },
1706
+ maxTokens: input.maxTokens,
1707
+ timeoutMs: input.timeoutMs,
1708
+ signal: input.signal,
1709
+ ...input.target?.providerId ? { providerId: input.target.providerId } : {},
1710
+ ...input.target?.model ? { model: input.target.model } : {},
1711
+ ...input.target?.role ? { role: input.target.role } : {},
1712
+ ...input.target?.fallbackProfile ? { fallbackProfile: input.target.fallbackProfile } : {},
1713
+ ...input.target?.fallbackModels ? { fallbackModels: [...input.target.fallbackModels] } : {}
1714
+ });
1715
+ addUsage(input.usage, result);
1716
+ return result;
1717
+ } catch (error) {
1718
+ input.usage.calls += 1;
1719
+ return emptyCallResult(errorMessage(error));
1720
+ }
1721
+ }
1722
+ };
1723
+ function parseVote(text, question, refusalOptionId) {
1724
+ const parsed = parseObject(text);
1725
+ if (!parsed.ok) return parsed;
1726
+ const rationale = optionalString(parsed.value["rationale"]);
1727
+ if (question.options && question.options.length > 0) {
1728
+ const optionId = optionalString(parsed.value["optionId"]);
1729
+ const allowed = /* @__PURE__ */ new Set([...question.options.map((option) => option.id.trim()), refusalOptionId]);
1730
+ if (!optionId || !allowed.has(optionId)) {
1731
+ return { ok: false, error: "Voter returned an unknown or missing optionId." };
1732
+ }
1733
+ return { ok: true, vote: { optionId, ...rationale ? { rationale } : {} } };
1734
+ }
1735
+ const stance = optionalString(parsed.value["stance"]);
1736
+ if (!stance) return { ok: false, error: "Voter returned an empty or missing stance." };
1737
+ return { ok: true, vote: { stance, ...rationale ? { rationale } : {} } };
1738
+ }
1739
+ function parseJudge(text, question, refusalOptionId) {
1740
+ const parsed = parseObject(text);
1741
+ if (!parsed.ok) return parsed;
1742
+ const rationale = optionalString(parsed.value["rationale"]);
1743
+ if (question.options && question.options.length > 0) {
1744
+ const optionId = optionalString(parsed.value["optionId"]);
1745
+ const allowed = /* @__PURE__ */ new Set([...question.options.map((option) => option.id.trim()), refusalOptionId]);
1746
+ if (!optionId || !allowed.has(optionId)) {
1747
+ return { ok: false, error: "Judge returned an unknown or missing optionId." };
1748
+ }
1749
+ return { ok: true, value: { optionId, ...rationale ? { rationale } : {} } };
1750
+ }
1751
+ const answer = optionalString(parsed.value["answer"]);
1752
+ if (!answer) return { ok: false, error: "Judge returned an empty or missing answer." };
1753
+ return { ok: true, value: { answer, ...rationale ? { rationale } : {} } };
1754
+ }
1755
+ function parseObject(text) {
1756
+ const trimmed = text.trim();
1757
+ const first = trimmed.indexOf("{");
1758
+ const last = trimmed.lastIndexOf("}");
1759
+ if (first < 0 || last < first) return { ok: false, error: "LLM response did not contain JSON." };
1760
+ try {
1761
+ const value = JSON.parse(trimmed.slice(first, last + 1));
1762
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1763
+ return { ok: false, error: "LLM response JSON must be an object." };
1764
+ }
1765
+ return { ok: true, value };
1766
+ } catch (error) {
1767
+ return { ok: false, error: `Invalid LLM response JSON: ${errorMessage(error)}` };
1768
+ }
1769
+ }
1770
+ function resultEnvelope(input) {
1771
+ const validVoteCount = input.votes.filter((vote) => vote.status === "valid").length;
1772
+ return {
1773
+ status: input.status,
1774
+ ...input.answer ? { answer: input.answer } : {},
1775
+ ...input.optionId ? { optionId: input.optionId } : {},
1776
+ ...input.reason ? { reason: input.reason } : {},
1777
+ resolution: input.resolution,
1778
+ votes: Object.freeze([...input.votes]),
1779
+ configuredSeatCount: input.profile.seats.length,
1780
+ validVoteCount,
1781
+ distinctTargetCount: distinctTargetCount(input.votes, input.profile),
1782
+ judgeUsed: input.judgeUsed ?? false,
1783
+ usage: usageResult(input.usage, input.startedAt),
1784
+ ...input.warnings.length > 0 ? { warnings: Object.freeze([...input.warnings]) } : {},
1785
+ ...input.errors.length > 0 ? { errors: Object.freeze([...input.errors]) } : {}
1786
+ };
1787
+ }
1788
+ function callMetadata(result) {
1789
+ return {
1790
+ ...result.provider ? { provider: result.provider } : {},
1791
+ ...result.model ? { model: result.model } : {},
1792
+ ...result.fromFallback ? { fromFallback: true } : {},
1793
+ durationMs: result.durationMs
1794
+ };
1795
+ }
1796
+ function addUsage(usage, result) {
1797
+ usage.calls += 1;
1798
+ usage.inputTokens += result.tokens.input;
1799
+ usage.outputTokens += result.tokens.output;
1800
+ usage.totalTokens += result.tokens.total;
1801
+ }
1802
+ function usageResult(usage, startedAt) {
1803
+ return Object.freeze({ ...usage, durationMs: Math.max(0, Date.now() - startedAt) });
1804
+ }
1805
+ function cancelledVote(seat) {
1806
+ return { seatId: seat.id, persona: seat.persona, status: "cancelled", error: "Cancelled." };
1807
+ }
1808
+ function distinctTargetCount(votes, profile) {
1809
+ const keys = votes.filter((vote) => vote.status === "valid").map(
1810
+ (vote) => profile.distinctness === "provider" ? vote.provider : `${vote.provider ?? ""}/${vote.model ?? ""}`
1811
+ ).filter(Boolean);
1812
+ return new Set(keys).size;
1813
+ }
1814
+ function distinctnessWarnings(votes, profile) {
1815
+ if (profile.distinctness === "none") return [];
1816
+ const valid = votes.filter((vote) => vote.status === "valid");
1817
+ const distinct = distinctTargetCount(valid, profile);
1818
+ if (valid.length > 1 && distinct < valid.length) {
1819
+ return [
1820
+ `Council distinctness policy "${profile.distinctness}" was not met: ${distinct} distinct target(s) served ${valid.length} valid vote(s).`
1821
+ ];
1822
+ }
1823
+ return [];
1824
+ }
1825
+ function optionLabel(question, optionId) {
1826
+ if (!optionId) return void 0;
1827
+ return question.options?.find((option) => option.id.trim() === optionId)?.label.trim();
1828
+ }
1829
+ function validateRefusalCollision(question, refusalOptionId) {
1830
+ if (question.options?.some((option) => option.id.trim() === refusalOptionId)) {
1831
+ throw new Error(`CouncilOrchestrator: option id "${refusalOptionId}" is reserved.`);
1832
+ }
1833
+ }
1834
+ function validateConcurrency(value) {
1835
+ if (!Number.isSafeInteger(value) || value <= 0 || value > MAX_COUNCIL_CONCURRENCY) {
1836
+ throw new Error(
1837
+ `CouncilOrchestrator: maxConcurrency must be an integer in [1, ${MAX_COUNCIL_CONCURRENCY}].`
1838
+ );
1839
+ }
1840
+ return value;
1841
+ }
1842
+ async function mapConcurrent(items, concurrency, worker) {
1843
+ const results = new Array(items.length);
1844
+ let next = 0;
1845
+ const run = async () => {
1846
+ while (true) {
1847
+ const index = next++;
1848
+ if (index >= items.length) return;
1849
+ const item = items[index];
1850
+ if (item !== void 0) results[index] = await worker(item, index);
1851
+ }
1852
+ };
1853
+ await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => run()));
1854
+ return results;
1855
+ }
1856
+ function emptyCallResult(error) {
1857
+ return {
1858
+ text: "",
1859
+ model: "",
1860
+ provider: "",
1861
+ tokens: { input: 0, output: 0, total: 0 },
1862
+ durationMs: 0,
1863
+ fromFallback: false,
1864
+ error
1865
+ };
1866
+ }
1867
+ function optionalString(value) {
1868
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
1869
+ }
1870
+ function errorMessage(error) {
1871
+ return error instanceof Error ? error.message : String(error);
1872
+ }
1873
+
1874
+ // src/tools/council-tool.ts
1875
+ var COUNCIL_TOOL_NAME = "council";
1876
+ var MAX_COUNCIL_TOOL_OPTIONS = 12;
1877
+ var MAX_COUNCIL_QUESTION_CHARS = 2e4;
1878
+ var MAX_COUNCIL_CONTEXT_CHARS = 8e4;
1879
+ var INPUT_SCHEMA = {
1880
+ type: "object",
1881
+ properties: {
1882
+ question: {
1883
+ type: "string",
1884
+ description: "The decision or open question for the Council.",
1885
+ maxLength: MAX_COUNCIL_QUESTION_CHARS
1886
+ },
1887
+ context: {
1888
+ type: "string",
1889
+ description: "Optional evidence and constraints. Treated as untrusted quoted data.",
1890
+ maxLength: MAX_COUNCIL_CONTEXT_CHARS
1891
+ },
1892
+ options: {
1893
+ type: "array",
1894
+ maxItems: MAX_COUNCIL_TOOL_OPTIONS,
1895
+ items: {
1896
+ type: "object",
1897
+ properties: {
1898
+ id: { type: "string", description: "Stable option id." },
1899
+ label: { type: "string", description: "Human-readable option label." },
1900
+ consequence: { type: "string", description: "Optional consequence or trade-off." }
1901
+ },
1902
+ required: ["id", "label"],
1903
+ additionalProperties: false
1904
+ },
1905
+ description: "Optional bounded list of choices. Omit for an open-ended Council answer."
1906
+ },
1907
+ profile: {
1908
+ type: "string",
1909
+ description: "Registered Council profile id. Defaults to the host-configured profile."
1910
+ }
1911
+ },
1912
+ required: ["question"],
1913
+ additionalProperties: false
1914
+ };
1915
+ function createCouncilTool(opts) {
1916
+ const orchestrator = new CouncilOrchestrator(opts);
1917
+ return {
1918
+ name: COUNCIL_TOOL_NAME,
1919
+ description: "Ask an independent, multi-persona Council to evaluate a decision or synthesize an answer. Uses bounded parallel voters, quorum/veto/weighted resolution, optional judging, model routing, fallback chains, and cancellation.",
1920
+ usageHint: "Use for consequential or disputed decisions that benefit from independent lenses. Provide `options` for a vote or omit them for an open answer. Keep context evidence-focused; the Council treats it as untrusted data.",
1921
+ category: "meta",
1922
+ inputSchema: INPUT_SCHEMA,
1923
+ permission: "auto",
1924
+ mutating: false,
1925
+ riskTier: "safe",
1926
+ managesOwnTimeout: true,
1927
+ maxOutputBytes: 256e3,
1928
+ async execute(input, _ctx, { signal }) {
1929
+ const question = {
1930
+ question: input.question,
1931
+ ...input.context ? { context: input.context } : {},
1932
+ ...input.options ? { options: input.options } : {},
1933
+ ...input.profile ? { profile: input.profile } : {},
1934
+ signal
1935
+ };
1936
+ return orchestrator.ask(question);
1937
+ },
1938
+ validate: validateCouncilToolInput
1939
+ };
1940
+ }
1941
+ function validateCouncilToolInput(input) {
1942
+ const errors = [];
1943
+ const question = input.question?.trim() ?? "";
1944
+ if (!question) errors.push("`question` must not be empty.");
1945
+ if (question.length > MAX_COUNCIL_QUESTION_CHARS) {
1946
+ errors.push(`\`question\` must not exceed ${MAX_COUNCIL_QUESTION_CHARS} characters.`);
1947
+ }
1948
+ if ((input.context?.length ?? 0) > MAX_COUNCIL_CONTEXT_CHARS) {
1949
+ errors.push(`\`context\` must not exceed ${MAX_COUNCIL_CONTEXT_CHARS} characters.`);
1950
+ }
1951
+ if ((input.options?.length ?? 0) > MAX_COUNCIL_TOOL_OPTIONS) {
1952
+ errors.push(`\`options\` must not contain more than ${MAX_COUNCIL_TOOL_OPTIONS} items.`);
1953
+ }
1954
+ const ids = /* @__PURE__ */ new Set();
1955
+ for (const option of input.options ?? []) {
1956
+ const id = option.id.trim();
1957
+ if (!id) errors.push("Every option must have a non-empty `id`.");
1958
+ if (!option.label.trim()) errors.push(`Option "${id || "<empty>"}" must have a label.`);
1959
+ if (ids.has(id)) errors.push(`Duplicate option id "${id}".`);
1960
+ ids.add(id);
1961
+ }
1962
+ return errors;
1963
+ }
1964
+
1965
+ // src/types/blocks.ts
1966
+ function isTextBlock(b) {
1967
+ return b.type === "text";
1968
+ }
1969
+
1970
+ // src/types/provider.ts
1971
+ 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;
1972
+ var CONTENT_FILTER_RE = /content.(filter|policy|moderation)|safety (system|filter)/i;
1973
+ function classifyProviderError(status, body, message) {
1974
+ const type = body?.type;
1975
+ if (status === 0) return "network";
1976
+ if (status === 408) return "timeout";
1977
+ if (status === 599) return "stream_hang";
1978
+ if (type === "rate_limit_error" || status === 429) return "rate_limit";
1979
+ if (type === "overloaded_error" || status === 529) return "overloaded";
1980
+ if (status >= 500) return "server";
1981
+ if (type === "authentication_error" || type === "permission_error" || status === 401 || status === 403) {
1982
+ return "auth";
1983
+ }
1984
+ const text = [message, body?.message, type, body?.raw].filter(Boolean).join("\n");
1985
+ if (type === "content_filter" || CONTENT_FILTER_RE.test(text)) return "content_filter";
1986
+ if (status === 413 || status >= 400 && CONTEXT_OVERFLOW_RE.test(text)) {
1987
+ return "context_overflow";
1988
+ }
1989
+ if (status >= 400) return "invalid_request";
1990
+ return "unknown";
1991
+ }
1992
+ function isFallbackWorthy(kind) {
1993
+ return FALLBACK_WORTHY_BY_KIND[kind];
1994
+ }
1995
+ var FALLBACK_WORTHY_BY_KIND = {
1996
+ rate_limit: true,
1997
+ overloaded: true,
1998
+ server: true,
1999
+ timeout: true,
2000
+ network: true,
2001
+ stream_hang: true,
2002
+ auth: false,
2003
+ context_overflow: false,
2004
+ content_filter: false,
2005
+ invalid_request: false,
2006
+ unknown: false
2007
+ };
2008
+ var ProviderError = class extends WrongStackError {
2009
+ status;
2010
+ retryable;
2011
+ providerId;
2012
+ /** Canonical failure classification — see {@link ProviderErrorKind}. */
2013
+ kind;
2014
+ body;
2015
+ constructor(message, status, retryable, providerId, opts = {}) {
2016
+ const kind = opts.kind ?? classifyProviderError(status, opts.body, message);
2017
+ super({
2018
+ message,
2019
+ code: kindToCode(kind),
2020
+ subsystem: "provider",
2021
+ severity: status >= 500 ? "error" : "warning",
2022
+ recoverable: retryable,
2023
+ context: { providerId, status },
2024
+ cause: opts.cause
2025
+ });
2026
+ this.name = "ProviderError";
2027
+ this.status = status;
2028
+ this.retryable = retryable;
2029
+ this.providerId = providerId;
2030
+ this.kind = kind;
2031
+ this.body = opts.body;
2032
+ }
2033
+ /**
2034
+ * Render a one-line, user-facing description. Designed for the CLI/TUI
2035
+ * status line and the agent's retry warning. Avoids dumping raw JSON
2036
+ * (which is what users see today when a 529 lands and the log message
2037
+ * includes the full `{"type":"error",...}` body).
2038
+ *
2039
+ * Examples:
2040
+ * "minimax-coding-plan overloaded (529): High traffic detected. Upgrade for highspeed model. [req 06534785201de9c0…]"
2041
+ * "openai rate limited (429): Retry after 12s"
2042
+ * "anthropic invalid request (400): messages.0.role must be one of 'user'|'assistant'"
2043
+ * "groq HTTP 500 (server error)"
2044
+ */
2045
+ describe() {
2046
+ const kind = describeStatus(this.status, this.body?.type);
2047
+ const head = `${this.providerId} ${kind}`;
2048
+ const detail = this.body?.message?.trim();
2049
+ const reqId = this.body?.requestId ? ` [req ${this.body.requestId.slice(0, 16)}${this.body.requestId.length > 16 ? "\u2026" : ""}]` : "";
2050
+ if (detail && detail.length > 0) {
2051
+ return `${head}: ${truncate(detail, 240)}${reqId}`;
2052
+ }
2053
+ return `${head}${reqId}`;
2054
+ }
2055
+ };
2056
+ function describeStatus(status, type) {
2057
+ if (status === 0) return "network error";
2058
+ if (status === 599) return `stream hang (${status})`;
2059
+ if (type === "overloaded_error" || status === 529) return `overloaded (${status})`;
2060
+ if (type === "rate_limit_error" || status === 429) return `rate limited (${status})`;
2061
+ if (type === "authentication_error" || status === 401) return `auth failed (${status})`;
2062
+ if (type === "permission_error" || status === 403) return `forbidden (${status})`;
2063
+ if (type === "not_found_error" || status === 404) return `not found (${status})`;
2064
+ if (type === "content_filter") return `content filtered (${status})`;
2065
+ if (type === "invalid_request_error" || status === 400) return `invalid request (${status})`;
2066
+ if (status === 408) return `timeout (${status})`;
2067
+ if (status >= 500 && status < 600) return `HTTP ${status} (server error)`;
2068
+ if (type) return `${type} (${status})`;
2069
+ return `HTTP ${status}`;
2070
+ }
2071
+ var KIND_TO_CODE = {
2072
+ network: ERROR_CODES.PROVIDER_NETWORK_ERROR,
2073
+ timeout: ERROR_CODES.PROVIDER_NETWORK_ERROR,
2074
+ rate_limit: ERROR_CODES.PROVIDER_RATE_LIMITED,
2075
+ auth: ERROR_CODES.PROVIDER_AUTH_FAILED,
2076
+ overloaded: ERROR_CODES.PROVIDER_OVERLOADED,
2077
+ context_overflow: ERROR_CODES.PROVIDER_CONTEXT_OVERFLOW,
2078
+ server: ERROR_CODES.PROVIDER_SERVER_ERROR,
2079
+ stream_hang: ERROR_CODES.PROVIDER_SERVER_ERROR,
2080
+ content_filter: ERROR_CODES.PROVIDER_INVALID_REQUEST,
2081
+ invalid_request: ERROR_CODES.PROVIDER_INVALID_REQUEST,
2082
+ unknown: ERROR_CODES.PROVIDER_INVALID_REQUEST
2083
+ };
2084
+ function kindToCode(kind) {
2085
+ return KIND_TO_CODE[kind];
2086
+ }
2087
+
2088
+ // src/core/fallback-model.ts
2089
+ function visibleProviderModels(config, providerId, providerModels) {
2090
+ const entry = config.providers?.[providerId];
2091
+ return entry?.models !== void 0 ? [...entry.models] : providerModels;
2092
+ }
2093
+ function parseModelRef(ref) {
2094
+ const trimmed = ref.trim();
2095
+ const slash = trimmed.indexOf("/");
2096
+ if (slash !== -1) {
2097
+ return {
2098
+ provider: trimmed.slice(0, slash) || void 0,
2099
+ model: trimmed.slice(slash + 1).trim()
2100
+ };
2101
+ }
2102
+ const parts = trimmed.split(/\s+/);
2103
+ if (parts.length >= 2) {
2104
+ return { provider: parts[0], model: parts.slice(1).join(" ") };
2105
+ }
2106
+ return { model: trimmed };
2107
+ }
2108
+ function formatModelRef(ref, defaultProvider) {
2109
+ const provider = ref.provider ?? defaultProvider;
2110
+ return provider ? `${provider}/${ref.model}` : ref.model;
2111
+ }
2112
+ function normalizeModelRef(ref, defaultProvider) {
2113
+ const parsed = parseModelRef(ref);
2114
+ return formatModelRef(parsed, defaultProvider);
2115
+ }
2116
+ function fallbackProfileChain(config, profileName) {
2117
+ if (!profileName) return [];
2118
+ const chain = config.fallbackProfiles?.[profileName];
2119
+ return Array.isArray(chain) ? chain.filter((ref) => parseModelRef(ref).model) : [];
2120
+ }
2121
+ function providerHasKey(entry) {
2122
+ if (!entry) return false;
2123
+ if (typeof entry.apiKey === "string" && entry.apiKey.length > 0) return true;
2124
+ if (Array.isArray(entry.apiKeys) && entry.apiKeys.some((k) => k?.apiKey)) return true;
2125
+ if (Array.isArray(entry.envVars) && entry.envVars.some((v) => !!process.env[v])) return true;
2126
+ return false;
2127
+ }
2128
+ var SMART_DEFAULT_MAX = 4;
2129
+ function smartDefaultFallbackChain(config) {
2130
+ const leaderProvider = config.provider;
2131
+ const leaderModel = config.model;
2132
+ const providers = config.providers ?? {};
2133
+ const favoriteSet = new Set(
2134
+ (config.favoriteModels ?? []).map((ref) => normalizeModelRef(ref, leaderProvider))
2135
+ );
2136
+ const hasFavorites = favoriteSet.size > 0;
2137
+ const favoritesOnly = config.favoriteModelsOnly === true;
2138
+ const seen = /* @__PURE__ */ new Set();
2139
+ const favoriteRefs = [];
2140
+ const sameProvider = [];
2141
+ const crossProvider = [];
2142
+ const ids = Object.keys(providers).sort(
2143
+ (a, b) => a === leaderProvider ? -1 : b === leaderProvider ? 1 : a.localeCompare(b)
2144
+ );
2145
+ for (const id of ids) {
2146
+ const entry = providers[id];
2147
+ if (!providerHasKey(entry)) continue;
2148
+ const models = visibleProviderModels(config, id, entry?.models ?? []);
2149
+ for (const model of models) {
2150
+ if (id === leaderProvider && model === leaderModel) continue;
2151
+ const ref = `${id}/${model}`;
2152
+ if (seen.has(ref)) continue;
2153
+ seen.add(ref);
2154
+ if (favoriteSet.has(ref)) {
2155
+ favoriteRefs.push(ref);
2156
+ continue;
2157
+ }
2158
+ if (favoritesOnly && hasFavorites) continue;
2159
+ (id === leaderProvider ? sameProvider : crossProvider).push(ref);
2160
+ }
2161
+ }
2162
+ return [...favoriteRefs, ...sameProvider, ...crossProvider].slice(0, SMART_DEFAULT_MAX);
2163
+ }
2164
+ function effectiveFallbackChain(config) {
2165
+ const explicit = config.fallbackModels ?? [];
2166
+ const filteredExplicit = explicit.filter((ref) => {
2167
+ const parsed = parseModelRef(ref);
2168
+ if (!parsed.model) return false;
2169
+ const providerId = parsed.provider ?? config.provider;
2170
+ const entry = config.providers?.[providerId];
2171
+ if (!entry?.models) return true;
2172
+ return entry.models.includes(parsed.model);
2173
+ });
2174
+ if (filteredExplicit.length > 0) return filteredExplicit;
2175
+ if (config.fallbackAuto === false) return [];
2176
+ return smartDefaultFallbackChain(config);
2177
+ }
2178
+ var DEFAULT_PRIMARY_COOLDOWN_MAX_MS = 10 * 6e4;
2179
+
2180
+ // src/execution/one-shot-llm.ts
2181
+ var DEFAULT_TIMEOUT_MS = 3e4;
2182
+ var DEFAULT_MAX_TOKENS = 1024;
2183
+ var OneShotOrchestrator = class {
2184
+ opts;
2185
+ constructor(opts) {
2186
+ this.opts = opts;
2187
+ }
2188
+ /**
2189
+ * Make a one-shot LLM call. Resolves provider+model, applies fallback
2190
+ * chain on transient errors, and returns a structured result.
2191
+ *
2192
+ * Never throws — all errors are captured in `OneShotLLMResult.error`.
2193
+ */
2194
+ async call(input) {
2195
+ const startedAt = performance.now();
2196
+ const config = this.opts.getConfig();
2197
+ const target = this.resolveTarget(input, config);
2198
+ if (!target) {
2199
+ return {
2200
+ text: "",
2201
+ model: input.model ?? config.model ?? "unknown",
2202
+ provider: input.providerId ?? config.provider ?? "unknown",
2203
+ tokens: { input: 0, output: 0, total: 0 },
2204
+ durationMs: Math.round(performance.now() - startedAt),
2205
+ fromFallback: false,
2206
+ error: "No provider or model could be resolved. Check your config."
2207
+ };
2208
+ }
2209
+ let provider;
2210
+ try {
2211
+ provider = await this.opts.buildProvider(target.providerId, target.model);
2212
+ } catch (err) {
2213
+ return {
2214
+ text: "",
2215
+ model: target.model,
2216
+ provider: target.providerId,
2217
+ tokens: { input: 0, output: 0, total: 0 },
2218
+ durationMs: Math.round(performance.now() - startedAt),
2219
+ fromFallback: false,
2220
+ error: `Cannot build provider "${target.providerId}": ${err instanceof Error ? err.message : String(err)}`
2221
+ };
2222
+ }
2223
+ const request = this.buildRequest(input, target.model);
2224
+ const signal = this.resolveSignal(input);
2225
+ const chain = this.resolveFallbackChain(input, config, target);
2226
+ let servingProviderId = provider.id;
2227
+ let servingModel = target.model;
2228
+ let fromFallback = false;
2229
+ const primaryAttempt = await this.tryCall(provider, request, signal);
2230
+ let result = primaryAttempt.response;
2231
+ let lastError = primaryAttempt.error;
2232
+ let fallbackEligible = primaryAttempt.fallbackEligible;
2233
+ if (!result && fallbackEligible && chain.length > 0) {
2234
+ for (const ref of chain) {
2235
+ const parsed = parseModelRef(ref);
2236
+ if (!parsed.model) continue;
2237
+ const fbProviderId = parsed.provider ?? config.provider;
2238
+ if (fbProviderId === provider.id && parsed.model === target.model) continue;
2239
+ servingProviderId = fbProviderId;
2240
+ servingModel = parsed.model;
2241
+ let fbProvider;
2242
+ try {
2243
+ fbProvider = await this.opts.buildProvider(fbProviderId, parsed.model);
2244
+ } catch (err) {
2245
+ lastError = err;
2246
+ continue;
2247
+ }
2248
+ servingProviderId = fbProvider.id;
2249
+ const attempt = await this.tryCall(
2250
+ fbProvider,
2251
+ this.buildRequest(input, parsed.model),
2252
+ signal
2253
+ );
2254
+ if (attempt.response) {
2255
+ result = attempt.response;
2256
+ fromFallback = true;
2257
+ break;
2258
+ }
2259
+ lastError = attempt.error;
2260
+ fallbackEligible = attempt.fallbackEligible;
2261
+ if (!fallbackEligible) break;
2262
+ }
2263
+ }
2264
+ const elapsed = Math.round(performance.now() - startedAt);
2265
+ if (result) {
2266
+ const textBlocks = result.content.filter(isTextBlock);
2267
+ const text = textBlocks.map((b) => b.text).join("\n").trim();
2268
+ return {
2269
+ text: text || "(empty response)",
2270
+ model: result.model ?? servingModel,
2271
+ provider: servingProviderId,
2272
+ tokens: {
2273
+ input: result.usage?.input ?? 0,
2274
+ output: result.usage?.output ?? 0,
2275
+ total: (result.usage?.input ?? 0) + (result.usage?.output ?? 0)
2276
+ },
2277
+ durationMs: elapsed,
2278
+ fromFallback,
2279
+ stopReason: result.stopReason
2280
+ };
2281
+ }
2282
+ return {
2283
+ text: "",
2284
+ model: servingModel,
2285
+ provider: servingProviderId,
2286
+ tokens: { input: 0, output: 0, total: 0 },
2287
+ durationMs: elapsed,
2288
+ fromFallback,
2289
+ error: lastError instanceof Error ? lastError.message : String(lastError ?? "Unknown error")
2290
+ };
2291
+ }
2292
+ // ── Private helpers ─────────────────────────────────────────────
2293
+ /**
2294
+ * Resolve the target provider + model from input + config.
2295
+ * Priority: role-based routing > explicit providerId+model > defaults.
2296
+ */
2297
+ resolveTarget(input, config) {
2298
+ if (input.role && this.opts.modelRouter) {
2299
+ const pick = this.opts.modelRouter.pickForTask(input.role, "");
2300
+ if (pick) {
2301
+ return { providerId: pick.provider, model: pick.model };
2302
+ }
2303
+ }
2304
+ if (input.providerId && input.model) {
2305
+ return { providerId: input.providerId, model: input.model };
2306
+ }
2307
+ if (input.model) {
2308
+ return { providerId: config.provider, model: input.model };
2309
+ }
2310
+ if (input.providerId) {
2311
+ return { providerId: input.providerId, model: config.model };
2312
+ }
2313
+ if (config.provider && config.model) {
2314
+ return { providerId: config.provider, model: config.model };
2315
+ }
2316
+ return void 0;
2317
+ }
2318
+ /**
2319
+ * Build a Provider Request from the input.
2320
+ */
2321
+ buildRequest(input, model) {
2322
+ const messages = [...input.messages ?? []];
2323
+ if (input.userPrompt) {
2324
+ messages.push({ role: "user", content: input.userPrompt });
2325
+ }
2326
+ const system = asTextBlocks(input.system);
2327
+ return {
2328
+ model,
2329
+ ...system.length > 0 ? { system } : {},
2330
+ messages,
2331
+ maxTokens: input.maxTokens ?? DEFAULT_MAX_TOKENS,
2332
+ ...input.temperature !== void 0 ? { temperature: input.temperature } : {},
2333
+ ...input.responseFormat ? { responseFormat: input.responseFormat } : {}
2334
+ };
2335
+ }
2336
+ /**
2337
+ * Resolve the abort signal. Provider calls always receive the per-call
2338
+ * timeout, composed with external cancellation when the caller supplies it.
2339
+ */
2340
+ resolveSignal(input) {
2341
+ const timeoutSignal = AbortSignal.timeout(input.timeoutMs ?? DEFAULT_TIMEOUT_MS);
2342
+ return input.signal ? AbortSignal.any([input.signal, timeoutSignal]) : timeoutSignal;
2343
+ }
2344
+ /**
2345
+ * Build the fallback model chain from input + config + current target.
2346
+ */
2347
+ resolveFallbackChain(input, config, target) {
2348
+ if (input.fallbackModels && input.fallbackModels.length > 0) {
2349
+ return input.fallbackModels;
2350
+ }
2351
+ if (input.fallbackProfile) {
2352
+ const profile = fallbackProfileChain(config, input.fallbackProfile);
2353
+ if (profile.length > 0) return profile;
2354
+ }
2355
+ if (config.fallbackAuto !== false) {
2356
+ return effectiveFallbackChain(config).filter((ref) => {
2357
+ const parsed = parseModelRef(ref);
2358
+ const fbProvider = parsed.provider ?? config.provider;
2359
+ return !(fbProvider === target.providerId && parsed.model === target.model);
2360
+ });
2361
+ }
2362
+ return [];
2363
+ }
2364
+ /** Attempt a provider call while preserving the actual failure for callers. */
2365
+ async tryCall(provider, request, signal) {
2366
+ try {
2367
+ return {
2368
+ response: await provider.complete(request, { signal }),
2369
+ fallbackEligible: false
2370
+ };
2371
+ } catch (err) {
2372
+ return {
2373
+ error: err,
2374
+ fallbackEligible: !signal.aborted && (!(err instanceof ProviderError) || isFallbackWorthy(err.kind))
2375
+ };
2376
+ }
2377
+ }
2378
+ };
2379
+ function asTextBlocks(system) {
2380
+ if (!system) return [];
2381
+ if (Array.isArray(system)) return system;
2382
+ return [{ type: "text", text: system }];
2383
+ }
2384
+
2385
+ // src/tools/one-shot-llm-tool.ts
2386
+ var ONE_SHOT_LLM_TOOL_NAME = "llm";
2387
+ var INPUT_SCHEMA2 = {
2388
+ type: "object",
2389
+ properties: {
2390
+ system: {
2391
+ type: "string",
2392
+ description: "System prompt guiding the LLM behaviour."
2393
+ },
2394
+ userPrompt: {
2395
+ type: "string",
2396
+ description: "Single user turn \u2014 appended as a user-role message."
2397
+ },
2398
+ messages: {
2399
+ type: "array",
2400
+ items: {
2401
+ type: "object",
2402
+ properties: {
2403
+ role: {
2404
+ type: "string",
2405
+ enum: ["system", "user", "assistant", "tool"],
2406
+ description: "Message author role."
2407
+ },
2408
+ content: {
2409
+ type: "string",
2410
+ description: "Message text content."
2411
+ }
2412
+ },
2413
+ required: ["role", "content"],
2414
+ additionalProperties: true
2415
+ },
2416
+ description: "Conversation messages for few-shot examples or multi-turn context. Appended before userPrompt when both are set."
2417
+ },
2418
+ model: {
2419
+ type: "string",
2420
+ description: 'Model id (e.g. "deepseek-chat", "gpt-4o-mini"). Defaults to session model.'
2421
+ },
2422
+ providerId: {
2423
+ type: "string",
2424
+ description: 'Provider id (e.g. "anthropic", "openai"). Defaults to session provider.'
2425
+ },
2426
+ role: {
2427
+ type: "string",
2428
+ description: "Roster role for model-matrix routing. Overrides model/providerId."
2429
+ },
2430
+ fallbackProfile: {
2431
+ type: "string",
2432
+ description: "Named fallback profile from config.fallbackProfiles."
2433
+ },
2434
+ fallbackModels: {
2435
+ type: "array",
2436
+ items: { type: "string" },
2437
+ description: 'Explicit fallback model chain (e.g. ["anthropic/claude-haiku", "openai/gpt-4o-mini"]).'
2438
+ },
2439
+ maxTokens: {
2440
+ type: "number",
2441
+ description: "Maximum output tokens (default 1024)."
2442
+ },
2443
+ responseFormat: {
2444
+ oneOf: [
2445
+ { type: "string", enum: ["text", "json_object"], description: "Simple response format." },
2446
+ {
2447
+ type: "object",
2448
+ properties: {
2449
+ type: { type: "string", enum: ["json_schema"], description: "Structured JSON output." },
2450
+ json_schema: {
2451
+ type: "object",
2452
+ description: "JSON Schema definition for the structured output."
2453
+ }
2454
+ },
2455
+ required: ["type"],
2456
+ additionalProperties: false
2457
+ }
2458
+ ],
2459
+ description: 'Response format: "text" (default), "json_object", or { type: "json_schema", json_schema: {...} }.'
2460
+ },
2461
+ temperature: {
2462
+ type: "number",
2463
+ description: "Sampling temperature."
2464
+ },
2465
+ timeoutMs: {
2466
+ type: "number",
2467
+ description: "Hard timeout in ms (default 30s)."
2468
+ }
2469
+ }
2470
+ };
2471
+ function createOneShotLLMTool(opts) {
2472
+ const orchestrator = new OneShotOrchestrator({
2473
+ buildProvider: opts.buildProvider,
2474
+ getConfig: opts.getConfig,
2475
+ modelRouter: opts.modelRouter,
2476
+ logger: opts.logger
2477
+ });
2478
+ return {
2479
+ name: ONE_SHOT_LLM_TOOL_NAME,
2480
+ description: "Make a one-shot LLM call with a system prompt and user input. Supports provider selection, model routing by role, fallback chains, and timeout. Returns the response text, model info, token usage, and whether a fallback was used. Use this for summarization, classification, extraction, and any single-turn LLM task.",
2481
+ usageHint: "Provide `system` for the instruction and `userPrompt` for the input. Either set `model`+`providerId`, or have defaults configured on the tool. Set `fallbackProfile` or `fallbackModels` for resilience. Check `error` on the result for failure details.",
2482
+ inputSchema: INPUT_SCHEMA2,
2483
+ permission: "auto",
2484
+ mutating: false,
2485
+ async execute(input, _ctx, { signal }) {
2486
+ if (!input.model && !input.providerId && !opts.defaultModel && !opts.defaultProvider) {
2487
+ return {
2488
+ text: "",
2489
+ model: "",
2490
+ provider: "",
2491
+ tokens: { input: 0, output: 0, total: 0 },
2492
+ durationMs: 0,
2493
+ fromFallback: false,
2494
+ error: "Either provide `model` and `providerId` in the call, or configure defaultProvider/defaultModel when creating the tool. The `llm` tool does not infer provider/model from the session by default."
2495
+ };
2496
+ }
2497
+ const effectiveInput = {
2498
+ ...input,
2499
+ signal: input.signal ? AbortSignal.any([input.signal, signal]) : signal,
2500
+ model: input.model ?? opts.defaultModel,
2501
+ providerId: input.providerId ?? opts.defaultProvider
2502
+ };
2503
+ return orchestrator.call(effectiveInput);
2504
+ }
2505
+ };
2506
+ }
642
2507
  export {
643
- createMcpControlTool
2508
+ COUNCIL_TOOL_NAME,
2509
+ MAX_COUNCIL_CONTEXT_CHARS,
2510
+ MAX_COUNCIL_QUESTION_CHARS,
2511
+ MAX_COUNCIL_TOOL_OPTIONS,
2512
+ ONE_SHOT_LLM_TOOL_NAME,
2513
+ createCouncilTool,
2514
+ createMcpControlTool,
2515
+ createOneShotLLMTool
644
2516
  };
645
2517
  //# sourceMappingURL=index.js.map