@wrongstack/core 0.310.1 → 0.313.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/dist/chronicle/index.js +84 -6391
  2. package/dist/chronicle/project-server.js +84 -4868
  3. package/dist/chunk-2BUUIMQB.js +225 -0
  4. package/dist/chunk-2FK4WTFF.js +207 -0
  5. package/dist/chunk-2FQL4PDC.js +292 -0
  6. package/dist/chunk-2KKODIQU.js +2256 -0
  7. package/dist/chunk-2YU4WVBW.js +134 -0
  8. package/dist/chunk-3E244UDA.js +356 -0
  9. package/dist/chunk-3XPVB7BA.js +135 -0
  10. package/dist/chunk-4LJJYBAU.js +388 -0
  11. package/dist/chunk-4VWO3ZXL.js +342 -0
  12. package/dist/chunk-4XV6IR37.js +187 -0
  13. package/dist/chunk-4ZHTKJHM.js +183 -0
  14. package/dist/chunk-52RTVH4M.js +361 -0
  15. package/dist/chunk-57GEWK5X.js +26 -0
  16. package/dist/chunk-5DSMEVA4.js +931 -0
  17. package/dist/chunk-5RP3QTRL.js +1223 -0
  18. package/dist/chunk-5S437PRC.js +50 -0
  19. package/dist/chunk-5XDUGTKN.js +511 -0
  20. package/dist/chunk-62V2AFVG.js +422 -0
  21. package/dist/chunk-65RDIOR2.js +2470 -0
  22. package/dist/chunk-6AKOTGLM.js +597 -0
  23. package/dist/chunk-7ZHZE5EO.js +28 -0
  24. package/dist/chunk-A455ATZB.js +140 -0
  25. package/dist/chunk-AD7ZFZCS.js +46 -0
  26. package/dist/chunk-AI4LLQ2U.js +856 -0
  27. package/dist/chunk-ASX6AW36.js +789 -0
  28. package/dist/chunk-AT4V4XGI.js +3522 -0
  29. package/dist/chunk-AWNQ6ED6.js +311 -0
  30. package/dist/chunk-B4AMLZPB.js +9 -0
  31. package/dist/chunk-BAKVJLEV.js +417 -0
  32. package/dist/chunk-BJHVULHO.js +55 -0
  33. package/dist/chunk-CB2HFKTP.js +9 -0
  34. package/dist/chunk-CE4ZOCOI.js +1640 -0
  35. package/dist/chunk-CQQL4ZOQ.js +151 -0
  36. package/dist/chunk-DEIQMBD2.js +65 -0
  37. package/dist/chunk-E6I5RXVK.js +58 -0
  38. package/dist/chunk-EFEHDQGY.js +1589 -0
  39. package/dist/chunk-EODDVJBM.js +82 -0
  40. package/dist/chunk-EPC3GZD4.js +308 -0
  41. package/dist/chunk-FAYRX2AD.js +13 -0
  42. package/dist/chunk-FFSX2QDQ.js +362 -0
  43. package/dist/chunk-FHAMQS7Q.js +5856 -0
  44. package/dist/chunk-FIV4IJL7.js +2263 -0
  45. package/dist/chunk-FNXVT7HF.js +52 -0
  46. package/dist/chunk-FSWCWSTS.js +395 -0
  47. package/dist/chunk-FW5LDDMJ.js +16464 -0
  48. package/dist/chunk-FXQLIX3K.js +211 -0
  49. package/dist/chunk-G5AAKEEZ.js +228 -0
  50. package/dist/chunk-G5S3S5ZN.js +38 -0
  51. package/dist/chunk-GAZEWQEZ.js +231 -0
  52. package/dist/chunk-GFJOMSHG.js +125 -0
  53. package/dist/chunk-GFXZTHNI.js +158 -0
  54. package/dist/chunk-GM54GJBU.js +960 -0
  55. package/dist/chunk-GPQ3NJPR.js +900 -0
  56. package/dist/chunk-GTCU6DZ3.js +1 -0
  57. package/dist/chunk-H5E77KUJ.js +15 -0
  58. package/dist/chunk-H72H4XDU.js +1023 -0
  59. package/dist/chunk-HA4ZYA6T.js +178 -0
  60. package/dist/chunk-I45KAW2N.js +5020 -0
  61. package/dist/chunk-IQ35HMQL.js +1396 -0
  62. package/dist/chunk-IYFBFX34.js +86 -0
  63. package/dist/chunk-JJPMWPB6.js +109 -0
  64. package/dist/chunk-JTHKOYWU.js +663 -0
  65. package/dist/chunk-JVGPR3TS.js +365 -0
  66. package/dist/chunk-JVKB2TBL.js +48 -0
  67. package/dist/chunk-JX7VBZDK.js +31 -0
  68. package/dist/chunk-K3G6JBUC.js +5232 -0
  69. package/dist/chunk-K4YUNKU7.js +589 -0
  70. package/dist/chunk-KBZGE6LN.js +710 -0
  71. package/dist/chunk-KDQJCT2J.js +45 -0
  72. package/dist/chunk-KI4L2DUL.js +560 -0
  73. package/dist/chunk-KJ2ZTQSL.js +47 -0
  74. package/dist/chunk-KSJ5UTP7.js +154 -0
  75. package/dist/chunk-LHOA5DWA.js +28 -0
  76. package/dist/chunk-LX5QWLBL.js +27 -0
  77. package/dist/chunk-LZDDLNCZ.js +279 -0
  78. package/dist/chunk-M2WF6AXG.js +115 -0
  79. package/dist/chunk-M7BS2K6U.js +1 -0
  80. package/dist/chunk-NDBIPFNY.js +102 -0
  81. package/dist/chunk-NEMIWEXC.js +1199 -0
  82. package/dist/chunk-NFAYHPRH.js +323 -0
  83. package/dist/chunk-O37BQ65S.js +21 -0
  84. package/dist/chunk-O74MJHXU.js +187 -0
  85. package/dist/chunk-OJZEX73F.js +112 -0
  86. package/dist/chunk-PAAWLLZX.js +74 -0
  87. package/dist/chunk-PFXFQQRT.js +339 -0
  88. package/dist/chunk-PFZO3C6H.js +14 -0
  89. package/dist/chunk-PHH7LD45.js +113 -0
  90. package/dist/chunk-QRTG4XTI.js +188 -0
  91. package/dist/chunk-QVOVMXJR.js +1351 -0
  92. package/dist/chunk-RCQYT2I4.js +757 -0
  93. package/dist/chunk-RD6DEE36.js +1178 -0
  94. package/dist/chunk-RJIZEJ42.js +30 -0
  95. package/dist/chunk-RPOA7IAE.js +150 -0
  96. package/dist/chunk-SN2DQRL6.js +1172 -0
  97. package/dist/chunk-SQJVFEBV.js +41 -0
  98. package/dist/chunk-SQP64RCS.js +5391 -0
  99. package/dist/chunk-SVT4RNTR.js +49 -0
  100. package/dist/chunk-SWUFN7XM.js +43 -0
  101. package/dist/chunk-SXMVYC5H.js +174 -0
  102. package/dist/chunk-SZ6AYUIZ.js +23 -0
  103. package/dist/chunk-T3KGZBU5.js +3171 -0
  104. package/dist/chunk-TBRL2RCC.js +9 -0
  105. package/dist/chunk-TRT2HRWZ.js +18 -0
  106. package/dist/chunk-TYW2GI5I.js +1077 -0
  107. package/dist/chunk-UIWONWVG.js +4959 -0
  108. package/dist/chunk-VB3H5WWB.js +151 -0
  109. package/dist/chunk-W4A6QA3D.js +258 -0
  110. package/dist/chunk-WBPVKEEE.js +227 -0
  111. package/dist/chunk-WEBMSQP2.js +123 -0
  112. package/dist/chunk-WOEZV3Q4.js +333 -0
  113. package/dist/chunk-WQQON3SV.js +473 -0
  114. package/dist/chunk-WQTOVMGV.js +119 -0
  115. package/dist/chunk-WZO23AWO.js +2779 -0
  116. package/dist/chunk-XBDP4QPW.js +7 -0
  117. package/dist/chunk-XEBHSJLT.js +103 -0
  118. package/dist/chunk-XQ2ECNK3.js +1 -0
  119. package/dist/chunk-XQ2XGIBF.js +2086 -0
  120. package/dist/chunk-Y2GE5TSI.js +355 -0
  121. package/dist/chunk-YBUJCZ57.js +351 -0
  122. package/dist/chunk-YNCVSFGF.js +149 -0
  123. package/dist/chunk-YUPMXOGP.js +282 -0
  124. package/dist/chunk-YY6APYUM.js +49 -0
  125. package/dist/chunk-Z2IJUO45.js +691 -0
  126. package/dist/chunk-ZEHUCJHY.js +129 -0
  127. package/dist/chunk-ZYU2YOJ6.js +1496 -0
  128. package/dist/chunk-ZZ4V3WIZ.js +163 -0
  129. package/dist/coordination/agents/index.js +145 -6260
  130. package/dist/coordination/index.js +365 -34323
  131. package/dist/coordination/mailbox-project-server.js +48 -3220
  132. package/dist/coordination/mailbox-types.d.ts +13 -1
  133. package/dist/coordination/provider-status-tracker.d.ts +4 -0
  134. package/dist/coordination/sqlite-mailbox.d.ts +2 -1
  135. package/dist/core/agent.d.ts +14 -1
  136. package/dist/core/index.js +267 -12479
  137. package/dist/core/model-ref.js +5 -23
  138. package/dist/core/provider-runner.d.ts +36 -1
  139. package/dist/design/index.js +50 -1087
  140. package/dist/execution/index.d.ts +1 -1
  141. package/dist/execution/index.js +1599 -21887
  142. package/dist/execution/prompt-enhancer.js +16 -535
  143. package/dist/execution/retry-policy.d.ts +31 -1
  144. package/dist/extension/index.js +7 -311
  145. package/dist/goal/index.js +33 -3435
  146. package/dist/hooks/index.js +14 -1138
  147. package/dist/hq/index.js +112 -5139
  148. package/dist/hq/protocol.js +21 -668
  149. package/dist/index.js +1410 -92655
  150. package/dist/infrastructure/index.js +64 -4811
  151. package/dist/kernel/events/provider-events.d.ts +15 -0
  152. package/dist/kernel/events/wrongtrace-events.d.ts +26 -0
  153. package/dist/kernel/events.d.ts +16 -7
  154. package/dist/kernel/index.js +14 -992
  155. package/dist/models/index.js +40 -1117
  156. package/dist/notifications/index.js +3 -108
  157. package/dist/observability/index.js +17 -279
  158. package/dist/plugin/index.js +584 -14835
  159. package/dist/prompts/index.js +14 -948
  160. package/dist/registry/index.js +9 -822
  161. package/dist/registry-R23UBE7H.js +14 -0
  162. package/dist/replay/index.js +6 -84
  163. package/dist/review-finding-store-DKAV3AS6.js +21 -0
  164. package/dist/review-report-store-OWY3EC3N.js +19 -0
  165. package/dist/security/index.js +114 -2202
  166. package/dist/session-catalog/index.js +36 -2103
  167. package/dist/session-catalog/project-server.js +42 -1435
  168. package/dist/skills/index.js +35 -1578
  169. package/dist/storage/index.js +182 -15600
  170. package/dist/tasking/index.js +10 -676
  171. package/dist/tools/index.js +52 -9589
  172. package/dist/types/config/autonomy.d.ts +5 -0
  173. package/dist/types/config/tools.d.ts +2 -2
  174. package/dist/types/config/ui.d.ts +1 -1
  175. package/dist/types/errors.d.ts +18 -0
  176. package/dist/types/index.js +121 -1341
  177. package/dist/types/provider-runner.d.ts +8 -0
  178. package/dist/types/session-markers.js +8 -144
  179. package/dist/utils/error.js +3 -4
  180. package/dist/utils/expect-defined.js +3 -9
  181. package/dist/utils/heap-watchdog.js +6 -706
  182. package/dist/utils/index.js +269 -5685
  183. package/dist/utils/sage-output-block.js +5 -42
  184. package/dist/wiring/proxy-rewrite.d.ts +92 -4
  185. package/dist/wiring/proxy-rewrite.js +20 -61
  186. package/dist/worktree/index.js +6 -887
  187. package/package.json +4 -4
