@wrongstack/core 0.298.3 → 0.300.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 (70) hide show
  1. package/dist/chronicle/index.js +4 -1
  2. package/dist/coordination/agents/index.js +4 -1
  3. package/dist/coordination/director.d.ts +8 -0
  4. package/dist/coordination/fleet-manager.d.ts +48 -3
  5. package/dist/coordination/ifleet-manager.d.ts +2 -0
  6. package/dist/coordination/index.js +127 -24
  7. package/dist/coordination/multi-agent-coordinator.d.ts +1 -0
  8. package/dist/core/fallback-model.d.ts +48 -0
  9. package/dist/core/index.d.ts +3 -2
  10. package/dist/core/index.js +288 -34
  11. package/dist/core/instruction-template.d.ts +80 -0
  12. package/dist/core/system-prompt-blocks.d.ts +10 -1
  13. package/dist/core/system-prompt-builder.d.ts +35 -1
  14. package/dist/defaults/index.js +358 -117
  15. package/dist/design/index.js +4 -1
  16. package/dist/execution/autonomy-brain.d.ts +7 -0
  17. package/dist/execution/council-brain.d.ts +17 -2
  18. package/dist/execution/council-orchestrator.d.ts +23 -4
  19. package/dist/execution/council-personas.d.ts +10 -0
  20. package/dist/execution/council-prompts.d.ts +12 -1
  21. package/dist/execution/index.d.ts +1 -1
  22. package/dist/execution/index.js +412 -145
  23. package/dist/fleet-notifier.d.ts +9 -2
  24. package/dist/goal/index.js +4 -1
  25. package/dist/hooks/index.js +140 -10
  26. package/dist/hq/exposure.d.ts +0 -11
  27. package/dist/hq/index.js +34 -8
  28. package/dist/hq/protocol/client.d.ts +14 -1
  29. package/dist/hq/protocol/fleet.d.ts +22 -0
  30. package/dist/hq/protocol.js +12 -1
  31. package/dist/index.d.ts +2 -1
  32. package/dist/index.js +1718 -753
  33. package/dist/infrastructure/index.js +50 -2
  34. package/dist/infrastructure/mcp-servers.d.ts +35 -0
  35. package/dist/kernel/events/brain-events.d.ts +9 -0
  36. package/dist/kernel/events/provider-events.d.ts +49 -2
  37. package/dist/kernel/events/sdd-events.d.ts +2 -0
  38. package/dist/models/index.js +1 -1
  39. package/dist/plugin/api.d.ts +6 -0
  40. package/dist/plugin/config.d.ts +55 -0
  41. package/dist/plugin/index.d.ts +1 -1
  42. package/dist/plugin/index.js +138 -22
  43. package/dist/security/index.d.ts +1 -1
  44. package/dist/security/index.js +157 -42
  45. package/dist/security/permission-helpers.d.ts +23 -6
  46. package/dist/security/permission-policy.d.ts +16 -0
  47. package/dist/security/totp.d.ts +14 -0
  48. package/dist/storage/director-state.d.ts +7 -0
  49. package/dist/storage/index.js +46 -9
  50. package/dist/tools/council-tool.d.ts +1 -1
  51. package/dist/tools/fallback-system-config-view-tool.d.ts +1 -1
  52. package/dist/tools/index.js +449 -112
  53. package/dist/types/config/skills-fleet-brain.d.ts +4 -2
  54. package/dist/types/config/tools.d.ts +99 -0
  55. package/dist/types/council.d.ts +11 -0
  56. package/dist/types/index.d.ts +3 -2
  57. package/dist/types/index.js +3 -3
  58. package/dist/types/multi-agent.d.ts +10 -0
  59. package/dist/types/one-shot-llm.d.ts +31 -3
  60. package/dist/types/plugin.d.ts +28 -0
  61. package/dist/types/session.d.ts +5 -1
  62. package/dist/utils/index.js +4 -1
  63. package/dist/utils/wstack-paths.d.ts +2 -0
  64. package/dist/worktree/index.js +47 -25
  65. package/dist/worktree/worktree-manager.d.ts +16 -10
  66. package/instructions/coordination/subagent-baseline.md +8 -0
  67. package/instructions/system-lite.md +83 -3
  68. package/instructions/system-pro.md +286 -97
  69. package/instructions/system.md +236 -85
  70. package/package.json +3 -3
@@ -3979,7 +3979,11 @@ async function completeBrainLlm(target, input) {
3979
3979
  return (await completeBrainLlmDetailed(target, input)).text;
3980
3980
  }
3981
3981
  async function completeBrainLlmDetailed(target, input) {
3982
- const signal = AbortSignal.timeout(input.timeoutMs);
3982
+ if (input.signal?.aborted) {
3983
+ throw new DOMException("Brain call aborted before it started.", "AbortError");
3984
+ }
3985
+ const timeoutSignal = AbortSignal.timeout(input.timeoutMs);
3986
+ const signal = input.signal ? AbortSignal.any([input.signal, timeoutSignal]) : timeoutSignal;
3983
3987
  const response = await target.provider.complete(
3984
3988
  {
3985
3989
  model: target.model,
@@ -4193,7 +4197,7 @@ function truncate(s, max) {
4193
4197
  var QUOTA_EXHAUSTED_RE = /(?:insufficient|exhausted|depleted|exceeded|no|not enough)[-_\s]*(?:quota|credit|balance)|(?:quota|credit|balance)(?:\s+(?:has|have))?(?:\s+been)?[-_\s]*(?:exhausted|depleted|exceeded|insufficient)|billing[_\s-]*(?:hard[_\s-]*)?limit|payment required|spending limit|plan limit|usage[-_\s]*limit[-_\s]*(?:reached|exceeded)/i;
4194
4198
 
4195
4199
  // src/types/provider.ts
4196
- var CONTEXT_OVERFLOW_RE = /context.length|context.window|maximum context|max.*tokens?.*exceeded|prompt is too long|too long|exceeds the context|\btokens\b.*exceed|too many tokens|reduce the length|resulted in \d+ tokens|input.{0,12}too (?:large|long)|context_length_exceeded/i;
4200
+ var CONTEXT_OVERFLOW_RE = /context.length|context.window|maximum context|max.*tokens?.*exceeded|(?:prompt|request|input|messages?).{0,12}too (?:large|long)|exceeds the context|\btokens\b.*exceed|too many tokens|reduce the length|resulted in \d+ tokens|context_length_exceeded/i;
4197
4201
  var CONTENT_FILTER_RE = /content.(filter|policy|moderation)|safety (system|filter)/i;
4198
4202
  var RATE_LIMIT_EXCEEDED_RE = /rate[-_\s]*limit[-_\s]*exceeded/i;
4199
4203
  function classifyProviderError(status, body, message) {
@@ -4203,7 +4207,7 @@ function classifyProviderError(status, body, message) {
4203
4207
  if (status === 408) return "timeout";
4204
4208
  if (status === 599) return "stream_hang";
4205
4209
  if (status === 402 || QUOTA_EXHAUSTED_RE.test(text)) return "quota_exhausted";
4206
- if (status === 429 && body?.message && RATE_LIMIT_EXCEEDED_RE.test(body.message)) {
4210
+ if (status === 429 && body?.message && body.type !== "rate_limit_exceeded" && RATE_LIMIT_EXCEEDED_RE.test(body.message)) {
4207
4211
  return "quota_exhausted";
4208
4212
  }
4209
4213
  if (type === "rate_limit_error" || status === 429) return "rate_limit";
@@ -4280,7 +4284,7 @@ var ProviderError = class extends WrongStackError {
4280
4284
  const e = err;
4281
4285
  const name = e.name;
4282
4286
  if (typeof name !== "string" || !name.endsWith("Error")) return false;
4283
- return typeof e.status === "number" && typeof e.retryable === "boolean" && typeof e.kind === "string";
4287
+ return typeof e.status === "number" && typeof e.retryable === "boolean" && typeof e.kind === "string" && typeof e.describe === "function";
4284
4288
  }
4285
4289
  constructor(message, status, retryable, providerId, opts = {}) {
4286
4290
  const kind = opts.kind ?? classifyProviderError(status, opts.body, message);
@@ -4785,6 +4789,9 @@ var BUILTIN_COUNCIL_PERSONAS = Object.freeze([
4785
4789
  tags: ["users", "usability", "accessibility"]
4786
4790
  })
4787
4791
  ]);
4792
+ var BUILTIN_COUNCIL_PERSONA_IDS = Object.freeze(
4793
+ BUILTIN_COUNCIL_PERSONAS.map((persona) => persona.id)
4794
+ );
4788
4795
  var CouncilPersonaRegistry = class _CouncilPersonaRegistry {
4789
4796
  byId;
4790
4797
  constructor(personas = []) {
@@ -5171,22 +5178,29 @@ function buildCouncilJudgeUserPrompt(question, votes, opts = {}) {
5171
5178
  "</council-ballots>"
5172
5179
  ].filter(Boolean).join("\n\n");
5173
5180
  }
5174
- function normalizeOptions(options) {
5175
- if (!options) return [];
5181
+ function validateCouncilOptions(options) {
5182
+ const errors = [];
5176
5183
  const seen = /* @__PURE__ */ new Set();
5177
- return options.map((option) => {
5184
+ for (const option of options ?? []) {
5178
5185
  const id = option.id.trim();
5179
- const label = option.label.trim();
5180
- if (!id) throw new Error("buildCouncilQuestionPrompt: option id must not be empty.");
5181
- if (!label) throw new Error(`buildCouncilQuestionPrompt: option "${id}" needs a label.`);
5182
- if (seen.has(id)) throw new Error(`buildCouncilQuestionPrompt: duplicate option id "${id}".`);
5186
+ if (!id) errors.push("Every option must have a non-empty `id`.");
5187
+ if (!option.label.trim()) errors.push(`Option "${id || "<empty>"}" must have a label.`);
5188
+ if (seen.has(id)) errors.push(`Duplicate option id "${id}".`);
5183
5189
  seen.add(id);
5184
- return {
5185
- id,
5186
- label,
5187
- ...option.consequence?.trim() ? { consequence: option.consequence.trim() } : {}
5188
- };
5189
- });
5190
+ }
5191
+ return errors;
5192
+ }
5193
+ function normalizeOptions(options) {
5194
+ if (!options) return [];
5195
+ const errors = validateCouncilOptions(options);
5196
+ if (errors.length > 0) {
5197
+ throw new Error(`buildCouncilQuestionPrompt: ${errors.join("; ")}`);
5198
+ }
5199
+ return options.map((option) => ({
5200
+ id: option.id.trim(),
5201
+ label: option.label.trim(),
5202
+ ...option.consequence?.trim() ? { consequence: option.consequence.trim() } : {}
5203
+ }));
5190
5204
  }
5191
5205
  function requiredInstruction(path17) {
5192
5206
  const text = readBundledInstructionText(path17);
@@ -5294,6 +5308,8 @@ function requireFraction(value, label) {
5294
5308
  var COUNCIL_REFUSAL_OPTION_ID = "council_refuse";
5295
5309
  var DEFAULT_COUNCIL_MAX_CONCURRENCY = 3;
5296
5310
  var MAX_COUNCIL_CONCURRENCY = 8;
5311
+ var OVERALL_TIMEOUT_REASON = "Council overall timeout exceeded.";
5312
+ var CALL_CANCELLED_REASON = "Cancelled.";
5297
5313
  var CouncilOrchestrator = class {
5298
5314
  caller;
5299
5315
  personas;
@@ -5304,6 +5320,17 @@ var CouncilOrchestrator = class {
5304
5320
  fallbackProfileManager;
5305
5321
  seatCaller;
5306
5322
  judgeCaller;
5323
+ /**
5324
+ * Normalized ad-hoc profiles keyed by the caller's config object identity.
5325
+ * The Brain adapter reuses ONE profile object for every decision, so this
5326
+ * avoids re-validating + re-freezing it on every ask() without caching
5327
+ * string-keyed registry lookups (those are already O(1)).
5328
+ *
5329
+ * Hosts must treat ad-hoc profile configs as IMMUTABLE once passed to
5330
+ * ask(): the cache is keyed by object identity and never invalidated, so
5331
+ * mutating a cached profile would silently serve the first snapshot.
5332
+ */
5333
+ profileCache = /* @__PURE__ */ new WeakMap();
5307
5334
  constructor(opts) {
5308
5335
  if (!opts.caller && !opts.seatCaller && !opts.judgeCaller) {
5309
5336
  throw new Error(
@@ -5322,13 +5349,33 @@ var CouncilOrchestrator = class {
5322
5349
  this.seatCaller = opts.seatCaller;
5323
5350
  this.judgeCaller = opts.judgeCaller;
5324
5351
  }
5325
- async ask(question) {
5326
- const startedAt = Date.now();
5327
- const profile = resolveCouncilProfile(question.profile, {
5352
+ /**
5353
+ * Resolve the effective profile for a question. String ids and the default
5354
+ * go through the registry (already O(1)); ad-hoc config objects are
5355
+ * normalized once per stable object identity and cached, because hosts such
5356
+ * as the Brain adapter pass the same profile object on every ask().
5357
+ */
5358
+ resolveProfile(profile) {
5359
+ if (typeof profile === "string" || profile === void 0) {
5360
+ return resolveCouncilProfile(profile, {
5361
+ registry: this.profiles,
5362
+ personas: this.personas,
5363
+ defaultProfile: this.defaultProfile
5364
+ });
5365
+ }
5366
+ const cached = this.profileCache.get(profile);
5367
+ if (cached) return cached;
5368
+ const resolved = resolveCouncilProfile(profile, {
5328
5369
  registry: this.profiles,
5329
5370
  personas: this.personas,
5330
5371
  defaultProfile: this.defaultProfile
5331
5372
  });
5373
+ this.profileCache.set(profile, resolved);
5374
+ return resolved;
5375
+ }
5376
+ async ask(question) {
5377
+ const startedAt = Date.now();
5378
+ const profile = this.resolveProfile(question.profile);
5332
5379
  validateRefusalCollision(question, this.refusalOptionId);
5333
5380
  const timeoutSignal = AbortSignal.timeout(profile.overallTimeoutMs);
5334
5381
  const signal = question.signal ? AbortSignal.any([question.signal, timeoutSignal]) : timeoutSignal;
@@ -5345,11 +5392,16 @@ var CouncilOrchestrator = class {
5345
5392
  try {
5346
5393
  return await this.callSeat(question, profile, seat, i, signal, usage);
5347
5394
  } catch (error) {
5395
+ const timedOut = signal.aborted && !question.signal?.aborted;
5348
5396
  return {
5349
5397
  seatId: seat.id,
5350
5398
  persona: seat.persona,
5351
- status: signal.aborted ? "cancelled" : "failed",
5352
- error: errorMessage(error)
5399
+ // Only the caller's own cancel is a "cancelled" vote; the overall
5400
+ // budget expiring is a failure (timeout), matching the envelope.
5401
+ status: question.signal?.aborted ? "cancelled" : "failed",
5402
+ // Canonical text for aborted-by-budget or cancelled seats, so one
5403
+ // event does not surface a different string per code path.
5404
+ error: question.signal?.aborted ? CALL_CANCELLED_REASON : timedOut ? OVERALL_TIMEOUT_REASON : errorMessage(error)
5353
5405
  };
5354
5406
  }
5355
5407
  }
@@ -5359,7 +5411,7 @@ var CouncilOrchestrator = class {
5359
5411
  if (question.signal?.aborted) {
5360
5412
  return resultEnvelope({
5361
5413
  status: "cancelled",
5362
- reason: "Council call cancelled.",
5414
+ reason: CALL_CANCELLED_REASON,
5363
5415
  resolution: "none",
5364
5416
  votes,
5365
5417
  profile,
@@ -5372,14 +5424,17 @@ var CouncilOrchestrator = class {
5372
5424
  if (timeoutSignal.aborted) {
5373
5425
  return resultEnvelope({
5374
5426
  status: "failed",
5375
- reason: "Council overall timeout exceeded.",
5427
+ reason: OVERALL_TIMEOUT_REASON,
5376
5428
  resolution: "none",
5377
5429
  votes,
5378
5430
  profile,
5379
5431
  usage,
5380
5432
  startedAt,
5381
5433
  warnings,
5382
- errors: [...errors, "Council overall timeout exceeded."]
5434
+ // Seat-prefixed errors normally carry the canonical timeout text, but
5435
+ // a signal-blind caller can resolve valid votes even after the budget
5436
+ // expired — append the standalone entry only when nothing carries it.
5437
+ errors: errors.some((entry) => entry.includes(OVERALL_TIMEOUT_REASON)) ? errors : [...errors, OVERALL_TIMEOUT_REASON]
5383
5438
  });
5384
5439
  }
5385
5440
  if (!question.options || question.options.length === 0) {
@@ -5406,7 +5461,17 @@ var CouncilOrchestrator = class {
5406
5461
  );
5407
5462
  }
5408
5463
  async callSeat(question, profile, seat, seatIndex, signal, usage) {
5409
- if (signal.aborted) return cancelledVote(seat);
5464
+ if (signal.aborted) {
5465
+ return question.signal?.aborted ? cancelledVote(seat) : {
5466
+ seatId: seat.id,
5467
+ persona: seat.persona,
5468
+ status: "failed",
5469
+ ...seat.target?.providerId ? { provider: seat.target.providerId } : {},
5470
+ ...seat.target?.model ? { model: seat.target.model } : {},
5471
+ durationMs: 0,
5472
+ error: OVERALL_TIMEOUT_REASON
5473
+ };
5474
+ }
5410
5475
  let persona;
5411
5476
  try {
5412
5477
  persona = this.personas.require(seat.persona);
@@ -5432,12 +5497,15 @@ var CouncilOrchestrator = class {
5432
5497
  });
5433
5498
  const metadata = callMetadata(result);
5434
5499
  if (result.error) {
5500
+ const timedOut = signal.aborted && !question.signal?.aborted;
5435
5501
  return {
5436
5502
  seatId: seat.id,
5437
5503
  persona: seat.persona,
5438
- status: signal.aborted ? "cancelled" : "failed",
5504
+ status: question.signal?.aborted ? "cancelled" : "failed",
5439
5505
  ...metadata,
5440
- error: result.error
5506
+ // Canonical text for cancelled or aborted-by-budget seats, so one
5507
+ // cancel/timeout event does not surface a raw provider string.
5508
+ error: question.signal?.aborted ? CALL_CANCELLED_REASON : timedOut ? OVERALL_TIMEOUT_REASON : result.error
5441
5509
  };
5442
5510
  }
5443
5511
  const parsed = parseVote(result.text, question, this.refusalOptionId);
@@ -5536,9 +5604,27 @@ var CouncilOrchestrator = class {
5536
5604
  signal,
5537
5605
  usage
5538
5606
  );
5607
+ if (signal.aborted) {
5608
+ const cancelled = question.signal?.aborted === true;
5609
+ const reason = cancelled ? CALL_CANCELLED_REASON : OVERALL_TIMEOUT_REASON;
5610
+ return resultEnvelope({
5611
+ status: cancelled ? "cancelled" : "failed",
5612
+ reason,
5613
+ resolution: "none",
5614
+ votes,
5615
+ profile,
5616
+ usage,
5617
+ startedAt,
5618
+ warnings,
5619
+ errors: errors.some((entry) => entry.includes(reason)) ? errors : [...errors, reason],
5620
+ judgeUsed: true
5621
+ });
5622
+ }
5539
5623
  if (!judged.ok) {
5540
5624
  return resultEnvelope({
5541
- status: signal.aborted ? "cancelled" : "abstained",
5625
+ // User cancel -> cancelled; overall budget expired mid-judge -> failed;
5626
+ // otherwise the judge simply failed -> abstained (can't decide).
5627
+ status: question.signal?.aborted ? "cancelled" : signal.aborted ? "failed" : "abstained",
5542
5628
  reason: judged.error,
5543
5629
  resolution: "none",
5544
5630
  votes,
@@ -5598,6 +5684,19 @@ var CouncilOrchestrator = class {
5598
5684
  });
5599
5685
  }
5600
5686
  if (!profile.judge) {
5687
+ if (divergentStances(valid)) {
5688
+ return resultEnvelope({
5689
+ status: "abstained",
5690
+ reason: "Council produced multiple distinct stances and has no judge to reconcile them.",
5691
+ resolution: "none",
5692
+ votes,
5693
+ profile,
5694
+ usage,
5695
+ startedAt,
5696
+ warnings,
5697
+ errors
5698
+ });
5699
+ }
5601
5700
  const first = valid[0];
5602
5701
  if (!first) {
5603
5702
  return resultEnvelope({
@@ -5634,9 +5733,30 @@ var CouncilOrchestrator = class {
5634
5733
  signal,
5635
5734
  usage
5636
5735
  );
5736
+ if (signal.aborted) {
5737
+ const cancelled = question.signal?.aborted === true;
5738
+ const reason = cancelled ? CALL_CANCELLED_REASON : OVERALL_TIMEOUT_REASON;
5739
+ return resultEnvelope({
5740
+ status: cancelled ? "cancelled" : "failed",
5741
+ reason,
5742
+ resolution: "none",
5743
+ votes,
5744
+ profile,
5745
+ usage,
5746
+ startedAt,
5747
+ warnings,
5748
+ errors: errors.some((entry) => entry.includes(reason)) ? errors : [...errors, reason],
5749
+ judgeUsed: true
5750
+ });
5751
+ }
5637
5752
  if (!judged.ok) {
5638
5753
  return resultEnvelope({
5639
- status: signal.aborted ? "cancelled" : "failed",
5754
+ // User cancel -> cancelled; overall budget expired mid-judge -> failed;
5755
+ // otherwise the judge simply failed -> failed (open questions cannot
5756
+ // abstain for a judge failure — 'abstained' is reserved for quorum
5757
+ // failure and stance divergence; the option path maps this same
5758
+ // plain-judge-failure case to 'abstained' instead).
5759
+ status: question.signal?.aborted ? "cancelled" : "failed",
5640
5760
  reason: judged.error,
5641
5761
  resolution: "none",
5642
5762
  votes,
@@ -5675,18 +5795,28 @@ var CouncilOrchestrator = class {
5675
5795
  signal,
5676
5796
  usage
5677
5797
  });
5678
- if (result.error) return { ok: false, error: result.error };
5798
+ if (result.error) {
5799
+ if (signal.aborted && !question.signal?.aborted) {
5800
+ return { ok: false, error: OVERALL_TIMEOUT_REASON };
5801
+ }
5802
+ if (question.signal?.aborted) {
5803
+ return { ok: false, error: CALL_CANCELLED_REASON };
5804
+ }
5805
+ return { ok: false, error: result.error };
5806
+ }
5679
5807
  return parseJudge(result.text, question, this.refusalOptionId);
5680
5808
  }
5681
5809
  /**
5682
- * Resolve the effective LLM caller for a call. Voter seats use
5683
- * `seatCaller(seatIndex)` when wired. Judge seats (seatIndex undefined)
5684
- * use `judgeCaller` if set, otherwise `seatCaller(0)` if set, otherwise
5685
- * the shared `caller`.
5810
+ * Resolve the effective LLM caller for a call. Voter seats (defined
5811
+ * seatIndex) use `seatCaller(seatIndex)` when wired, otherwise the shared
5812
+ * `caller` a seat never falls through to the judge caller. Judge seats
5813
+ * (seatIndex undefined) use `judgeCaller` if set, otherwise `seatCaller(0)`
5814
+ * if set, otherwise the shared `caller`.
5686
5815
  */
5687
5816
  resolveCaller(seatIndex) {
5688
- if (seatIndex !== void 0 && this.seatCaller) {
5689
- return this.seatCaller(seatIndex);
5817
+ if (seatIndex !== void 0) {
5818
+ if (this.seatCaller) return this.seatCaller(seatIndex);
5819
+ return this.caller ?? this.judgeCaller;
5690
5820
  }
5691
5821
  if (this.judgeCaller) return this.judgeCaller;
5692
5822
  if (this.seatCaller) return this.seatCaller(0);
@@ -5695,6 +5825,7 @@ var CouncilOrchestrator = class {
5695
5825
  async safeCall(input) {
5696
5826
  const effectiveCaller = this.resolveCaller(input.seatIndex);
5697
5827
  const resolvedTarget = this.resolveCouncilTarget(input.target);
5828
+ const startedAt = Date.now();
5698
5829
  try {
5699
5830
  const result = await effectiveCaller.call({
5700
5831
  system: input.system,
@@ -5711,8 +5842,17 @@ var CouncilOrchestrator = class {
5711
5842
  addUsage(input.usage, result);
5712
5843
  return result;
5713
5844
  } catch (error) {
5714
- input.usage.calls += 1;
5715
- return emptyCallResult(errorMessage(error));
5845
+ const failed = {
5846
+ text: "",
5847
+ model: resolvedTarget?.model ?? "",
5848
+ provider: resolvedTarget?.providerId ?? "",
5849
+ tokens: { input: 0, output: 0, total: 0 },
5850
+ durationMs: Math.max(0, Date.now() - startedAt),
5851
+ fromFallback: false,
5852
+ error: errorMessage(error)
5853
+ };
5854
+ addUsage(input.usage, failed);
5855
+ return failed;
5716
5856
  }
5717
5857
  }
5718
5858
  /**
@@ -5744,47 +5884,77 @@ var CouncilOrchestrator = class {
5744
5884
  };
5745
5885
  }
5746
5886
  };
5747
- function parseVote(text, question, refusalOptionId) {
5887
+ function parseCouncilResponse(text, question, refusalOptionId, opts) {
5888
+ const roleLabel = opts.role === "judge" ? "Judge" : "Voter";
5748
5889
  const parsed = parseObject(text);
5749
5890
  if (!parsed.ok && (!question.options || question.options.length === 0)) {
5750
5891
  const fallback = text.trim();
5751
- if (fallback) return { ok: true, vote: { stance: fallback } };
5752
- return { ok: false, error: "Voter returned an empty response." };
5892
+ if (fallback) return { ok: true, value: { [opts.freeTextField]: fallback } };
5893
+ return { ok: false, error: `${roleLabel} returned an empty response.` };
5753
5894
  }
5754
5895
  if (!parsed.ok) return parsed;
5755
5896
  const rationale = optionalString(parsed.value["rationale"]);
5756
5897
  if (question.options && question.options.length > 0) {
5757
5898
  const optionId = optionalString(parsed.value["optionId"]);
5758
- const allowed = /* @__PURE__ */ new Set([...question.options.map((option) => option.id.trim()), refusalOptionId]);
5899
+ const allowed = /* @__PURE__ */ new Set([
5900
+ ...question.options.map((option) => option.id.trim()),
5901
+ refusalOptionId
5902
+ ]);
5759
5903
  if (!optionId || !allowed.has(optionId)) {
5760
- return { ok: false, error: "Voter returned an unknown or missing optionId." };
5904
+ return { ok: false, error: `${roleLabel} returned an unknown or missing optionId.` };
5761
5905
  }
5762
- return { ok: true, vote: { optionId, ...rationale ? { rationale } : {} } };
5906
+ return { ok: true, value: { optionId, ...rationale ? { rationale } : {} } };
5763
5907
  }
5764
- const stance = optionalString(parsed.value["stance"]);
5765
- if (!stance) return { ok: false, error: "Voter returned an empty or missing stance." };
5766
- return { ok: true, vote: { stance, ...rationale ? { rationale } : {} } };
5908
+ const freeText = optionalString(parsed.value[opts.freeTextField]);
5909
+ if (!freeText) {
5910
+ return {
5911
+ ok: false,
5912
+ error: `${roleLabel} returned an empty or missing ${opts.freeTextField}.`
5913
+ };
5914
+ }
5915
+ return { ok: true, value: { [opts.freeTextField]: freeText, ...rationale ? { rationale } : {} } };
5767
5916
  }
5768
- function parseJudge(text, question, refusalOptionId) {
5769
- const parsed = parseObject(text);
5770
- if (!parsed.ok && (!question.options || question.options.length === 0)) {
5771
- const fallback = text.trim();
5772
- if (fallback) return { ok: true, value: { answer: fallback } };
5773
- return { ok: false, error: "Judge returned an empty response." };
5917
+ function divergentStances(valid) {
5918
+ const seen = /* @__PURE__ */ new Set();
5919
+ for (const vote of valid) {
5920
+ const normalized = vote.stance.trim().replace(/^["'`]+|["'`]+$/g, "").toLowerCase().replace(/[.!?;:,]+$/g, "").replace(/\s+/g, " ").trim();
5921
+ if (!normalized) return true;
5922
+ seen.add(normalized);
5923
+ if (seen.size > 1) return true;
5774
5924
  }
5925
+ return false;
5926
+ }
5927
+ function parseVote(text, question, refusalOptionId) {
5928
+ const parsed = parseCouncilResponse(text, question, refusalOptionId, {
5929
+ role: "voter",
5930
+ freeTextField: "stance"
5931
+ });
5775
5932
  if (!parsed.ok) return parsed;
5776
- const rationale = optionalString(parsed.value["rationale"]);
5777
- if (question.options && question.options.length > 0) {
5778
- const optionId = optionalString(parsed.value["optionId"]);
5779
- const allowed = /* @__PURE__ */ new Set([...question.options.map((option) => option.id.trim()), refusalOptionId]);
5780
- if (!optionId || !allowed.has(optionId)) {
5781
- return { ok: false, error: "Judge returned an unknown or missing optionId." };
5933
+ const { optionId, stance, rationale } = parsed.value;
5934
+ return {
5935
+ ok: true,
5936
+ vote: {
5937
+ ...optionId ? { optionId } : {},
5938
+ ...stance ? { stance } : {},
5939
+ ...rationale ? { rationale } : {}
5782
5940
  }
5783
- return { ok: true, value: { optionId, ...rationale ? { rationale } : {} } };
5784
- }
5785
- const answer = optionalString(parsed.value["answer"]);
5786
- if (!answer) return { ok: false, error: "Judge returned an empty or missing answer." };
5787
- return { ok: true, value: { answer, ...rationale ? { rationale } : {} } };
5941
+ };
5942
+ }
5943
+ function parseJudge(text, question, refusalOptionId) {
5944
+ const parsed = parseCouncilResponse(text, question, refusalOptionId, {
5945
+ role: "judge",
5946
+ freeTextField: "answer"
5947
+ });
5948
+ if (!parsed.ok) return parsed;
5949
+ const { optionId, answer, rationale } = parsed.value;
5950
+ return {
5951
+ ok: true,
5952
+ value: {
5953
+ ...optionId ? { optionId } : {},
5954
+ ...answer ? { answer } : {},
5955
+ ...rationale ? { rationale } : {}
5956
+ }
5957
+ };
5788
5958
  }
5789
5959
  function parseObject(text) {
5790
5960
  const trimmed = text.trim();
@@ -5828,7 +5998,7 @@ function callMetadata(result) {
5828
5998
  };
5829
5999
  }
5830
6000
  function addUsage(usage, result) {
5831
- usage.calls += 1;
6001
+ usage.calls += Math.max(1, result.attempts ?? 1);
5832
6002
  usage.inputTokens += result.tokens.input;
5833
6003
  usage.outputTokens += result.tokens.output;
5834
6004
  usage.totalTokens += result.tokens.total;
@@ -5837,21 +6007,40 @@ function usageResult(usage, startedAt) {
5837
6007
  return Object.freeze({ ...usage, durationMs: Math.max(0, Date.now() - startedAt) });
5838
6008
  }
5839
6009
  function cancelledVote(seat) {
5840
- return { seatId: seat.id, persona: seat.persona, status: "cancelled", error: "Cancelled." };
6010
+ return {
6011
+ seatId: seat.id,
6012
+ persona: seat.persona,
6013
+ status: "cancelled",
6014
+ ...seat.target?.providerId ? { provider: seat.target.providerId } : {},
6015
+ ...seat.target?.model ? { model: seat.target.model } : {},
6016
+ durationMs: 0,
6017
+ error: CALL_CANCELLED_REASON
6018
+ };
5841
6019
  }
5842
6020
  function distinctTargetCount(votes, profile) {
5843
- const keys = votes.filter((vote) => vote.status === "valid").map(
5844
- (vote) => profile.distinctness === "provider" ? vote.provider : `${vote.provider ?? ""}/${vote.model ?? ""}`
5845
- ).filter(Boolean);
5846
- return new Set(keys).size;
6021
+ return new Set(distinctTargetKeys(votes, profile)).size;
6022
+ }
6023
+ function distinctTargetKeys(votes, profile) {
6024
+ const keys = [];
6025
+ for (const vote of votes) {
6026
+ if (vote.status !== "valid") continue;
6027
+ const provider = vote.provider?.trim() ?? "";
6028
+ const model = vote.model?.trim() ?? "";
6029
+ if (profile.distinctness === "provider") {
6030
+ if (provider) keys.push(provider);
6031
+ } else if (provider || model) {
6032
+ keys.push(`${provider}/${model}`);
6033
+ }
6034
+ }
6035
+ return keys;
5847
6036
  }
5848
6037
  function distinctnessWarnings(votes, profile) {
5849
6038
  if (profile.distinctness === "none") return [];
5850
- const valid = votes.filter((vote) => vote.status === "valid");
5851
- const distinct = distinctTargetCount(valid, profile);
5852
- if (valid.length > 1 && distinct < valid.length) {
6039
+ const keys = distinctTargetKeys(votes, profile);
6040
+ const distinct = new Set(keys).size;
6041
+ if (keys.length > 1 && distinct < keys.length) {
5853
6042
  return [
5854
- `Council distinctness policy "${profile.distinctness}" was not met: ${distinct} distinct target(s) served ${valid.length} valid vote(s).`
6043
+ `Council distinctness policy "${profile.distinctness}" was not met: ${distinct} distinct target(s) served ${keys.length} valid vote(s).`
5855
6044
  ];
5856
6045
  }
5857
6046
  return [];
@@ -5887,17 +6076,6 @@ async function mapConcurrent(items, concurrency, worker) {
5887
6076
  await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => run()));
5888
6077
  return results;
5889
6078
  }
5890
- function emptyCallResult(error) {
5891
- return {
5892
- text: "",
5893
- model: "",
5894
- provider: "",
5895
- tokens: { input: 0, output: 0, total: 0 },
5896
- durationMs: 0,
5897
- fromFallback: false,
5898
- error
5899
- };
5900
- }
5901
6079
  function optionalString(value) {
5902
6080
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
5903
6081
  }
@@ -5906,52 +6084,96 @@ function errorMessage(error) {
5906
6084
  }
5907
6085
 
5908
6086
  // src/execution/council-brain.ts
6087
+ var MAX_CUSTOM_PERSONA_ID_CHARS = 48;
6088
+ function customPersonaId(raw, index) {
6089
+ const slug = raw.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, MAX_CUSTOM_PERSONA_ID_CHARS).replace(/-+$/g, "");
6090
+ return slug ? `custom-${slug}` : `custom-${index + 1}`;
6091
+ }
6092
+ function resolvePersonaRegistry(voters) {
6093
+ let registry = DEFAULT_COUNCIL_PERSONA_REGISTRY;
6094
+ const personaIds = [];
6095
+ const byRawText = /* @__PURE__ */ new Map();
6096
+ voters.forEach((voter, index) => {
6097
+ const raw = voter.persona?.trim();
6098
+ if (!raw) {
6099
+ personaIds.push("executor");
6100
+ return;
6101
+ }
6102
+ if (registry.has(raw)) {
6103
+ personaIds.push(raw);
6104
+ return;
6105
+ }
6106
+ const existing = byRawText.get(raw);
6107
+ if (existing) {
6108
+ personaIds.push(existing);
6109
+ return;
6110
+ }
6111
+ let id = customPersonaId(raw, index);
6112
+ let suffix = 2;
6113
+ while (registry.has(id)) id = `${customPersonaId(raw, index)}-${suffix++}`;
6114
+ registry = registry.with({
6115
+ id,
6116
+ name: raw.length <= 40 ? raw : `${raw.slice(0, 39)}\u2026`,
6117
+ description: "Custom decision lens supplied by the host configuration.",
6118
+ instruction: raw
6119
+ });
6120
+ byRawText.set(raw, id);
6121
+ personaIds.push(id);
6122
+ });
6123
+ return { registry, personaIds };
6124
+ }
6125
+ function makeSeatCallerForVoter(target) {
6126
+ return {
6127
+ async call(input) {
6128
+ const startedAt = Date.now();
6129
+ try {
6130
+ const result = await completeBrainLlmDetailed(
6131
+ { provider: target.provider, model: input.model ?? target.model },
6132
+ {
6133
+ system: typeof input.system === "string" ? input.system : Array.isArray(input.system) ? input.system.map((b) => b.text).join("\n") : "",
6134
+ user: input.userPrompt ?? "",
6135
+ timeoutMs: input.timeoutMs ?? 15e3,
6136
+ maxTokens: input.maxTokens,
6137
+ // Forward the orchestrator's signal (overall budget + caller
6138
+ // cancellation) so an in-flight seat call is interrupted when
6139
+ // the council times out or is cancelled instead of running to
6140
+ // its per-call timeout. Previously this was dropped and a
6141
+ // cancelled council waited for every seat to finish.
6142
+ signal: input.signal
6143
+ }
6144
+ );
6145
+ const inputTokens = result.usage?.input ?? 0;
6146
+ const outputTokens = result.usage?.output ?? 0;
6147
+ return {
6148
+ text: result.text,
6149
+ model: target.model,
6150
+ provider: target.provider.id,
6151
+ tokens: {
6152
+ input: inputTokens,
6153
+ output: outputTokens,
6154
+ total: inputTokens + outputTokens
6155
+ },
6156
+ durationMs: Date.now() - startedAt,
6157
+ fromFallback: false
6158
+ };
6159
+ } catch (error) {
6160
+ return {
6161
+ text: "",
6162
+ model: target.model,
6163
+ provider: target.provider.id,
6164
+ tokens: { input: 0, output: 0, total: 0 },
6165
+ durationMs: 0,
6166
+ fromFallback: false,
6167
+ error: error instanceof Error ? error.message : String(error)
6168
+ };
6169
+ }
6170
+ }
6171
+ };
6172
+ }
5909
6173
  function createCouncilBrainArbiter(opts) {
5910
6174
  if (opts.voters.length === 0) {
5911
6175
  throw new Error("createCouncilBrainArbiter: at least one voter is required.");
5912
6176
  }
5913
- function makeSeatCallerForVoter(target) {
5914
- return {
5915
- async call(input) {
5916
- const startedAt = Date.now();
5917
- try {
5918
- const result = await completeBrainLlmDetailed(
5919
- { provider: target.provider, model: input.model ?? target.model },
5920
- {
5921
- system: typeof input.system === "string" ? input.system : Array.isArray(input.system) ? input.system.map((b) => b.text).join("\n") : "",
5922
- user: input.userPrompt ?? "",
5923
- timeoutMs: input.timeoutMs ?? 15e3,
5924
- maxTokens: input.maxTokens
5925
- }
5926
- );
5927
- const inputTokens = result.usage?.input ?? 0;
5928
- const outputTokens = result.usage?.output ?? 0;
5929
- return {
5930
- text: result.text,
5931
- model: target.model,
5932
- provider: target.provider.id,
5933
- tokens: {
5934
- input: inputTokens,
5935
- output: outputTokens,
5936
- total: inputTokens + outputTokens
5937
- },
5938
- durationMs: Date.now() - startedAt,
5939
- fromFallback: false
5940
- };
5941
- } catch (error) {
5942
- return {
5943
- text: "",
5944
- model: target.model,
5945
- provider: target.provider.id,
5946
- tokens: { input: 0, output: 0, total: 0 },
5947
- durationMs: 0,
5948
- fromFallback: false,
5949
- error: error instanceof Error ? error.message : String(error)
5950
- };
5951
- }
5952
- }
5953
- };
5954
- }
5955
6177
  const seatCaller = (seatIndex) => {
5956
6178
  const voter = opts.voters[seatIndex];
5957
6179
  if (!voter) {
@@ -5971,14 +6193,15 @@ function createCouncilBrainArbiter(opts) {
5971
6193
  }
5972
6194
  return makeSeatCallerForVoter(voter);
5973
6195
  };
6196
+ const { registry: personas, personaIds } = resolvePersonaRegistry(opts.voters);
5974
6197
  const seats = opts.voters.map((voter, i) => ({
5975
6198
  id: `voter-${i}`,
5976
6199
  label: voter.label ?? voter.model,
5977
- persona: voter.persona ?? "executor",
6200
+ persona: personaIds[i] ?? "executor",
5978
6201
  target: {
5979
6202
  providerId: voter.provider.id,
5980
6203
  model: voter.model,
5981
- role: voter.persona ?? "executor"
6204
+ role: personaIds[i] ?? "executor"
5982
6205
  },
5983
6206
  weight: voter.weight,
5984
6207
  veto: voter.veto
@@ -5988,18 +6211,31 @@ function createCouncilBrainArbiter(opts) {
5988
6211
  model: opts.judge.model,
5989
6212
  role: "judge"
5990
6213
  } : false;
6214
+ const perCallTimeoutMs = opts.decisionTimeoutMs ?? 15e3;
6215
+ const effectiveConcurrency = Math.max(
6216
+ 1,
6217
+ Math.min(opts.maxConcurrency ?? DEFAULT_COUNCIL_MAX_CONCURRENCY, seats.length)
6218
+ );
6219
+ const overallTimeoutMs = Math.max(
6220
+ DEFAULT_COUNCIL_OVERALL_TIMEOUT_MS,
6221
+ perCallTimeoutMs * (Math.ceil(seats.length / effectiveConcurrency) + 1)
6222
+ );
5991
6223
  const profile = {
5992
6224
  id: "brain-council-adapter",
5993
6225
  seats,
5994
6226
  judge: judgeTarget,
5995
6227
  quorumFraction: opts.quorumFraction ?? 0.5,
5996
6228
  approvalFraction: opts.approvalFraction ?? 0.5,
5997
- perCallTimeoutMs: opts.decisionTimeoutMs ?? 15e3,
6229
+ perCallTimeoutMs,
6230
+ overallTimeoutMs,
5998
6231
  ...opts.judgeMaxTokens !== void 0 ? { judgeMaxTokens: opts.judgeMaxTokens } : {},
5999
6232
  distinctness: opts.distinctness ?? "none"
6000
6233
  };
6001
6234
  const orchestrator = new CouncilOrchestrator({
6002
6235
  defaultProfile: "brain-council-adapter",
6236
+ // Carries the ad-hoc lenses registered above; without it the orchestrator
6237
+ // falls back to the built-in registry and rejects every custom persona.
6238
+ personas,
6003
6239
  // Previously never passed, so the panel was pinned at the orchestrator's
6004
6240
  // default of 3 concurrent seats no matter how many voters were configured.
6005
6241
  ...opts.maxConcurrency !== void 0 ? { maxConcurrency: opts.maxConcurrency } : {},
@@ -7820,7 +8056,9 @@ function safeProfileName(name) {
7820
8056
  function activeProfileName(globalRoot) {
7821
8057
  try {
7822
8058
  const parsed = JSON.parse(fs.readFileSync(path4.join(globalRoot, "config.json"), "utf8"));
7823
- return safeProfileName(typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0);
8059
+ return safeProfileName(
8060
+ typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0
8061
+ );
7824
8062
  } catch {
7825
8063
  return "default";
7826
8064
  }
@@ -7900,6 +8138,7 @@ function resolveWstackPaths(opts) {
7900
8138
  projectPlan: path4.join(projectDir, "plan.json"),
7901
8139
  projectAutophase: path4.join(projectDir, "autophase"),
7902
8140
  projectSddBoards: path4.join(projectDir, "sdd-boards"),
8141
+ projectRequirementIntakes: path4.join(projectDir, "requirement-intakes"),
7903
8142
  syncConfig: path4.join(profileDir, "sync.json"),
7904
8143
  configHistoryDir: path4.join(globalRoot, "config-history"),
7905
8144
  projectStatus: (projectHash2) => path4.join(globalRoot, "projects", projectHash2, "status.json")
@@ -15917,8 +16156,17 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
15917
16156
  durationMs: 0
15918
16157
  };
15919
16158
  this.completedResults.push(synthetic);
16159
+ this.trimCompletedResults();
15920
16160
  this.emit("task.completed", { task, result: synthetic });
15921
16161
  }
16162
+ trimCompletedResults() {
16163
+ if (this.completedResults.length > _DefaultMultiAgentCoordinator.MAX_COMPLETED_RESULTS) {
16164
+ this.completedResults.splice(
16165
+ 0,
16166
+ this.completedResults.length - _DefaultMultiAgentCoordinator.MAX_COMPLETED_RESULTS
16167
+ );
16168
+ }
16169
+ }
15922
16170
  async runDispatched(subagentId, task) {
15923
16171
  const subagent = this.subagents.get(subagentId);
15924
16172
  if (!subagent) return;
@@ -16052,12 +16300,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
16052
16300
  }
16053
16301
  recordCompletion(result) {
16054
16302
  this.completedResults.push(result);
16055
- if (this.completedResults.length > _DefaultMultiAgentCoordinator.MAX_COMPLETED_RESULTS) {
16056
- this.completedResults.splice(
16057
- 0,
16058
- this.completedResults.length - _DefaultMultiAgentCoordinator.MAX_COMPLETED_RESULTS
16059
- );
16060
- }
16303
+ this.trimCompletedResults();
16061
16304
  this.totalIterations += result.iterations;
16062
16305
  if (this.inFlight > 0) {
16063
16306
  this.inFlight--;
@@ -20363,6 +20606,7 @@ var OneShotOrchestrator = class {
20363
20606
  tokens: { input: 0, output: 0, total: 0 },
20364
20607
  durationMs: Math.round(performance.now() - startedAt),
20365
20608
  fromFallback: false,
20609
+ attempts: 0,
20366
20610
  error: "No provider or model could be resolved. Check your config."
20367
20611
  };
20368
20612
  }
@@ -20377,6 +20621,7 @@ var OneShotOrchestrator = class {
20377
20621
  tokens: { input: 0, output: 0, total: 0 },
20378
20622
  durationMs: Math.round(performance.now() - startedAt),
20379
20623
  fromFallback: false,
20624
+ attempts: 0,
20380
20625
  error: `Cannot build provider "${target.providerId}": ${err instanceof Error ? err.message : String(err)}`
20381
20626
  };
20382
20627
  }
@@ -20389,11 +20634,13 @@ var OneShotOrchestrator = class {
20389
20634
  let fromFallback = false;
20390
20635
  let lastError;
20391
20636
  let fallbackEligible = false;
20637
+ let attempts = 0;
20392
20638
  if (tracker && !tracker.isAvailable(target.providerId, target.model) || !evaluateModelCalendar(config.modelAvailabilitySchedule, target.providerId, target.model).allowed) {
20393
20639
  this.opts.logger?.debug(
20394
20640
  `one-shot: primary "${target.providerId}/${target.model}" is blocked \u2014 trying fallback`
20395
20641
  );
20396
20642
  } else {
20643
+ attempts += 1;
20397
20644
  const primaryAttempt = await this.tryCall(
20398
20645
  provider,
20399
20646
  request,
@@ -20408,10 +20655,17 @@ var OneShotOrchestrator = class {
20408
20655
  tracker?.recordSuccess(target.providerId, target.model);
20409
20656
  servingProviderId = provider.id;
20410
20657
  servingModel = target.model;
20411
- return this.buildResult(result, servingProviderId, servingModel, false, startedAt);
20658
+ return this.buildResult(result, servingProviderId, servingModel, false, startedAt, attempts);
20412
20659
  }
20413
20660
  if (!fallbackEligible || chain.length === 0) {
20414
- return this.buildErrorResult(lastError, target.providerId, target.model, false, startedAt);
20661
+ return this.buildErrorResult(
20662
+ lastError,
20663
+ target.providerId,
20664
+ target.model,
20665
+ false,
20666
+ startedAt,
20667
+ attempts
20668
+ );
20415
20669
  }
20416
20670
  }
20417
20671
  const estimatedTokens = estimateRequestTokens(request.messages, request.system, []).total;
@@ -20439,6 +20693,7 @@ var OneShotOrchestrator = class {
20439
20693
  }
20440
20694
  servingProviderId = fbProvider.id;
20441
20695
  servingModel = entry.model;
20696
+ attempts += 1;
20442
20697
  const attempt = await this.tryCall(
20443
20698
  fbProvider,
20444
20699
  this.buildRequest(input, entry.model),
@@ -20449,7 +20704,14 @@ var OneShotOrchestrator = class {
20449
20704
  if (attempt.response) {
20450
20705
  tracker?.recordSuccess(entry.providerId, entry.model);
20451
20706
  fromFallback = true;
20452
- return this.buildResult(attempt.response, servingProviderId, servingModel, true, startedAt);
20707
+ return this.buildResult(
20708
+ attempt.response,
20709
+ servingProviderId,
20710
+ servingModel,
20711
+ true,
20712
+ startedAt,
20713
+ attempts
20714
+ );
20453
20715
  }
20454
20716
  lastError = attempt.error;
20455
20717
  }
@@ -20458,7 +20720,8 @@ var OneShotOrchestrator = class {
20458
20720
  servingProviderId,
20459
20721
  servingModel,
20460
20722
  fromFallback,
20461
- startedAt
20723
+ startedAt,
20724
+ attempts
20462
20725
  );
20463
20726
  }
20464
20727
  // ── Private helpers ─────────────────────────────────────────────
@@ -20469,7 +20732,8 @@ var OneShotOrchestrator = class {
20469
20732
  resolveTarget(input, config) {
20470
20733
  if (input.role && this.opts.modelRouter) {
20471
20734
  const pick = this.opts.modelRouter.pickForTask(input.role, "");
20472
- if (pick) {
20735
+ const hasExplicitTarget = Boolean(input.providerId || input.model);
20736
+ if (pick && (pick.fromMatrix === true || !hasExplicitTarget)) {
20473
20737
  return { providerId: pick.provider, model: pick.model };
20474
20738
  }
20475
20739
  }
@@ -20581,7 +20845,7 @@ var OneShotOrchestrator = class {
20581
20845
  }
20582
20846
  }
20583
20847
  /** Build a success result from a provider Response. */
20584
- buildResult(response, servingProviderId, servingModel, fromFallback, startedAt) {
20848
+ buildResult(response, servingProviderId, servingModel, fromFallback, startedAt, attempts) {
20585
20849
  const textBlocks = response.content.filter(isTextBlock);
20586
20850
  const text = textBlocks.map((b) => b.text).join("\n").trim();
20587
20851
  return {
@@ -20595,11 +20859,12 @@ var OneShotOrchestrator = class {
20595
20859
  },
20596
20860
  durationMs: Math.round(performance.now() - startedAt),
20597
20861
  fromFallback,
20862
+ attempts,
20598
20863
  stopReason: response.stopReason
20599
20864
  };
20600
20865
  }
20601
20866
  /** Build a total-failure error result. */
20602
- buildErrorResult(error, servingProviderId, servingModel, fromFallback, startedAt) {
20867
+ buildErrorResult(error, servingProviderId, servingModel, fromFallback, startedAt, attempts) {
20603
20868
  return {
20604
20869
  text: "",
20605
20870
  model: servingModel,
@@ -20607,6 +20872,7 @@ var OneShotOrchestrator = class {
20607
20872
  tokens: { input: 0, output: 0, total: 0 },
20608
20873
  durationMs: Math.round(performance.now() - startedAt),
20609
20874
  fromFallback,
20875
+ attempts,
20610
20876
  error: error instanceof Error ? error.message : String(error ?? "Unknown error")
20611
20877
  };
20612
20878
  }
@@ -21142,6 +21408,7 @@ export {
21142
21408
  AutonomousRunner,
21143
21409
  BRAIN_EVALUATION_CASE_VERSION,
21144
21410
  BUILTIN_COUNCIL_PERSONAS,
21411
+ BUILTIN_COUNCIL_PERSONA_IDS,
21145
21412
  BUILTIN_COUNCIL_PROFILES,
21146
21413
  COUNCIL_JUDGE_PROMPT_PATH,
21147
21414
  COUNCIL_REFUSAL_OPTION_ID,