@@ -0,0 +1,1640 @@
1
+ import {
2
+ evaluateModelCalendar
3
+ } from "./chunk-WQQON3SV.js";
4
+ import {
5
+ ProviderError,
6
+ isFallbackWorthy
7
+ } from "./chunk-NFAYHPRH.js";
8
+ import {
9
+ readBundledInstructionText,
10
+ renderInstructionTemplate
11
+ } from "./chunk-5S437PRC.js";
12
+ import {
13
+ estimateRequestTokens
14
+ } from "./chunk-FSWCWSTS.js";
15
+ import {
16
+ isTextBlock
17
+ } from "./chunk-O37BQ65S.js";
18
+
19
+ // src/execution/council-orchestrator-helpers.ts
20
+ var COUNCIL_REFUSAL_OPTION_ID = "council_refuse";
21
+ var DEFAULT_COUNCIL_MAX_CONCURRENCY = 3;
22
+ var MAX_COUNCIL_CONCURRENCY = 8;
23
+ var OVERALL_TIMEOUT_REASON = "Council overall timeout exceeded.";
24
+ var CALL_CANCELLED_REASON = "Cancelled.";
25
+ function validateConcurrency(value) {
26
+ if (!Number.isSafeInteger(value) || value <= 0 || value > MAX_COUNCIL_CONCURRENCY) {
27
+ throw new Error(
28
+ `CouncilOrchestrator: maxConcurrency must be an integer in [1, ${MAX_COUNCIL_CONCURRENCY}].`
29
+ );
30
+ }
31
+ return value;
32
+ }
33
+ function validateRefusalCollision(question, refusalOptionId) {
34
+ if (question.options?.some((option) => option.id.trim() === refusalOptionId)) {
35
+ throw new Error(`CouncilOrchestrator: option id "${refusalOptionId}" is reserved.`);
36
+ }
37
+ }
38
+ function optionLabel(question, optionId) {
39
+ if (!optionId) return void 0;
40
+ return question.options?.find((option) => option.id.trim() === optionId)?.label.trim();
41
+ }
42
+ async function mapConcurrent(items, concurrency, worker) {
43
+ const results = new Array(items.length);
44
+ let next = 0;
45
+ const run = async () => {
46
+ while (true) {
47
+ const index = next++;
48
+ if (index >= items.length) return;
49
+ const item = items[index];
50
+ if (item !== void 0) results[index] = await worker(item, index);
51
+ }
52
+ };
53
+ await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => run()));
54
+ return results;
55
+ }
56
+ function distinctTargetKeys(votes, profile) {
57
+ const keys = [];
58
+ for (const vote of votes) {
59
+ if (vote.status !== "valid") continue;
60
+ const provider = vote.provider?.trim() ?? "";
61
+ const model = vote.model?.trim() ?? "";
62
+ if (profile.distinctness === "provider") {
63
+ if (provider) keys.push(provider);
64
+ } else if (provider || model) {
65
+ keys.push(`${provider}/${model}`);
66
+ }
67
+ }
68
+ return keys;
69
+ }
70
+ function distinctTargetCount(votes, profile) {
71
+ return new Set(distinctTargetKeys(votes, profile)).size;
72
+ }
73
+ function distinctnessWarnings(votes, profile) {
74
+ if (profile.distinctness === "none") return [];
75
+ const keys = distinctTargetKeys(votes, profile);
76
+ const distinct = new Set(keys).size;
77
+ if (keys.length > 1 && distinct < keys.length) {
78
+ return [
79
+ `Council distinctness policy "${profile.distinctness}" was not met: ${distinct} distinct target(s) served ${keys.length} valid vote(s).`
80
+ ];
81
+ }
82
+ return [];
83
+ }
84
+ function cancelledVote(seat) {
85
+ return {
86
+ seatId: seat.id,
87
+ persona: seat.persona,
88
+ status: "cancelled",
89
+ ...seat.target?.providerId ? { provider: seat.target.providerId } : {},
90
+ ...seat.target?.model ? { model: seat.target.model } : {},
91
+ durationMs: 0,
92
+ error: CALL_CANCELLED_REASON
93
+ };
94
+ }
95
+ function callMetadata(result) {
96
+ return {
97
+ ...result.provider ? { provider: result.provider } : {},
98
+ ...result.model ? { model: result.model } : {},
99
+ ...result.fromFallback ? { fromFallback: true } : {},
100
+ durationMs: result.durationMs
101
+ };
102
+ }
103
+ function addUsage(usage, result) {
104
+ usage.calls += Math.max(1, result.attempts ?? 1);
105
+ usage.inputTokens += result.tokens.input;
106
+ usage.outputTokens += result.tokens.output;
107
+ usage.totalTokens += result.tokens.total;
108
+ }
109
+ function usageResult(usage, startedAt) {
110
+ return Object.freeze({ ...usage, durationMs: Math.max(0, Date.now() - startedAt) });
111
+ }
112
+ function resultEnvelope(input) {
113
+ const validVoteCount = input.votes.filter((vote) => vote.status === "valid").length;
114
+ return {
115
+ status: input.status,
116
+ ...input.answer ? { answer: input.answer } : {},
117
+ ...input.optionId ? { optionId: input.optionId } : {},
118
+ ...input.reason ? { reason: input.reason } : {},
119
+ resolution: input.resolution,
120
+ votes: Object.freeze([...input.votes]),
121
+ configuredSeatCount: input.profile.seats.length,
122
+ validVoteCount,
123
+ distinctTargetCount: distinctTargetCount(input.votes, input.profile),
124
+ judgeUsed: input.judgeUsed ?? false,
125
+ usage: usageResult(input.usage, input.startedAt),
126
+ ...input.warnings.length > 0 ? { warnings: Object.freeze([...input.warnings]) } : {},
127
+ ...input.errors.length > 0 ? { errors: Object.freeze([...input.errors]) } : {}
128
+ };
129
+ }
130
+
131
+ // src/execution/council-personas.ts
132
+ var PERSONA_ID_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
133
+ var BUILTIN_COUNCIL_PERSONAS = Object.freeze([
134
+ freezePersona({
135
+ id: "executor",
136
+ name: "Executor",
137
+ description: "Tests whether a proposal is practical and keeps useful work moving.",
138
+ 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.",
139
+ tags: ["delivery", "feasibility", "progress"]
140
+ }),
141
+ freezePersona({
142
+ id: "skeptic",
143
+ name: "Skeptic",
144
+ description: "Challenges assumptions and looks for concrete failure modes.",
145
+ instruction: "Identify unsupported assumptions, unsafe premises, irreversible consequences, and credible failure modes. Oppose or refuse only when you can name a concrete reason.",
146
+ defaultVeto: true,
147
+ tags: ["risk", "assumptions", "failure-modes"]
148
+ }),
149
+ freezePersona({
150
+ id: "auditor",
151
+ name: "Auditor",
152
+ description: "Evaluates cost, waste, evidence quality, and expected value.",
153
+ instruction: "Compare resource cost, opportunity cost, evidence quality, reversibility, and expected value. Prefer the option that achieves the objective with the least avoidable waste.",
154
+ tags: ["cost", "evidence", "efficiency"]
155
+ }),
156
+ freezePersona({
157
+ id: "security",
158
+ name: "Security Reviewer",
159
+ description: "Examines trust boundaries, abuse cases, and security impact.",
160
+ 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.",
161
+ defaultVeto: true,
162
+ tags: ["security", "trust", "abuse-cases"]
163
+ }),
164
+ freezePersona({
165
+ id: "maintainer",
166
+ name: "Maintainer",
167
+ description: "Evaluates complexity, compatibility, and long-term ownership.",
168
+ instruction: "Evaluate behavioral compatibility, conceptual complexity, testability, migration cost, and future maintenance. Prefer the smallest design that remains clear and extensible.",
169
+ tags: ["maintenance", "compatibility", "simplicity"]
170
+ }),
171
+ freezePersona({
172
+ id: "user-advocate",
173
+ name: "User Advocate",
174
+ description: "Evaluates the decision from the affected user\u2019s perspective.",
175
+ instruction: "Evaluate usability, surprise, accessibility, failure recovery, and whether the outcome solves the user\u2019s stated need. Prefer understandable behavior with safe recovery paths.",
176
+ tags: ["users", "usability", "accessibility"]
177
+ })
178
+ ]);
179
+ var BUILTIN_COUNCIL_PERSONA_IDS = Object.freeze(
180
+ BUILTIN_COUNCIL_PERSONAS.map((persona) => persona.id)
181
+ );
182
+ var CouncilPersonaRegistry = class _CouncilPersonaRegistry {
183
+ byId;
184
+ constructor(personas = []) {
185
+ const entries = /* @__PURE__ */ new Map();
186
+ for (const persona of personas) {
187
+ const normalized = freezePersona(persona);
188
+ if (entries.has(normalized.id)) {
189
+ throw new Error(`CouncilPersonaRegistry: duplicate persona id "${normalized.id}".`);
190
+ }
191
+ entries.set(normalized.id, normalized);
192
+ }
193
+ this.byId = entries;
194
+ }
195
+ has(id) {
196
+ return this.byId.has(id);
197
+ }
198
+ get(id) {
199
+ return this.byId.get(id);
200
+ }
201
+ require(id) {
202
+ const persona = this.get(id);
203
+ if (!persona) throw new Error(`CouncilPersonaRegistry: unknown persona "${id}".`);
204
+ return persona;
205
+ }
206
+ list() {
207
+ return Object.freeze([...this.byId.values()]);
208
+ }
209
+ /** Return a new registry; the current registry is never mutated. */
210
+ with(persona, opts = {}) {
211
+ const normalized = freezePersona(persona);
212
+ if (this.has(normalized.id) && opts.replace !== true) {
213
+ throw new Error(`CouncilPersonaRegistry: persona "${normalized.id}" already exists.`);
214
+ }
215
+ return new _CouncilPersonaRegistry([
216
+ ...this.list().filter((entry) => entry.id !== normalized.id),
217
+ normalized
218
+ ]);
219
+ }
220
+ };
221
+ var DEFAULT_COUNCIL_PERSONA_REGISTRY = new CouncilPersonaRegistry(
222
+ BUILTIN_COUNCIL_PERSONAS
223
+ );
224
+ function createCouncilPersonaRegistry(additional = []) {
225
+ let registry = DEFAULT_COUNCIL_PERSONA_REGISTRY;
226
+ for (const persona of additional) registry = registry.with(persona);
227
+ return registry;
228
+ }
229
+ function freezePersona(persona) {
230
+ const id = persona.id.trim();
231
+ const name = persona.name.trim();
232
+ const description = persona.description.trim();
233
+ const instruction = persona.instruction.trim();
234
+ if (!PERSONA_ID_RE.test(id)) {
235
+ throw new Error(`CouncilPersonaRegistry: invalid persona id "${persona.id}".`);
236
+ }
237
+ if (!name) throw new Error(`CouncilPersonaRegistry: persona "${id}" requires a name.`);
238
+ if (!description) {
239
+ throw new Error(`CouncilPersonaRegistry: persona "${id}" requires a description.`);
240
+ }
241
+ if (!instruction) {
242
+ throw new Error(`CouncilPersonaRegistry: persona "${id}" requires an instruction.`);
243
+ }
244
+ if (persona.defaultWeight !== void 0 && (!Number.isFinite(persona.defaultWeight) || persona.defaultWeight <= 0)) {
245
+ throw new Error(`CouncilPersonaRegistry: persona "${id}" has an invalid default weight.`);
246
+ }
247
+ const tags = Object.freeze(
248
+ [...new Set((persona.tags ?? []).map((tag) => tag.trim()).filter(Boolean))]
249
+ );
250
+ return Object.freeze({
251
+ id,
252
+ name,
253
+ description,
254
+ instruction,
255
+ ...persona.defaultWeight !== void 0 ? { defaultWeight: persona.defaultWeight } : {},
256
+ ...persona.defaultVeto !== void 0 ? { defaultVeto: persona.defaultVeto } : {},
257
+ ...tags.length > 0 ? { tags } : {}
258
+ });
259
+ }
260
+
261
+ // src/execution/council-profiles.ts
262
+ var PROFILE_ID_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
263
+ var DISTINCTNESS_VALUES = /* @__PURE__ */ new Set([
264
+ "none",
265
+ "model",
266
+ "provider"
267
+ ]);
268
+ var DEFAULT_COUNCIL_QUORUM_FRACTION = 0.5;
269
+ var DEFAULT_COUNCIL_APPROVAL_FRACTION = 0.5;
270
+ var DEFAULT_COUNCIL_VOTER_MAX_TOKENS = 300;
271
+ var DEFAULT_COUNCIL_JUDGE_MAX_TOKENS = 500;
272
+ var DEFAULT_COUNCIL_PER_CALL_TIMEOUT_MS = 3e4;
273
+ var DEFAULT_COUNCIL_OVERALL_TIMEOUT_MS = 9e4;
274
+ var BUILTIN_COUNCIL_PROFILES = Object.freeze([
275
+ Object.freeze({
276
+ id: "balanced",
277
+ name: "Balanced",
278
+ description: "Three complementary lenses plus an independent judge for general decisions.",
279
+ seats: Object.freeze([
280
+ Object.freeze({ persona: "executor", target: Object.freeze({ role: "planner" }) }),
281
+ Object.freeze({ persona: "skeptic", target: Object.freeze({ role: "critic" }) }),
282
+ Object.freeze({ persona: "auditor", target: Object.freeze({ role: "analyst" }) })
283
+ ]),
284
+ judge: Object.freeze({ role: "reviewer" }),
285
+ quorumFraction: 0.5,
286
+ approvalFraction: 0.5,
287
+ distinctness: "model"
288
+ }),
289
+ Object.freeze({
290
+ id: "fast",
291
+ name: "Fast",
292
+ description: "Two-seat panel without a judge for quick, low-cost decisions.",
293
+ seats: Object.freeze([
294
+ Object.freeze({ persona: "executor", target: Object.freeze({ role: "planner" }) }),
295
+ Object.freeze({ persona: "skeptic", target: Object.freeze({ role: "critic" }) })
296
+ ]),
297
+ judge: false,
298
+ quorumFraction: 0.5,
299
+ approvalFraction: 0.5,
300
+ distinctness: "none",
301
+ voterMaxTokens: 200,
302
+ perCallTimeoutMs: 2e4,
303
+ overallTimeoutMs: 3e4
304
+ }),
305
+ Object.freeze({
306
+ id: "risk-review",
307
+ name: "Risk Review",
308
+ description: "Security, assumptions, maintenance, and cost review for high-impact choices.",
309
+ seats: Object.freeze([
310
+ Object.freeze({ persona: "skeptic", target: Object.freeze({ role: "critic" }) }),
311
+ Object.freeze({ persona: "security", target: Object.freeze({ role: "security-reviewer" }) }),
312
+ Object.freeze({ persona: "maintainer", target: Object.freeze({ role: "reviewer" }) }),
313
+ Object.freeze({ persona: "auditor", target: Object.freeze({ role: "analyst" }) })
314
+ ]),
315
+ judge: Object.freeze({ role: "architect" }),
316
+ quorumFraction: 0.75,
317
+ approvalFraction: 0.5,
318
+ distinctness: "provider",
319
+ judgeMaxTokens: 700,
320
+ overallTimeoutMs: 12e4
321
+ })
322
+ ]);
323
+ var CouncilProfileRegistry = class _CouncilProfileRegistry {
324
+ byId;
325
+ personas;
326
+ constructor(profiles = [], personas = DEFAULT_COUNCIL_PERSONA_REGISTRY) {
327
+ this.personas = personas;
328
+ const entries = /* @__PURE__ */ new Map();
329
+ for (const profile of profiles) {
330
+ const normalized = normalizeCouncilProfile(profile, personas);
331
+ if (entries.has(normalized.id)) {
332
+ throw new Error(`CouncilProfileRegistry: duplicate profile id "${normalized.id}".`);
333
+ }
334
+ entries.set(normalized.id, normalized);
335
+ }
336
+ this.byId = entries;
337
+ }
338
+ has(id) {
339
+ return this.byId.has(id);
340
+ }
341
+ get(id) {
342
+ return this.byId.get(id);
343
+ }
344
+ require(id) {
345
+ const profile = this.get(id);
346
+ if (!profile) throw new Error(`CouncilProfileRegistry: unknown profile "${id}".`);
347
+ return profile;
348
+ }
349
+ list() {
350
+ return Object.freeze([...this.byId.values()]);
351
+ }
352
+ /** Return a new registry; the current registry is never mutated. */
353
+ with(profile, opts = {}) {
354
+ const personas = opts.personas ?? this.personas;
355
+ const normalized = normalizeCouncilProfile(profile, personas);
356
+ if (this.has(normalized.id) && opts.replace !== true) {
357
+ throw new Error(`CouncilProfileRegistry: profile "${normalized.id}" already exists.`);
358
+ }
359
+ return new _CouncilProfileRegistry(
360
+ [
361
+ ...this.list().filter((entry) => entry.id !== normalized.id),
362
+ profile
363
+ ],
364
+ personas
365
+ );
366
+ }
367
+ };
368
+ var DEFAULT_COUNCIL_PROFILE_REGISTRY = new CouncilProfileRegistry(
369
+ BUILTIN_COUNCIL_PROFILES
370
+ );
371
+ function createCouncilProfileRegistry(additional = [], personas = DEFAULT_COUNCIL_PERSONA_REGISTRY) {
372
+ return new CouncilProfileRegistry([...BUILTIN_COUNCIL_PROFILES, ...additional], personas);
373
+ }
374
+ function resolveCouncilProfile(profile, opts = {}) {
375
+ const personas = opts.personas ?? DEFAULT_COUNCIL_PERSONA_REGISTRY;
376
+ if (profile && typeof profile !== "string") return normalizeCouncilProfile(profile, personas);
377
+ const id = profile ?? opts.defaultProfile ?? "balanced";
378
+ return (opts.registry ?? DEFAULT_COUNCIL_PROFILE_REGISTRY).require(id);
379
+ }
380
+ function normalizeCouncilProfile(profile, personas = DEFAULT_COUNCIL_PERSONA_REGISTRY) {
381
+ const id = profile.id.trim();
382
+ if (!PROFILE_ID_RE.test(id)) {
383
+ throw new Error(`CouncilProfileRegistry: invalid profile id "${profile.id}".`);
384
+ }
385
+ if (profile.seats.length === 0) {
386
+ throw new Error(`CouncilProfileRegistry: profile "${id}" requires at least one seat.`);
387
+ }
388
+ const usedIds = /* @__PURE__ */ new Set();
389
+ const seats = profile.seats.map((seat) => {
390
+ const persona = personas.require(seat.persona.trim());
391
+ const explicitSeatId = seat.id?.trim();
392
+ if (explicitSeatId && usedIds.has(explicitSeatId)) {
393
+ throw new Error(`CouncilProfileRegistry: duplicate seat id "${explicitSeatId}".`);
394
+ }
395
+ const seatId = uniqueSeatId(explicitSeatId || persona.id, usedIds);
396
+ const label = seat.label?.trim() || persona.name;
397
+ const weight = seat.weight ?? persona.defaultWeight ?? 1;
398
+ if (!Number.isFinite(weight) || weight <= 0) {
399
+ throw new Error(`CouncilProfileRegistry: seat "${seatId}" has an invalid weight.`);
400
+ }
401
+ return Object.freeze({
402
+ id: seatId,
403
+ label,
404
+ persona: persona.id,
405
+ ...seat.target ? { target: freezeTarget(seat.target, `seat "${seatId}"`) } : {},
406
+ weight,
407
+ veto: seat.veto ?? persona.defaultVeto ?? false
408
+ });
409
+ });
410
+ const quorumFraction = fraction(
411
+ profile.quorumFraction ?? DEFAULT_COUNCIL_QUORUM_FRACTION,
412
+ "quorumFraction",
413
+ id
414
+ );
415
+ const approvalFraction = fraction(
416
+ profile.approvalFraction ?? DEFAULT_COUNCIL_APPROVAL_FRACTION,
417
+ "approvalFraction",
418
+ id
419
+ );
420
+ const distinctness = profile.distinctness ?? "model";
421
+ if (!DISTINCTNESS_VALUES.has(distinctness)) {
422
+ throw new Error(`CouncilProfileRegistry: profile "${id}" has invalid distinctness.`);
423
+ }
424
+ const voterMaxTokens = positiveInteger(
425
+ profile.voterMaxTokens ?? DEFAULT_COUNCIL_VOTER_MAX_TOKENS,
426
+ "voterMaxTokens",
427
+ id
428
+ );
429
+ const judgeMaxTokens = positiveInteger(
430
+ profile.judgeMaxTokens ?? DEFAULT_COUNCIL_JUDGE_MAX_TOKENS,
431
+ "judgeMaxTokens",
432
+ id
433
+ );
434
+ const perCallTimeoutMs = positiveInteger(
435
+ profile.perCallTimeoutMs ?? DEFAULT_COUNCIL_PER_CALL_TIMEOUT_MS,
436
+ "perCallTimeoutMs",
437
+ id
438
+ );
439
+ const overallTimeoutMs = positiveInteger(
440
+ profile.overallTimeoutMs ?? DEFAULT_COUNCIL_OVERALL_TIMEOUT_MS,
441
+ "overallTimeoutMs",
442
+ id
443
+ );
444
+ if (overallTimeoutMs < perCallTimeoutMs) {
445
+ throw new Error(
446
+ `CouncilProfileRegistry: profile "${id}" overallTimeoutMs must be at least perCallTimeoutMs.`
447
+ );
448
+ }
449
+ return Object.freeze({
450
+ id,
451
+ name: profile.name?.trim() || id,
452
+ description: profile.description?.trim() || "",
453
+ seats: Object.freeze(seats),
454
+ judge: profile.judge ? freezeTarget(profile.judge, "judge") : false,
455
+ quorumFraction,
456
+ approvalFraction,
457
+ distinctness,
458
+ voterMaxTokens,
459
+ judgeMaxTokens,
460
+ perCallTimeoutMs,
461
+ overallTimeoutMs
462
+ });
463
+ }
464
+ function uniqueSeatId(base, used) {
465
+ if (!PROFILE_ID_RE.test(base)) {
466
+ throw new Error(`CouncilProfileRegistry: invalid seat id "${base}".`);
467
+ }
468
+ let id = base;
469
+ let suffix = 2;
470
+ while (used.has(id)) id = `${base}-${suffix++}`;
471
+ used.add(id);
472
+ return id;
473
+ }
474
+ function freezeTarget(target, label) {
475
+ const providerId = optionalText(target.providerId);
476
+ const model = optionalText(target.model);
477
+ const role = optionalText(target.role);
478
+ const fallbackProfile = optionalText(target.fallbackProfile);
479
+ const fallbackModels = Object.freeze(
480
+ [...new Set((target.fallbackModels ?? []).map((ref) => ref.trim()).filter(Boolean))]
481
+ );
482
+ if (!providerId && !model && !role && !fallbackProfile && fallbackModels.length === 0) {
483
+ throw new Error(`CouncilProfileRegistry: ${label} target is empty.`);
484
+ }
485
+ return Object.freeze({
486
+ ...providerId ? { providerId } : {},
487
+ ...model ? { model } : {},
488
+ ...role ? { role } : {},
489
+ ...fallbackProfile ? { fallbackProfile } : {},
490
+ ...fallbackModels.length > 0 ? { fallbackModels } : {}
491
+ });
492
+ }
493
+ function optionalText(value) {
494
+ const trimmed = value?.trim();
495
+ return trimmed || void 0;
496
+ }
497
+ function fraction(value, field, profileId) {
498
+ if (!Number.isFinite(value) || value <= 0 || value > 1) {
499
+ throw new Error(`CouncilProfileRegistry: profile "${profileId}" ${field} must be in (0, 1].`);
500
+ }
501
+ return value;
502
+ }
503
+ function positiveInteger(value, field, profileId) {
504
+ if (!Number.isSafeInteger(value) || value <= 0) {
505
+ throw new Error(
506
+ `CouncilProfileRegistry: profile "${profileId}" ${field} must be a positive integer.`
507
+ );
508
+ }
509
+ return value;
510
+ }
511
+
512
+ // src/execution/council-prompts.ts
513
+ var COUNCIL_VOTER_PROMPT_PATH = "llm/council-voter.md";
514
+ var COUNCIL_JUDGE_PROMPT_PATH = "llm/council-judge.md";
515
+ function buildCouncilVoterSystemPrompt(persona) {
516
+ const template = requiredInstruction(COUNCIL_VOTER_PROMPT_PATH);
517
+ return renderInstructionTemplate(template, {
518
+ personaInstruction: persona.instruction
519
+ });
520
+ }
521
+ function buildCouncilJudgeSystemPrompt() {
522
+ return requiredInstruction(COUNCIL_JUDGE_PROMPT_PATH);
523
+ }
524
+ function buildCouncilQuestionPrompt(question, opts = {}) {
525
+ const text = question.question.trim();
526
+ if (!text) throw new Error("buildCouncilQuestionPrompt: question must not be empty.");
527
+ const options = normalizeOptions(question.options);
528
+ return [
529
+ "<council-question>",
530
+ `Question: ${text}`,
531
+ question.context?.trim() ? `Context:
532
+ ${question.context.trim()}` : "",
533
+ options.length > 0 ? `Options (JSON):
534
+ ${JSON.stringify(options)}` : "Options: none",
535
+ opts.refusalOptionId ? `Refusal option id: ${opts.refusalOptionId}` : "Refusal option id: not supplied",
536
+ "</council-question>"
537
+ ].filter(Boolean).join("\n\n");
538
+ }
539
+ function buildCouncilVoterUserPrompt(question, seat, opts = {}) {
540
+ return [
541
+ buildCouncilQuestionPrompt(question, opts),
542
+ "<seat-metadata>",
543
+ `Seat id: ${seat.id}`,
544
+ `Seat label: ${seat.label}`,
545
+ `Persona id: ${seat.persona}`,
546
+ `Vote weight: ${seat.weight}`,
547
+ seat.veto ? "Veto power: yes \u2014 your refusal unilaterally denies the proposal." : "",
548
+ "</seat-metadata>"
549
+ ].filter(Boolean).join("\n");
550
+ }
551
+ function buildCouncilJudgeUserPrompt(question, votes, opts = {}) {
552
+ const ballots = votes.map((vote) => ({
553
+ seatId: vote.seatId,
554
+ persona: vote.persona,
555
+ status: vote.status,
556
+ ...vote.optionId ? { optionId: vote.optionId } : {},
557
+ ...vote.stance ? { stance: vote.stance } : {},
558
+ ...vote.rationale ? { rationale: vote.rationale } : {}
559
+ }));
560
+ return [
561
+ buildCouncilQuestionPrompt(question, { refusalOptionId: opts.refusalOptionId }),
562
+ "<council-ballots>",
563
+ opts.reason?.trim() ? `Reason judging is required: ${opts.reason.trim()}` : "",
564
+ JSON.stringify(ballots),
565
+ "</council-ballots>"
566
+ ].filter(Boolean).join("\n\n");
567
+ }
568
+ function validateCouncilOptions(options) {
569
+ const errors = [];
570
+ const seen = /* @__PURE__ */ new Set();
571
+ for (const option of options ?? []) {
572
+ const id = option.id.trim();
573
+ if (!id) errors.push("Every option must have a non-empty `id`.");
574
+ if (!option.label.trim()) errors.push(`Option "${id || "<empty>"}" must have a label.`);
575
+ if (seen.has(id)) errors.push(`Duplicate option id "${id}".`);
576
+ seen.add(id);
577
+ }
578
+ return errors;
579
+ }
580
+ function normalizeOptions(options) {
581
+ if (!options) return [];
582
+ const errors = validateCouncilOptions(options);
583
+ if (errors.length > 0) {
584
+ throw new Error(`buildCouncilQuestionPrompt: ${errors.join("; ")}`);
585
+ }
586
+ return options.map((option) => ({
587
+ id: option.id.trim(),
588
+ label: option.label.trim(),
589
+ ...option.consequence?.trim() ? { consequence: option.consequence.trim() } : {}
590
+ }));
591
+ }
592
+ function requiredInstruction(path) {
593
+ const text = readBundledInstructionText(path);
594
+ if (!text) throw new Error(`Council instruction file is unavailable: ${path}`);
595
+ return text;
596
+ }
597
+
598
+ // src/execution/council-resolution.ts
599
+ function resolveCouncilVotes(input) {
600
+ validateInput(input);
601
+ const seatById = new Map(input.seats.map((seat) => [seat.id, seat]));
602
+ const validVotes = [];
603
+ const votedSeatIds = /* @__PURE__ */ new Set();
604
+ for (const vote of input.votes) {
605
+ if (!seatById.has(vote.seatId) || votedSeatIds.has(vote.seatId)) continue;
606
+ votedSeatIds.add(vote.seatId);
607
+ validVotes.push(vote);
608
+ }
609
+ if (validVotes.length / input.seats.length < input.quorumFraction) {
610
+ return {
611
+ status: "abstained",
612
+ reason: "quorum_not_met",
613
+ validVoteCount: validVotes.length,
614
+ seatCount: input.seats.length
615
+ };
616
+ }
617
+ const veto = validVotes.find(
618
+ (vote) => vote.optionId === input.refusalOptionId && seatById.get(vote.seatId)?.veto === true
619
+ );
620
+ if (veto) {
621
+ return {
622
+ status: "denied",
623
+ method: "veto",
624
+ optionId: veto.optionId,
625
+ seatId: veto.seatId
626
+ };
627
+ }
628
+ const weightByOption = /* @__PURE__ */ new Map();
629
+ let castWeight = 0;
630
+ for (const vote of validVotes) {
631
+ const weight = seatById.get(vote.seatId)?.weight ?? 1;
632
+ castWeight += weight;
633
+ weightByOption.set(vote.optionId, (weightByOption.get(vote.optionId) ?? 0) + weight);
634
+ }
635
+ let winner;
636
+ let contested = false;
637
+ for (const [optionId, weight] of weightByOption) {
638
+ if (!winner || weight > winner.weight) {
639
+ winner = { optionId, weight };
640
+ contested = false;
641
+ } else if (weight === winner.weight) {
642
+ contested = true;
643
+ }
644
+ }
645
+ const decisive = winner !== void 0 && !contested && winner.weight > input.approvalFraction * castWeight;
646
+ if (!decisive || !winner) {
647
+ return {
648
+ status: "needs_judge",
649
+ reason: contested ? "tie" : "approval_threshold_not_met",
650
+ castWeight
651
+ };
652
+ }
653
+ if (winner.optionId === input.refusalOptionId) {
654
+ return {
655
+ status: "denied",
656
+ method: "refusal",
657
+ optionId: winner.optionId,
658
+ winningWeight: winner.weight,
659
+ castWeight
660
+ };
661
+ }
662
+ return {
663
+ status: "decided",
664
+ method: "majority",
665
+ optionId: winner.optionId,
666
+ winningWeight: winner.weight,
667
+ castWeight
668
+ };
669
+ }
670
+ function validateInput(input) {
671
+ if (input.seats.length === 0) {
672
+ throw new Error("resolveCouncilVotes: at least one seat is required.");
673
+ }
674
+ requireFraction(input.quorumFraction, "quorumFraction");
675
+ requireFraction(input.approvalFraction, "approvalFraction");
676
+ const seatIds = /* @__PURE__ */ new Set();
677
+ for (const seat of input.seats) {
678
+ if (!seat.id.trim()) throw new Error("resolveCouncilVotes: seat id must not be empty.");
679
+ if (seatIds.has(seat.id)) {
680
+ throw new Error(`resolveCouncilVotes: duplicate seat id "${seat.id}".`);
681
+ }
682
+ seatIds.add(seat.id);
683
+ if (seat.weight !== void 0 && (!Number.isFinite(seat.weight) || seat.weight <= 0)) {
684
+ throw new Error(`resolveCouncilVotes: invalid weight for seat "${seat.id}".`);
685
+ }
686
+ }
687
+ }
688
+ function requireFraction(value, label) {
689
+ if (!Number.isFinite(value) || value <= 0 || value > 1) {
690
+ throw new Error(`resolveCouncilVotes: ${label} must be in (0, 1].`);
691
+ }
692
+ }
693
+
694
+ // src/execution/council-response-parser.ts
695
+ function parseObject(text) {
696
+ const trimmed = text.trim();
697
+ const first = trimmed.indexOf("{");
698
+ const last = trimmed.lastIndexOf("}");
699
+ if (first < 0 || last < first) return { ok: false, error: "LLM response did not contain JSON." };
700
+ try {
701
+ const value = JSON.parse(trimmed.slice(first, last + 1));
702
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
703
+ return { ok: false, error: "LLM response JSON must be an object." };
704
+ }
705
+ return { ok: true, value };
706
+ } catch (error) {
707
+ return { ok: false, error: `Invalid LLM response JSON: ${errorMessage(error)}` };
708
+ }
709
+ }
710
+ function optionalString(value) {
711
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
712
+ }
713
+ function errorMessage(error) {
714
+ return error instanceof Error ? error.message : String(error);
715
+ }
716
+ function parseCouncilResponse(text, question, refusalOptionId, opts) {
717
+ const roleLabel = opts.role === "judge" ? "Judge" : "Voter";
718
+ const parsed = parseObject(text);
719
+ if (!parsed.ok && (!question.options || question.options.length === 0)) {
720
+ const fallback = text.trim();
721
+ if (fallback) return { ok: true, value: { [opts.freeTextField]: fallback } };
722
+ return { ok: false, error: `${roleLabel} returned an empty response.` };
723
+ }
724
+ if (!parsed.ok) return parsed;
725
+ const rationale = optionalString(parsed.value["rationale"]);
726
+ if (question.options && question.options.length > 0) {
727
+ const optionId = optionalString(parsed.value["optionId"]);
728
+ const allowed = /* @__PURE__ */ new Set([
729
+ ...question.options.map((option) => option.id.trim()),
730
+ refusalOptionId
731
+ ]);
732
+ if (!optionId || !allowed.has(optionId)) {
733
+ return { ok: false, error: `${roleLabel} returned an unknown or missing optionId.` };
734
+ }
735
+ return { ok: true, value: { optionId, ...rationale ? { rationale } : {} } };
736
+ }
737
+ const freeText = optionalString(parsed.value[opts.freeTextField]);
738
+ if (!freeText) {
739
+ return {
740
+ ok: false,
741
+ error: `${roleLabel} returned an empty or missing ${opts.freeTextField}.`
742
+ };
743
+ }
744
+ return {
745
+ ok: true,
746
+ value: { [opts.freeTextField]: freeText, ...rationale ? { rationale } : {} }
747
+ };
748
+ }
749
+ function divergentStances(valid) {
750
+ const seen = /* @__PURE__ */ new Set();
751
+ for (const vote of valid) {
752
+ const normalized = vote.stance.trim().replace(/^["'`]+|["'`]+$/g, "").toLowerCase().replace(/[.!?;:,]+$/g, "").replace(/\s+/g, " ").trim();
753
+ if (!normalized) return true;
754
+ seen.add(normalized);
755
+ if (seen.size > 1) return true;
756
+ }
757
+ return false;
758
+ }
759
+ function parseVote(text, question, refusalOptionId) {
760
+ const parsed = parseCouncilResponse(text, question, refusalOptionId, {
761
+ role: "voter",
762
+ freeTextField: "stance"
763
+ });
764
+ if (!parsed.ok) return parsed;
765
+ const { optionId, stance, rationale } = parsed.value;
766
+ return {
767
+ ok: true,
768
+ vote: {
769
+ ...optionId ? { optionId } : {},
770
+ ...stance ? { stance } : {},
771
+ ...rationale ? { rationale } : {}
772
+ }
773
+ };
774
+ }
775
+ function parseJudge(text, question, refusalOptionId) {
776
+ const parsed = parseCouncilResponse(text, question, refusalOptionId, {
777
+ role: "judge",
778
+ freeTextField: "answer"
779
+ });
780
+ if (!parsed.ok) return parsed;
781
+ const { optionId, answer, rationale } = parsed.value;
782
+ return {
783
+ ok: true,
784
+ value: {
785
+ ...optionId ? { optionId } : {},
786
+ ...answer ? { answer } : {},
787
+ ...rationale ? { rationale } : {}
788
+ }
789
+ };
790
+ }
791
+ function withTruncationNote(error, result, maxTokens) {
792
+ if (result.stopReason !== "max_tokens") return error;
793
+ return `${error} (response truncated at maxTokens=${maxTokens} \u2014 reasoning models may need a larger budget)`;
794
+ }
795
+
796
+ // src/execution/council-orchestrator.ts
797
+ var CouncilOrchestrator = class {
798
+ caller;
799
+ personas;
800
+ profiles;
801
+ defaultProfile;
802
+ maxConcurrency;
803
+ refusalOptionId;
804
+ fallbackProfileManager;
805
+ seatCaller;
806
+ judgeCaller;
807
+ constructor(opts) {
808
+ if (!opts.caller && !opts.seatCaller && !opts.judgeCaller) {
809
+ throw new Error("CouncilOrchestrator: provide `caller`, `seatCaller`, or `judgeCaller`.");
810
+ }
811
+ this.caller = opts.caller;
812
+ this.personas = opts.personas ?? DEFAULT_COUNCIL_PERSONA_REGISTRY;
813
+ this.profiles = opts.profiles ?? DEFAULT_COUNCIL_PROFILE_REGISTRY;
814
+ this.defaultProfile = opts.defaultProfile;
815
+ this.maxConcurrency = validateConcurrency(
816
+ opts.maxConcurrency ?? DEFAULT_COUNCIL_MAX_CONCURRENCY
817
+ );
818
+ this.refusalOptionId = opts.refusalOptionId?.trim() || COUNCIL_REFUSAL_OPTION_ID;
819
+ this.fallbackProfileManager = opts.fallbackProfileManager;
820
+ this.seatCaller = opts.seatCaller;
821
+ this.judgeCaller = opts.judgeCaller;
822
+ }
823
+ resolveProfile(profile) {
824
+ return resolveCouncilProfile(profile, {
825
+ registry: this.profiles,
826
+ personas: this.personas,
827
+ defaultProfile: this.defaultProfile
828
+ });
829
+ }
830
+ async ask(question) {
831
+ const startedAt = Date.now();
832
+ const profile = this.resolveProfile(question.profile);
833
+ validateRefusalCollision(question, this.refusalOptionId);
834
+ const timeoutSignal = AbortSignal.timeout(profile.overallTimeoutMs);
835
+ const signal = question.signal ? AbortSignal.any([question.signal, timeoutSignal]) : timeoutSignal;
836
+ const usage = {
837
+ calls: 0,
838
+ inputTokens: 0,
839
+ outputTokens: 0,
840
+ totalTokens: 0
841
+ };
842
+ const votes = await mapConcurrent(
843
+ profile.seats,
844
+ Math.min(this.maxConcurrency, profile.seats.length),
845
+ async (seat, i) => {
846
+ try {
847
+ return await this.callSeat(question, profile, seat, i, signal, usage);
848
+ } catch (error) {
849
+ const timedOut = signal.aborted && !question.signal?.aborted;
850
+ return {
851
+ seatId: seat.id,
852
+ persona: seat.persona,
853
+ status: question.signal?.aborted ? "cancelled" : "failed",
854
+ error: question.signal?.aborted ? CALL_CANCELLED_REASON : timedOut ? OVERALL_TIMEOUT_REASON : errorMessage(error)
855
+ };
856
+ }
857
+ }
858
+ );
859
+ const warnings = distinctnessWarnings(votes, profile);
860
+ const errors = votes.filter((vote) => vote.status === "failed" || vote.status === "invalid").map((vote) => `${vote.seatId}: ${vote.error ?? vote.status}`);
861
+ if (question.signal?.aborted) {
862
+ return resultEnvelope({
863
+ status: "cancelled",
864
+ reason: CALL_CANCELLED_REASON,
865
+ resolution: "none",
866
+ votes,
867
+ profile,
868
+ usage,
869
+ startedAt,
870
+ warnings,
871
+ errors
872
+ });
873
+ }
874
+ if (timeoutSignal.aborted) {
875
+ return resultEnvelope({
876
+ status: "failed",
877
+ reason: OVERALL_TIMEOUT_REASON,
878
+ resolution: "none",
879
+ votes,
880
+ profile,
881
+ usage,
882
+ startedAt,
883
+ warnings,
884
+ errors: errors.some((entry) => entry.includes(OVERALL_TIMEOUT_REASON)) ? errors : [...errors, OVERALL_TIMEOUT_REASON]
885
+ });
886
+ }
887
+ if (!question.options || question.options.length === 0) {
888
+ return this.resolveOpenQuestion(
889
+ question,
890
+ profile,
891
+ votes,
892
+ signal,
893
+ usage,
894
+ startedAt,
895
+ warnings,
896
+ errors
897
+ );
898
+ }
899
+ return this.resolveOptionQuestion(
900
+ question,
901
+ profile,
902
+ votes,
903
+ signal,
904
+ usage,
905
+ startedAt,
906
+ warnings,
907
+ errors
908
+ );
909
+ }
910
+ async callSeat(question, profile, seat, seatIndex, signal, usage) {
911
+ if (signal.aborted) {
912
+ return question.signal?.aborted ? cancelledVote(seat) : {
913
+ seatId: seat.id,
914
+ persona: seat.persona,
915
+ status: "failed",
916
+ ...seat.target?.providerId ? { provider: seat.target.providerId } : {},
917
+ ...seat.target?.model ? { model: seat.target.model } : {},
918
+ durationMs: 0,
919
+ error: OVERALL_TIMEOUT_REASON
920
+ };
921
+ }
922
+ let persona;
923
+ try {
924
+ persona = this.personas.require(seat.persona);
925
+ } catch (error) {
926
+ return {
927
+ seatId: seat.id,
928
+ persona: seat.persona,
929
+ status: "failed",
930
+ error: errorMessage(error)
931
+ };
932
+ }
933
+ const result = await this.safeCall({
934
+ system: buildCouncilVoterSystemPrompt(persona),
935
+ userPrompt: buildCouncilVoterUserPrompt(question, seat, {
936
+ refusalOptionId: question.options?.length ? this.refusalOptionId : void 0
937
+ }),
938
+ target: seat.target,
939
+ maxTokens: profile.voterMaxTokens,
940
+ timeoutMs: profile.perCallTimeoutMs,
941
+ signal,
942
+ usage,
943
+ seatIndex
944
+ });
945
+ const metadata = callMetadata(result);
946
+ if (result.error) {
947
+ const timedOut = signal.aborted && !question.signal?.aborted;
948
+ return {
949
+ seatId: seat.id,
950
+ persona: seat.persona,
951
+ status: question.signal?.aborted ? "cancelled" : "failed",
952
+ ...metadata,
953
+ error: question.signal?.aborted ? CALL_CANCELLED_REASON : timedOut ? OVERALL_TIMEOUT_REASON : result.error
954
+ };
955
+ }
956
+ const parsed = parseVote(result.text, question, this.refusalOptionId);
957
+ if (!parsed.ok) {
958
+ return {
959
+ seatId: seat.id,
960
+ persona: seat.persona,
961
+ status: "invalid",
962
+ ...metadata,
963
+ error: withTruncationNote(parsed.error, result, profile.voterMaxTokens)
964
+ };
965
+ }
966
+ return {
967
+ seatId: seat.id,
968
+ persona: seat.persona,
969
+ status: "valid",
970
+ ...parsed.vote,
971
+ ...metadata
972
+ };
973
+ }
974
+ async resolveOptionQuestion(question, profile, votes, signal, usage, startedAt, warnings, errors) {
975
+ const validVotes = votes.filter(
976
+ (vote) => vote.status === "valid" && typeof vote.optionId === "string"
977
+ );
978
+ const resolution = resolveCouncilVotes({
979
+ seats: profile.seats.map((seat) => ({
980
+ id: seat.id,
981
+ weight: seat.weight,
982
+ veto: seat.veto
983
+ })),
984
+ votes: validVotes.map((vote) => ({ seatId: vote.seatId, optionId: vote.optionId })),
985
+ refusalOptionId: this.refusalOptionId,
986
+ quorumFraction: profile.quorumFraction,
987
+ approvalFraction: profile.approvalFraction
988
+ });
989
+ if (resolution.status === "abstained") {
990
+ return resultEnvelope({
991
+ status: "abstained",
992
+ reason: "Council quorum was not met.",
993
+ resolution: "none",
994
+ votes,
995
+ profile,
996
+ usage,
997
+ startedAt,
998
+ warnings,
999
+ errors
1000
+ });
1001
+ }
1002
+ if (resolution.status === "denied") {
1003
+ return resultEnvelope({
1004
+ status: "denied",
1005
+ optionId: resolution.optionId,
1006
+ reason: `Council denied the proposal via ${resolution.method}.`,
1007
+ resolution: resolution.method,
1008
+ votes,
1009
+ profile,
1010
+ usage,
1011
+ startedAt,
1012
+ warnings,
1013
+ errors
1014
+ });
1015
+ }
1016
+ if (resolution.status === "decided") {
1017
+ return resultEnvelope({
1018
+ status: "decided",
1019
+ optionId: resolution.optionId,
1020
+ answer: optionLabel(question, resolution.optionId),
1021
+ resolution: "majority",
1022
+ votes,
1023
+ profile,
1024
+ usage,
1025
+ startedAt,
1026
+ warnings,
1027
+ errors
1028
+ });
1029
+ }
1030
+ if (!profile.judge) {
1031
+ return resultEnvelope({
1032
+ status: "abstained",
1033
+ reason: `Council requires a judge (${resolution.reason}), but this profile has none.`,
1034
+ resolution: "none",
1035
+ votes,
1036
+ profile,
1037
+ usage,
1038
+ startedAt,
1039
+ warnings,
1040
+ errors
1041
+ });
1042
+ }
1043
+ const judged = await this.callJudge(
1044
+ question,
1045
+ profile,
1046
+ votes,
1047
+ profile.judge,
1048
+ resolution.reason,
1049
+ signal,
1050
+ usage
1051
+ );
1052
+ if (signal.aborted) {
1053
+ const cancelled = question.signal?.aborted === true;
1054
+ const reason = cancelled ? CALL_CANCELLED_REASON : OVERALL_TIMEOUT_REASON;
1055
+ return resultEnvelope({
1056
+ status: cancelled ? "cancelled" : "failed",
1057
+ reason,
1058
+ resolution: "none",
1059
+ votes,
1060
+ profile,
1061
+ usage,
1062
+ startedAt,
1063
+ warnings,
1064
+ errors: errors.some((entry) => entry.includes(reason)) ? errors : [...errors, reason],
1065
+ judgeUsed: true
1066
+ });
1067
+ }
1068
+ if (!judged.ok) {
1069
+ return resultEnvelope({
1070
+ status: question.signal?.aborted ? "cancelled" : signal.aborted ? "failed" : "abstained",
1071
+ reason: judged.error,
1072
+ resolution: "none",
1073
+ votes,
1074
+ profile,
1075
+ usage,
1076
+ startedAt,
1077
+ warnings,
1078
+ errors: [...errors, judged.error],
1079
+ judgeUsed: true
1080
+ });
1081
+ }
1082
+ if (judged.value.optionId === this.refusalOptionId) {
1083
+ return resultEnvelope({
1084
+ status: "denied",
1085
+ optionId: this.refusalOptionId,
1086
+ reason: judged.value.rationale ?? "Council judge refused all options.",
1087
+ resolution: "judge",
1088
+ votes,
1089
+ profile,
1090
+ usage,
1091
+ startedAt,
1092
+ warnings,
1093
+ errors,
1094
+ judgeUsed: true
1095
+ });
1096
+ }
1097
+ return resultEnvelope({
1098
+ status: "decided",
1099
+ optionId: judged.value.optionId,
1100
+ answer: optionLabel(question, judged.value.optionId),
1101
+ reason: judged.value.rationale,
1102
+ resolution: "judge",
1103
+ votes,
1104
+ profile,
1105
+ usage,
1106
+ startedAt,
1107
+ warnings,
1108
+ errors,
1109
+ judgeUsed: true
1110
+ });
1111
+ }
1112
+ async resolveOpenQuestion(question, profile, votes, signal, usage, startedAt, warnings, errors) {
1113
+ const valid = votes.filter(
1114
+ (vote) => vote.status === "valid" && typeof vote.stance === "string"
1115
+ );
1116
+ if (valid.length / profile.seats.length < profile.quorumFraction) {
1117
+ return resultEnvelope({
1118
+ status: "abstained",
1119
+ reason: "Council quorum was not met.",
1120
+ resolution: "none",
1121
+ votes,
1122
+ profile,
1123
+ usage,
1124
+ startedAt,
1125
+ warnings,
1126
+ errors
1127
+ });
1128
+ }
1129
+ if (!profile.judge) {
1130
+ if (divergentStances(valid)) {
1131
+ return resultEnvelope({
1132
+ status: "abstained",
1133
+ reason: "Council produced multiple distinct stances and has no judge to reconcile them.",
1134
+ resolution: "none",
1135
+ votes,
1136
+ profile,
1137
+ usage,
1138
+ startedAt,
1139
+ warnings,
1140
+ errors
1141
+ });
1142
+ }
1143
+ const first = valid[0];
1144
+ if (!first) {
1145
+ return resultEnvelope({
1146
+ status: "failed",
1147
+ reason: "Council produced no valid stance.",
1148
+ resolution: "none",
1149
+ votes,
1150
+ profile,
1151
+ usage,
1152
+ startedAt,
1153
+ warnings,
1154
+ errors
1155
+ });
1156
+ }
1157
+ return resultEnvelope({
1158
+ status: "decided",
1159
+ answer: first.stance,
1160
+ reason: first.rationale,
1161
+ resolution: "first_stance",
1162
+ votes,
1163
+ profile,
1164
+ usage,
1165
+ startedAt,
1166
+ warnings,
1167
+ errors
1168
+ });
1169
+ }
1170
+ const judged = await this.callJudge(
1171
+ question,
1172
+ profile,
1173
+ votes,
1174
+ profile.judge,
1175
+ "open_question_synthesis",
1176
+ signal,
1177
+ usage
1178
+ );
1179
+ if (signal.aborted) {
1180
+ const cancelled = question.signal?.aborted === true;
1181
+ const reason = cancelled ? CALL_CANCELLED_REASON : OVERALL_TIMEOUT_REASON;
1182
+ return resultEnvelope({
1183
+ status: cancelled ? "cancelled" : "failed",
1184
+ reason,
1185
+ resolution: "none",
1186
+ votes,
1187
+ profile,
1188
+ usage,
1189
+ startedAt,
1190
+ warnings,
1191
+ errors: errors.some((entry) => entry.includes(reason)) ? errors : [...errors, reason],
1192
+ judgeUsed: true
1193
+ });
1194
+ }
1195
+ if (!judged.ok) {
1196
+ return resultEnvelope({
1197
+ status: question.signal?.aborted ? "cancelled" : "failed",
1198
+ reason: judged.error,
1199
+ resolution: "none",
1200
+ votes,
1201
+ profile,
1202
+ usage,
1203
+ startedAt,
1204
+ warnings,
1205
+ errors: [...errors, judged.error],
1206
+ judgeUsed: true
1207
+ });
1208
+ }
1209
+ return resultEnvelope({
1210
+ status: "decided",
1211
+ answer: judged.value.answer,
1212
+ reason: judged.value.rationale,
1213
+ resolution: "judge",
1214
+ votes,
1215
+ profile,
1216
+ usage,
1217
+ startedAt,
1218
+ warnings,
1219
+ errors,
1220
+ judgeUsed: true
1221
+ });
1222
+ }
1223
+ async callJudge(question, profile, votes, target, reason, signal, usage) {
1224
+ const result = await this.safeCall({
1225
+ system: buildCouncilJudgeSystemPrompt(),
1226
+ userPrompt: buildCouncilJudgeUserPrompt(question, votes, {
1227
+ reason,
1228
+ refusalOptionId: question.options?.length ? this.refusalOptionId : void 0
1229
+ }),
1230
+ target,
1231
+ maxTokens: profile.judgeMaxTokens,
1232
+ timeoutMs: profile.perCallTimeoutMs,
1233
+ signal,
1234
+ usage
1235
+ });
1236
+ if (result.error) {
1237
+ if (signal.aborted && !question.signal?.aborted) {
1238
+ return { ok: false, error: OVERALL_TIMEOUT_REASON };
1239
+ }
1240
+ if (question.signal?.aborted) {
1241
+ return { ok: false, error: CALL_CANCELLED_REASON };
1242
+ }
1243
+ return { ok: false, error: result.error };
1244
+ }
1245
+ const judged = parseJudge(result.text, question, this.refusalOptionId);
1246
+ if (!judged.ok) {
1247
+ return {
1248
+ ok: false,
1249
+ error: withTruncationNote(judged.error, result, profile.judgeMaxTokens)
1250
+ };
1251
+ }
1252
+ return judged;
1253
+ }
1254
+ resolveCaller(seatIndex) {
1255
+ if (seatIndex !== void 0) {
1256
+ if (this.seatCaller) return this.seatCaller(seatIndex);
1257
+ return this.caller ?? this.judgeCaller;
1258
+ }
1259
+ if (this.judgeCaller) return this.judgeCaller;
1260
+ if (this.seatCaller) return this.seatCaller(0);
1261
+ return this.caller;
1262
+ }
1263
+ async safeCall(input) {
1264
+ const effectiveCaller = this.resolveCaller(input.seatIndex);
1265
+ const resolvedTarget = this.resolveCouncilTarget(input.target);
1266
+ const startedAt = Date.now();
1267
+ try {
1268
+ const result = await effectiveCaller.call({
1269
+ system: input.system,
1270
+ userPrompt: input.userPrompt,
1271
+ responseFormat: { type: "json_object" },
1272
+ maxTokens: input.maxTokens,
1273
+ timeoutMs: input.timeoutMs,
1274
+ signal: input.signal,
1275
+ ...resolvedTarget?.providerId ? { providerId: resolvedTarget.providerId } : {},
1276
+ ...resolvedTarget?.model ? { model: resolvedTarget.model } : {},
1277
+ ...resolvedTarget?.role ? { role: resolvedTarget.role } : {},
1278
+ ...resolvedTarget?.fallbackModels && resolvedTarget.fallbackModels.length > 0 ? { fallbackModels: [...resolvedTarget.fallbackModels] } : {}
1279
+ });
1280
+ addUsage(input.usage, result);
1281
+ return result;
1282
+ } catch (error) {
1283
+ const failed = {
1284
+ text: "",
1285
+ model: resolvedTarget?.model ?? "",
1286
+ provider: resolvedTarget?.providerId ?? "",
1287
+ tokens: { input: 0, output: 0, total: 0 },
1288
+ durationMs: Math.max(0, Date.now() - startedAt),
1289
+ fromFallback: false,
1290
+ error: errorMessage(error)
1291
+ };
1292
+ addUsage(input.usage, failed);
1293
+ return failed;
1294
+ }
1295
+ }
1296
+ resolveCouncilTarget(target) {
1297
+ if (!target) return void 0;
1298
+ if (!target.fallbackProfile) return target;
1299
+ const mgr = this.fallbackProfileManager;
1300
+ if (!mgr) return target;
1301
+ const chain = mgr.resolve(target.fallbackProfile);
1302
+ if (chain.length === 0) return target;
1303
+ const combined = [
1304
+ ...chain.map((e) => `${e.providerId}/${e.model}`),
1305
+ ...target.fallbackModels ?? []
1306
+ ];
1307
+ const seen = /* @__PURE__ */ new Set();
1308
+ const deduped = combined.filter((ref) => {
1309
+ if (seen.has(ref)) return false;
1310
+ seen.add(ref);
1311
+ return true;
1312
+ });
1313
+ return {
1314
+ ...target.providerId ? { providerId: target.providerId } : {},
1315
+ ...target.model ? { model: target.model } : {},
1316
+ ...target.role ? { role: target.role } : {},
1317
+ fallbackModels: deduped
1318
+ };
1319
+ }
1320
+ };
1321
+
1322
+ // src/execution/one-shot-llm.ts
1323
+ var DEFAULT_TIMEOUT_MS = 3e4;
1324
+ var DEFAULT_MAX_TOKENS = 1024;
1325
+ var OneShotOrchestrator = class {
1326
+ opts;
1327
+ constructor(opts) {
1328
+ this.opts = opts;
1329
+ }
1330
+ /**
1331
+ * Make a one-shot LLM call. Resolves provider+model, applies fallback
1332
+ * chain on transient errors, and returns a structured result.
1333
+ *
1334
+ * Never throws — all errors are captured in `OneShotLLMResult.error`.
1335
+ */
1336
+ async call(input) {
1337
+ const startedAt = performance.now();
1338
+ const config = this.opts.getConfig();
1339
+ const target = this.resolveTarget(input, config);
1340
+ if (!target) {
1341
+ return {
1342
+ text: "",
1343
+ model: input.model ?? config.model ?? "unknown",
1344
+ provider: input.providerId ?? config.provider ?? "unknown",
1345
+ tokens: { input: 0, output: 0, total: 0 },
1346
+ durationMs: Math.round(performance.now() - startedAt),
1347
+ fromFallback: false,
1348
+ attempts: 0,
1349
+ error: "No provider or model could be resolved. Check your config."
1350
+ };
1351
+ }
1352
+ let provider;
1353
+ try {
1354
+ provider = await this.opts.buildProvider(target.providerId, target.model);
1355
+ } catch (err) {
1356
+ return {
1357
+ text: "",
1358
+ model: target.model,
1359
+ provider: target.providerId,
1360
+ tokens: { input: 0, output: 0, total: 0 },
1361
+ durationMs: Math.round(performance.now() - startedAt),
1362
+ fromFallback: false,
1363
+ attempts: 0,
1364
+ error: `Cannot build provider "${target.providerId}": ${err instanceof Error ? err.message : String(err)}`
1365
+ };
1366
+ }
1367
+ const request = this.buildRequest(input, target.model);
1368
+ const signal = this.resolveSignal(input);
1369
+ const chain = this.resolveFallbackChain(input, config, target);
1370
+ const tracker = this.opts.statusTracker;
1371
+ let servingProviderId = provider.id;
1372
+ let servingModel = target.model;
1373
+ let fromFallback = false;
1374
+ let lastError;
1375
+ let fallbackEligible = false;
1376
+ let attempts = 0;
1377
+ if (tracker && !tracker.isAvailable(target.providerId, target.model) || !evaluateModelCalendar(config.modelAvailabilitySchedule, target.providerId, target.model).allowed) {
1378
+ this.opts.logger?.debug(
1379
+ `one-shot: primary "${target.providerId}/${target.model}" is blocked \u2014 trying fallback`
1380
+ );
1381
+ } else {
1382
+ attempts += 1;
1383
+ const primaryAttempt = await this.tryCall(
1384
+ provider,
1385
+ request,
1386
+ signal,
1387
+ target.providerId,
1388
+ target.model
1389
+ );
1390
+ const result = primaryAttempt.response;
1391
+ lastError = primaryAttempt.error;
1392
+ fallbackEligible = primaryAttempt.fallbackEligible;
1393
+ if (result) {
1394
+ tracker?.recordSuccess(target.providerId, target.model);
1395
+ servingProviderId = provider.id;
1396
+ servingModel = target.model;
1397
+ return this.buildResult(result, servingProviderId, servingModel, false, startedAt, attempts);
1398
+ }
1399
+ if (!fallbackEligible || chain.length === 0) {
1400
+ return this.buildErrorResult(
1401
+ lastError,
1402
+ target.providerId,
1403
+ target.model,
1404
+ false,
1405
+ startedAt,
1406
+ attempts
1407
+ );
1408
+ }
1409
+ }
1410
+ const estimatedTokens = estimateRequestTokens(request.messages, request.system, []).total;
1411
+ const usableChain = tracker ? chain.filter((e) => tracker.isAvailable(e.providerId, e.model)) : chain;
1412
+ for (const entry of usableChain) {
1413
+ if (!evaluateModelCalendar(config.modelAvailabilitySchedule, entry.providerId, entry.model).allowed)
1414
+ continue;
1415
+ if (tracker && !tracker.isAvailable(entry.providerId, entry.model)) {
1416
+ continue;
1417
+ }
1418
+ if (entry.providerId === provider.id && entry.model === target.model) continue;
1419
+ let fbProvider;
1420
+ try {
1421
+ fbProvider = await this.opts.buildProvider(entry.providerId, entry.model);
1422
+ } catch (err) {
1423
+ lastError = err;
1424
+ continue;
1425
+ }
1426
+ const fbMaxContext = fbProvider.capabilities.maxContext;
1427
+ if (typeof fbMaxContext === "number" && Number.isFinite(fbMaxContext) && fbMaxContext > 0 && estimatedTokens > fbMaxContext) {
1428
+ this.opts.logger?.debug(
1429
+ `one-shot: skipping "${entry.providerId}/${entry.model}" \u2014 context window (${fbMaxContext}) is smaller than request tokens (${estimatedTokens})`
1430
+ );
1431
+ continue;
1432
+ }
1433
+ servingProviderId = fbProvider.id;
1434
+ servingModel = entry.model;
1435
+ attempts += 1;
1436
+ const attempt = await this.tryCall(
1437
+ fbProvider,
1438
+ this.buildRequest(input, entry.model),
1439
+ signal,
1440
+ entry.providerId,
1441
+ entry.model
1442
+ );
1443
+ if (attempt.response) {
1444
+ tracker?.recordSuccess(entry.providerId, entry.model);
1445
+ fromFallback = true;
1446
+ return this.buildResult(
1447
+ attempt.response,
1448
+ servingProviderId,
1449
+ servingModel,
1450
+ true,
1451
+ startedAt,
1452
+ attempts
1453
+ );
1454
+ }
1455
+ lastError = attempt.error;
1456
+ }
1457
+ return this.buildErrorResult(
1458
+ lastError,
1459
+ servingProviderId,
1460
+ servingModel,
1461
+ fromFallback,
1462
+ startedAt,
1463
+ attempts
1464
+ );
1465
+ }
1466
+ // ── Private helpers ─────────────────────────────────────────────
1467
+ /**
1468
+ * Resolve the target provider + model from input + config.
1469
+ * Priority: role-based routing > explicit providerId+model > defaults.
1470
+ */
1471
+ resolveTarget(input, config) {
1472
+ if (input.role && this.opts.modelRouter) {
1473
+ const pick = this.opts.modelRouter.pickForTask(input.role, "");
1474
+ const hasExplicitTarget = Boolean(input.providerId || input.model);
1475
+ if (pick && (pick.fromMatrix === true || !hasExplicitTarget)) {
1476
+ return { providerId: pick.provider, model: pick.model };
1477
+ }
1478
+ }
1479
+ if (input.providerId && input.model) {
1480
+ return { providerId: input.providerId, model: input.model };
1481
+ }
1482
+ if (input.model) {
1483
+ return { providerId: config.provider, model: input.model };
1484
+ }
1485
+ if (input.providerId) {
1486
+ return { providerId: input.providerId, model: config.model };
1487
+ }
1488
+ if (config.provider && config.model) {
1489
+ return { providerId: config.provider, model: config.model };
1490
+ }
1491
+ return void 0;
1492
+ }
1493
+ /**
1494
+ * Build a Provider Request from the input.
1495
+ */
1496
+ buildRequest(input, model) {
1497
+ const messages = [...input.messages ?? []];
1498
+ if (input.userPrompt) {
1499
+ messages.push({ role: "user", content: input.userPrompt });
1500
+ }
1501
+ const system = asTextBlocks(input.system);
1502
+ return {
1503
+ model,
1504
+ ...system.length > 0 ? { system } : {},
1505
+ messages,
1506
+ maxTokens: input.maxTokens ?? DEFAULT_MAX_TOKENS,
1507
+ ...input.temperature !== void 0 ? { temperature: input.temperature } : {},
1508
+ ...input.responseFormat ? { responseFormat: input.responseFormat } : {}
1509
+ };
1510
+ }
1511
+ /**
1512
+ * Resolve the abort signal. Provider calls always receive the per-call
1513
+ * timeout, composed with external cancellation when the caller supplies it.
1514
+ */
1515
+ resolveSignal(input) {
1516
+ const timeoutSignal = AbortSignal.timeout(input.timeoutMs ?? DEFAULT_TIMEOUT_MS);
1517
+ return input.signal ? AbortSignal.any([input.signal, timeoutSignal]) : timeoutSignal;
1518
+ }
1519
+ /**
1520
+ * Build the fallback model chain from input + config + current target.
1521
+ * Delegates to {@link FallbackProfileManager.resolveCandidates} — the
1522
+ * shared constructor used by both the agent loop and the one-shot path,
1523
+ * so both produce identical ordering, depth, and fromExplicitSource
1524
+ * semantics.
1525
+ *
1526
+ * The injected {@link OneShotOrchestratorOptions.fallbackProfileManager}
1527
+ * is the only allowed manager — OneShot never owns a private snapshot
1528
+ * so a live `ConfigStore` change reaches every call without rebuilding
1529
+ * the manager.
1530
+ */
1531
+ resolveFallbackChain(input, config, target) {
1532
+ return this.opts.fallbackProfileManager.resolveCandidates(target, {
1533
+ fallbackModels: input.fallbackModels && input.fallbackModels.length > 0 ? input.fallbackModels : config.fallbackModels,
1534
+ // The one-shot already tried `target` as the primary before entering
1535
+ // the chain. Passing it as `primary` suppresses the primary-insertion
1536
+ // step in resolveCandidates (primary === current → not pushed).
1537
+ primary: target
1538
+ });
1539
+ }
1540
+ /** Attempt a provider call while preserving the actual failure for callers. */
1541
+ async tryCall(provider, request, signal, providerId, model) {
1542
+ try {
1543
+ const direct = (req) => provider.complete(req, { signal });
1544
+ const response = this.opts.wrapProviderCall ? await this.opts.wrapProviderCall(request, direct) : await direct(request);
1545
+ return { response, fallbackEligible: false };
1546
+ } catch (err) {
1547
+ if (err instanceof ProviderError && providerId && model) {
1548
+ this.opts.statusTracker?.recordFailure(
1549
+ providerId,
1550
+ model,
1551
+ err.kind,
1552
+ err.status,
1553
+ err.describe(),
1554
+ { retryAfterMs: err.body?.retryAfterMs }
1555
+ );
1556
+ }
1557
+ return {
1558
+ error: err,
1559
+ // Provider failures retain their normal fallback policy. A plain error
1560
+ // thrown by the host wrapper is a local refusal (for example,
1561
+ // prompt-firewall block mode), not evidence that another provider may
1562
+ // succeed; retrying would only replay the same blocked request and
1563
+ // misattribute the final error to the last fallback.
1564
+ fallbackEligible: !signal.aborted && (err instanceof ProviderError ? isFallbackWorthy(err.kind) : this.opts.wrapProviderCall === void 0)
1565
+ };
1566
+ }
1567
+ }
1568
+ /** Build a success result from a provider Response. */
1569
+ buildResult(response, servingProviderId, servingModel, fromFallback, startedAt, attempts) {
1570
+ const textBlocks = response.content.filter(isTextBlock);
1571
+ const text = textBlocks.map((b) => b.text).join("\n").trim();
1572
+ return {
1573
+ text: text || "(empty response)",
1574
+ model: response.model ?? servingModel,
1575
+ provider: servingProviderId,
1576
+ tokens: {
1577
+ input: response.usage?.input ?? 0,
1578
+ output: response.usage?.output ?? 0,
1579
+ total: (response.usage?.input ?? 0) + (response.usage?.output ?? 0)
1580
+ },
1581
+ durationMs: Math.round(performance.now() - startedAt),
1582
+ fromFallback,
1583
+ attempts,
1584
+ stopReason: response.stopReason
1585
+ };
1586
+ }
1587
+ /** Build a total-failure error result. */
1588
+ buildErrorResult(error, servingProviderId, servingModel, fromFallback, startedAt, attempts) {
1589
+ return {
1590
+ text: "",
1591
+ model: servingModel,
1592
+ provider: servingProviderId,
1593
+ tokens: { input: 0, output: 0, total: 0 },
1594
+ durationMs: Math.round(performance.now() - startedAt),
1595
+ fromFallback,
1596
+ attempts,
1597
+ error: error instanceof Error ? error.message : String(error ?? "Unknown error")
1598
+ };
1599
+ }
1600
+ };
1601
+ function asTextBlocks(system) {
1602
+ if (!system) return [];
1603
+ if (Array.isArray(system)) return system;
1604
+ return [{ type: "text", text: system }];
1605
+ }
1606
+
1607
+ export {
1608
+ COUNCIL_REFUSAL_OPTION_ID,
1609
+ DEFAULT_COUNCIL_MAX_CONCURRENCY,
1610
+ MAX_COUNCIL_CONCURRENCY,
1611
+ BUILTIN_COUNCIL_PERSONAS,
1612
+ BUILTIN_COUNCIL_PERSONA_IDS,
1613
+ CouncilPersonaRegistry,
1614
+ DEFAULT_COUNCIL_PERSONA_REGISTRY,
1615
+ createCouncilPersonaRegistry,
1616
+ DEFAULT_COUNCIL_QUORUM_FRACTION,
1617
+ DEFAULT_COUNCIL_APPROVAL_FRACTION,
1618
+ DEFAULT_COUNCIL_VOTER_MAX_TOKENS,
1619
+ DEFAULT_COUNCIL_JUDGE_MAX_TOKENS,
1620
+ DEFAULT_COUNCIL_PER_CALL_TIMEOUT_MS,
1621
+ DEFAULT_COUNCIL_OVERALL_TIMEOUT_MS,
1622
+ BUILTIN_COUNCIL_PROFILES,
1623
+ CouncilProfileRegistry,
1624
+ DEFAULT_COUNCIL_PROFILE_REGISTRY,
1625
+ createCouncilProfileRegistry,
1626
+ resolveCouncilProfile,
1627
+ normalizeCouncilProfile,
1628
+ COUNCIL_VOTER_PROMPT_PATH,
1629
+ COUNCIL_JUDGE_PROMPT_PATH,
1630
+ buildCouncilVoterSystemPrompt,
1631
+ buildCouncilJudgeSystemPrompt,
1632
+ buildCouncilQuestionPrompt,
1633
+ buildCouncilVoterUserPrompt,
1634
+ buildCouncilJudgeUserPrompt,
1635
+ validateCouncilOptions,
1636
+ resolveCouncilVotes,
1637
+ CouncilOrchestrator,
1638
+ OneShotOrchestrator
1639
+ };
1640
+ //# sourceMappingURL=chunk-CE4ZOCOI.js.map