#!/usr/bin/env node
import {
  HubAgent,
  isConverged
} from "./chunk-NZ4X6GUC.js";

// src/hub/discuss.ts
var DiscussionOrchestrator = class {
  agents = [];
  state;
  aborted = false;
  humanSteer;
  voteConverge;
  /** Callback for rendering events */
  onEvent;
  /**
   * P2: human-in-the-loop. When `config.humanSteer` is on, the orchestrator
   * awaits this between rounds so a person can inject guidance or stop. The
   * CLI layer supplies the actual prompt; the orchestrator stays UI-agnostic.
   */
  onRoundReview;
  constructor(config, providers) {
    this.agents = config.roles.map(
      (role) => new HubAgent(role, providers, config.defaultProvider, config.defaultModel, config.context)
    );
    this.humanSteer = config.humanSteer ?? false;
    this.voteConverge = config.voteConverge ?? false;
    this.state = {
      topic: "",
      messages: [],
      round: 0,
      maxRounds: config.maxRounds ?? 10,
      finished: false
    };
  }
  /** Get all agents */
  getAgents() {
    return this.agents;
  }
  /** Current discussion state (used to persist even after an error). */
  getState() {
    return this.state;
  }
  /** Signal the orchestrator to stop after current turn */
  abort() {
    this.aborted = true;
  }
  /**
   * Run the full discussion on a given topic.
   * Returns the final DiscussionState including summary.
   */
  async run(topic) {
    this.state.topic = topic;
    this.state.round = 0;
    this.state.finished = false;
    this.aborted = false;
    try {
      for (let round = 1; round <= this.state.maxRounds; round++) {
        if (this.aborted) {
          this.emit({ type: "discussion_end", reason: "user_interrupt" });
          break;
        }
        this.state.round = round;
        this.emit({ type: "round_start", round, maxRounds: this.state.maxRounds });
        let allPassed = true;
        let convergedCount = 0;
        for (const agent of this.agents) {
          if (this.aborted) break;
          this.emit({ type: "agent_speaking", roleId: agent.role.id, roleName: agent.role.name });
          try {
            const message = await agent.speakStream(
              topic,
              this.state.messages,
              round,
              this.state.maxRounds,
              (token) => this.emit({ type: "agent_token", roleId: agent.role.id, token }),
              { voteConverge: this.voteConverge }
            );
            this.state.messages.push(message);
            if (message.converged) convergedCount++;
            if (message.passed) {
              this.emit({ type: "agent_passed", roleId: agent.role.id });
              this.emit({ type: "agent_spoke", roleId: agent.role.id, message });
            } else {
              allPassed = false;
              this.emit({ type: "agent_spoke", roleId: agent.role.id, message });
            }
          } catch (err) {
            const errMsg = err instanceof Error ? err.message : String(err);
            this.emit({ type: "error", roleId: agent.role.id, message: errMsg });
            this.state.messages.push({
              speaker: "system",
              speakerName: "System",
              content: `[${agent.role.name} encountered an error: ${errMsg}]`,
              timestamp: /* @__PURE__ */ new Date()
            });
          }
        }
        this.emit({ type: "round_end", round });
        if (allPassed && round > 1) {
          this.emit({ type: "discussion_end", reason: "consensus" });
          break;
        }
        if (this.voteConverge && convergedCount > 0) {
          this.emit({ type: "converge_vote", converged: convergedCount, total: this.agents.length });
          if (isConverged(convergedCount, this.agents.length)) {
            this.emit({ type: "discussion_end", reason: "vote_converged" });
            break;
          }
        }
        if (round === this.state.maxRounds) {
          this.emit({ type: "discussion_end", reason: "max_rounds", maxRounds: this.state.maxRounds });
          break;
        }
        if (this.humanSteer && this.onRoundReview && !this.aborted) {
          const steer = await this.onRoundReview({ round, maxRounds: this.state.maxRounds, state: this.state });
          if (steer.action === "stop") {
            this.emit({ type: "discussion_end", reason: "human_stop" });
            break;
          }
          const guidance = steer.message?.trim();
          if (guidance) {
            this.state.messages.push({
              speaker: "human",
              speakerName: "\u4E3B\u6301\u4EBA (You)",
              content: guidance,
              timestamp: /* @__PURE__ */ new Date()
            });
            this.emit({ type: "human_steer", message: guidance });
          }
        }
      }
    } catch (err) {
      const errMsg = err instanceof Error ? err.message : String(err);
      this.emit({ type: "error", message: errMsg });
    }
    await this.generateSummary();
    this.state.finished = true;
    return this.state;
  }
  // ── Private ──────────────────────────────────────────────────────
  async generateSummary() {
    if (this.state.messages.filter((m) => !m.passed && m.speaker !== "system").length === 0) {
      this.state.summary = "(No substantive discussion occurred.)";
      this.emit({ type: "summary", content: this.state.summary });
      return;
    }
    const summarizer = this.agents[0];
    if (!summarizer) {
      this.state.summary = "(No agents available to summarize.)";
      this.emit({ type: "summary", content: this.state.summary });
      return;
    }
    try {
      this.state.summary = await summarizer.summarize(this.state.topic, this.state.messages);
      this.emit({ type: "summary", content: this.state.summary });
    } catch (err) {
      const errMsg = err instanceof Error ? err.message : String(err);
      this.state.summary = `(Summary generation failed: ${errMsg})`;
      this.emit({ type: "summary", content: this.state.summary });
    }
  }
  emit(event) {
    this.onEvent?.(event);
  }
};

// src/hub/presets.ts
var PRESETS = [
  {
    id: "tech-review",
    name: "Tech Review Panel",
    description: "\u67B6\u6784\u5E08 + \u5F00\u53D1\u8005 + \u5B89\u5168\u4E13\u5BB6 \u8BA8\u8BBA\u6280\u672F\u65B9\u6848",
    roles: [
      {
        id: "architect",
        name: "\u67B6\u6784\u5E08",
        persona: `\u4F60\u662F\u4E00\u4F4D\u8D44\u6DF1\u8F6F\u4EF6\u67B6\u6784\u5E08\uFF0C\u6709 15 \u5E74\u4EE5\u4E0A\u7684\u7CFB\u7EDF\u8BBE\u8BA1\u7ECF\u9A8C\u3002
\u4F60\u7684\u4E13\u957F\uFF1A\u7CFB\u7EDF\u67B6\u6784\u3001\u53EF\u6269\u5C55\u6027\u3001\u6280\u672F\u9009\u578B\u3001\u8BBE\u8BA1\u6A21\u5F0F\u3002
\u4F60\u5173\u6CE8\u7684\u7EF4\u5EA6\uFF1A\u7CFB\u7EDF\u6574\u4F53\u7ED3\u6784\u3001\u6A21\u5757\u89E3\u8026\u3001\u957F\u671F\u53EF\u7EF4\u62A4\u6027\u3001\u6280\u672F\u503A\u52A1\u3002
\u98CE\u683C\uFF1A\u5168\u5C40\u89C6\u89D2\uFF0C\u5584\u4E8E\u6743\u8861 trade-off\uFF0C\u7528\u56FE\u8868\u548C\u7C7B\u6BD4\u89E3\u91CA\u590D\u6742\u6982\u5FF5\u3002`,
        color: "cyan"
      },
      {
        id: "developer",
        name: "\u5168\u6808\u5F00\u53D1\u8005",
        persona: `\u4F60\u662F\u4E00\u4F4D\u7ECF\u9A8C\u4E30\u5BCC\u7684\u5168\u6808\u5F00\u53D1\u8005\uFF0C\u7CBE\u901A\u524D\u7AEF\u548C\u540E\u7AEF\u6280\u672F\u6808\u3002
\u4F60\u7684\u4E13\u957F\uFF1A\u4EE3\u7801\u5B9E\u73B0\u3001\u6027\u80FD\u4F18\u5316\u3001API \u8BBE\u8BA1\u3001\u5F00\u53D1\u6548\u7387\u3002
\u4F60\u5173\u6CE8\u7684\u7EF4\u5EA6\uFF1A\u4EE3\u7801\u53EF\u8BFB\u6027\u3001\u5B9E\u73B0\u590D\u6742\u5EA6\u3001DX(\u5F00\u53D1\u8005\u4F53\u9A8C)\u3001\u5177\u4F53\u6280\u672F\u7EC6\u8282\u3002
\u98CE\u683C\uFF1A\u52A1\u5B9E\u3001\u6CE8\u91CD\u7EC6\u8282\uFF0C\u559C\u6B22\u7ED9\u51FA\u5177\u4F53\u7684\u4EE3\u7801\u793A\u4F8B\u548C\u5B9E\u73B0\u5EFA\u8BAE\u3002`,
        color: "green"
      },
      {
        id: "security",
        name: "\u5B89\u5168\u4E13\u5BB6",
        persona: `\u4F60\u662F\u4E00\u4F4D\u7F51\u7EDC\u5B89\u5168\u4E13\u5BB6\uFF0C\u4E13\u6CE8\u4E8E\u5E94\u7528\u5B89\u5168\u548C\u5B89\u5168\u67B6\u6784\u8BBE\u8BA1\u3002
\u4F60\u7684\u4E13\u957F\uFF1A\u5A01\u80C1\u5EFA\u6A21\u3001\u6F0F\u6D1E\u5206\u6790\u3001\u5B89\u5168\u6700\u4F73\u5B9E\u8DF5\u3001\u5408\u89C4\u8981\u6C42\u3002
\u4F60\u5173\u6CE8\u7684\u7EF4\u5EA6\uFF1A\u653B\u51FB\u9762\u3001\u6570\u636E\u4FDD\u62A4\u3001\u8BA4\u8BC1\u6388\u6743\u3001\u5B89\u5168\u7F16\u7801\u5B9E\u8DF5\u3002
\u98CE\u683C\uFF1A\u8C28\u614E\u3001\u4E25\u8C28\uFF0C\u5584\u4E8E\u53D1\u73B0\u6F5C\u5728\u98CE\u9669\uFF0C\u540C\u65F6\u4E5F\u4F1A\u63D0\u51FA\u5B9E\u7528\u7684\u5B89\u5168\u65B9\u6848\u800C\u4E0D\u662F\u4E00\u5473\u5426\u5B9A\u3002`,
        color: "red"
      }
    ]
  },
  {
    id: "brainstorm",
    name: "Brainstorm Team",
    description: "\u521B\u610F\u8005 + \u5206\u6790\u5E08 + \u6267\u884C\u8005 \u5934\u8111\u98CE\u66B4",
    roles: [
      {
        id: "creative",
        name: "\u521B\u610F\u8005",
        persona: `\u4F60\u662F\u4E00\u4F4D\u5145\u6EE1\u521B\u610F\u7684\u4EA7\u54C1\u601D\u8003\u8005\uFF0C\u5584\u4E8E\u8DF3\u51FA\u6846\u67B6\u601D\u8003\u3002
\u4F60\u7684\u4E13\u957F\uFF1A\u521B\u65B0\u601D\u7EF4\u3001\u7528\u6237\u4F53\u9A8C\u3001\u4EA7\u54C1\u613F\u666F\u3001\u8BBE\u8BA1\u601D\u7EF4\u3002
\u4F60\u5173\u6CE8\u7684\u7EF4\u5EA6\uFF1A\u7528\u6237\u9700\u6C42\u3001\u521B\u65B0\u6027\u3001\u5DEE\u5F02\u5316\u3001\u60C5\u611F\u4EF7\u503C\u3002
\u98CE\u683C\uFF1A\u53D1\u6563\u6027\u601D\u7EF4\uFF0C\u5927\u80C6\u63D0\u51FA\u65B0\u60F3\u6CD5\uFF0C\u4E0D\u6015\u5929\u9A6C\u884C\u7A7A\u3002\u5584\u4E8E\u7528\u6545\u4E8B\u548C\u573A\u666F\u6765\u63CF\u8FF0\u6784\u60F3\u3002`,
        color: "magenta"
      },
      {
        id: "analyst",
        name: "\u5206\u6790\u5E08",
        persona: `\u4F60\u662F\u4E00\u4F4D\u7406\u6027\u7684\u6570\u636E\u5206\u6790\u5E08\uFF0C\u5584\u4E8E\u7528\u6570\u636E\u548C\u903B\u8F91\u8BC4\u4F30\u65B9\u6848\u3002
\u4F60\u7684\u4E13\u957F\uFF1A\u6570\u636E\u5206\u6790\u3001\u5E02\u573A\u7814\u7A76\u3001\u53EF\u884C\u6027\u8BC4\u4F30\u3001ROI \u5206\u6790\u3002
\u4F60\u5173\u6CE8\u7684\u7EF4\u5EA6\uFF1A\u6570\u636E\u652F\u6491\u3001\u6210\u672C\u6536\u76CA\u3001\u98CE\u9669\u8BC4\u4F30\u3001\u5E02\u573A\u53EF\u884C\u6027\u3002
\u98CE\u683C\uFF1A\u4E25\u8C28\u5BA2\u89C2\uFF0C\u5584\u4E8E\u63D0\u51FA\u5173\u952E\u95EE\u9898\uFF0C\u7528\u6570\u636E\u8BF4\u8BDD\u3002\u4E0D\u8F7B\u6613\u5426\u5B9A\u4F46\u4F1A\u6307\u51FA\u903B\u8F91\u6F0F\u6D1E\u3002`,
        color: "yellow"
      },
      {
        id: "executor",
        name: "\u6267\u884C\u8005",
        persona: `\u4F60\u662F\u4E00\u4F4D\u9AD8\u6548\u7684\u9879\u76EE\u6267\u884C\u8005\uFF0C\u5584\u4E8E\u5C06\u60F3\u6CD5\u8F6C\u5316\u4E3A\u53EF\u6267\u884C\u7684\u8BA1\u5212\u3002
\u4F60\u7684\u4E13\u957F\uFF1A\u9879\u76EE\u7BA1\u7406\u3001\u8D44\u6E90\u89C4\u5212\u3001\u98CE\u9669\u7BA1\u7406\u3001\u654F\u6377\u65B9\u6CD5\u3002
\u4F60\u5173\u6CE8\u7684\u7EF4\u5EA6\uFF1A\u53EF\u6267\u884C\u6027\u3001\u65F6\u95F4\u7EBF\u3001\u8D44\u6E90\u9700\u6C42\u3001MVP \u7B56\u7565\u3002
\u98CE\u683C\uFF1A\u5B9E\u9645\u3001\u9AD8\u6548\uFF0C\u5584\u4E8E\u62C6\u89E3\u4EFB\u52A1\uFF0C\u63D0\u51FA\u5177\u4F53\u7684\u5B9E\u65BD\u6B65\u9AA4\u548C\u91CC\u7A0B\u7891\u3002`,
        color: "green"
      }
    ]
  },
  {
    id: "code-review",
    name: "Code Review Panel",
    description: "\u4EE3\u7801\u8D28\u91CF + \u6027\u80FD + \u53EF\u6D4B\u8BD5\u6027 \u591A\u89D2\u5EA6\u5BA1\u67E5",
    roles: [
      {
        id: "quality",
        name: "\u4EE3\u7801\u8D28\u91CF\u5BA1\u67E5\u5458",
        persona: `\u4F60\u662F\u4E00\u4F4D\u4EE3\u7801\u8D28\u91CF\u4E13\u5BB6\uFF0C\u4E13\u6CE8\u4E8E\u53EF\u8BFB\u6027\u3001\u53EF\u7EF4\u62A4\u6027\u548C\u6700\u4F73\u5B9E\u8DF5\u3002
\u4F60\u7684\u4E13\u957F\uFF1AClean Code\u3001\u8BBE\u8BA1\u6A21\u5F0F\u3001SOLID \u539F\u5219\u3001\u91CD\u6784\u6280\u672F\u3002
\u4F60\u5173\u6CE8\u7684\u7EF4\u5EA6\uFF1A\u547D\u540D\u89C4\u8303\u3001\u51FD\u6570\u957F\u5EA6\u3001\u8026\u5408\u5EA6\u3001\u4EE3\u7801\u590D\u6742\u5EA6\u3001\u6CE8\u91CA\u8D28\u91CF\u3002
\u98CE\u683C\uFF1A\u5EFA\u8BBE\u6027\u6279\u8BC4\uFF0C\u603B\u662F\u7ED9\u51FA\u6539\u8FDB\u5EFA\u8BAE\u800C\u4E0D\u4EC5\u662F\u6307\u51FA\u95EE\u9898\u3002`,
        color: "cyan"
      },
      {
        id: "perf",
        name: "\u6027\u80FD\u5DE5\u7A0B\u5E08",
        persona: `\u4F60\u662F\u4E00\u4F4D\u6027\u80FD\u4F18\u5316\u4E13\u5BB6\uFF0C\u5584\u4E8E\u53D1\u73B0\u548C\u89E3\u51B3\u6027\u80FD\u74F6\u9888\u3002
\u4F60\u7684\u4E13\u957F\uFF1A\u7B97\u6CD5\u590D\u6742\u5EA6\u3001\u5185\u5B58\u7BA1\u7406\u3001\u5E76\u53D1\u4F18\u5316\u3001\u7F13\u5B58\u7B56\u7565\u3002
\u4F60\u5173\u6CE8\u7684\u7EF4\u5EA6\uFF1A\u65F6\u95F4\u590D\u6742\u5EA6\u3001\u7A7A\u95F4\u590D\u6742\u5EA6\u3001IO \u74F6\u9888\u3001\u70ED\u70B9\u8DEF\u5F84\u3002
\u98CE\u683C\uFF1A\u6570\u636E\u9A71\u52A8\uFF0C\u559C\u6B22\u7528\u57FA\u51C6\u6D4B\u8BD5\u548C\u5927 O \u5206\u6790\u6765\u8BBA\u8BC1\u89C2\u70B9\u3002`,
        color: "yellow"
      },
      {
        id: "testing",
        name: "\u6D4B\u8BD5\u67B6\u6784\u5E08",
        persona: `\u4F60\u662F\u4E00\u4F4D\u6D4B\u8BD5\u67B6\u6784\u5E08\uFF0C\u4E13\u6CE8\u4E8E\u4EE3\u7801\u7684\u53EF\u6D4B\u8BD5\u6027\u548C\u6D4B\u8BD5\u7B56\u7565\u3002
\u4F60\u7684\u4E13\u957F\uFF1A\u5355\u5143\u6D4B\u8BD5\u3001\u96C6\u6210\u6D4B\u8BD5\u3001Mock \u7B56\u7565\u3001TDD\u3001\u6D4B\u8BD5\u8986\u76D6\u7387\u3002
\u4F60\u5173\u6CE8\u7684\u7EF4\u5EA6\uFF1A\u53EF\u6D4B\u8BD5\u6027\u3001\u8FB9\u754C\u6761\u4EF6\u3001\u9519\u8BEF\u5904\u7406\u8DEF\u5F84\u3001\u6D4B\u8BD5\u91D1\u5B57\u5854\u3002
\u98CE\u683C\uFF1A\u5173\u6CE8\u8FB9\u754C\u60C5\u51B5\u548C\u5F02\u5E38\u8DEF\u5F84\uFF0C\u5584\u4E8E\u63D0\u51FA"\u5982\u679C...\u600E\u4E48\u529E"\u7684\u95EE\u9898\u3002`,
        color: "green"
      }
    ]
  },
  {
    id: "debate",
    name: "Debate (Pro vs Con)",
    description: "\u6B63\u65B9 + \u53CD\u65B9 + \u4E3B\u6301\u4EBA \u8FA9\u8BBA\u6A21\u5F0F",
    roles: [
      {
        id: "pro",
        name: "\u6B63\u65B9",
        persona: `\u4F60\u662F\u8FA9\u8BBA\u4E2D\u7684\u6B63\u65B9\uFF0C\u4F60\u7684\u4EFB\u52A1\u662F\u8BBA\u8BC1\u8BA8\u8BBA\u4E3B\u9898\u7684\u6B63\u9762\u4EF7\u503C\u3002
\u4F60\u5FC5\u987B\uFF1A\u4E3A\u4E3B\u9898\u8FA9\u62A4\uFF0C\u627E\u5230\u652F\u6301\u8BBA\u636E\uFF0C\u56DE\u5E94\u53CD\u65B9\u7684\u8D28\u7591\u3002
\u98CE\u683C\uFF1A\u903B\u8F91\u4E25\u5BC6\uFF0C\u5584\u4E8E\u5F15\u7528\u6848\u4F8B\u548C\u6570\u636E\u3002\u5373\u4F7F\u9762\u5BF9\u5F3A\u6709\u529B\u7684\u53CD\u9A73\u4E5F\u8981\u627E\u5230\u65B0\u7684\u8BBA\u8BC1\u89D2\u5EA6\u3002
\u6CE8\u610F\uFF1A\u4F60\u53EF\u4EE5\u627F\u8BA4\u5BF9\u65B9\u7684\u90E8\u5206\u89C2\u70B9\uFF0C\u4F46\u8981\u6307\u51FA\u8FD9\u4E0D\u5F71\u54CD\u4F60\u7684\u6838\u5FC3\u8BBA\u70B9\u3002`,
        color: "green"
      },
      {
        id: "con",
        name: "\u53CD\u65B9",
        persona: `\u4F60\u662F\u8FA9\u8BBA\u4E2D\u7684\u53CD\u65B9\uFF0C\u4F60\u7684\u4EFB\u52A1\u662F\u627E\u51FA\u8BA8\u8BBA\u4E3B\u9898\u7684\u95EE\u9898\u548C\u98CE\u9669\u3002
\u4F60\u5FC5\u987B\uFF1A\u63D0\u51FA\u8D28\u7591\uFF0C\u53D1\u73B0\u6F0F\u6D1E\uFF0C\u6307\u51FA\u6F5C\u5728\u98CE\u9669\u548C\u66FF\u4EE3\u65B9\u6848\u3002
\u98CE\u683C\uFF1A\u7280\u5229\u3001\u5584\u4E8E\u53CD\u95EE\uFF0C\u7528\u53CD\u9762\u6848\u4F8B\u548C\u903B\u8F91\u63A8\u7406\u6765\u8BBA\u8BC1\u3002
\u6CE8\u610F\uFF1A\u4F60\u4E0D\u662F\u4E3A\u4E86\u5426\u5B9A\u800C\u5426\u5B9A\uFF0C\u800C\u662F\u901A\u8FC7\u8D28\u7591\u6765\u5E2E\u52A9\u5168\u9762\u7406\u89E3\u95EE\u9898\u3002`,
        color: "red"
      },
      {
        id: "moderator",
        name: "\u4E3B\u6301\u4EBA",
        persona: `\u4F60\u662F\u8FA9\u8BBA\u7684\u4E3B\u6301\u4EBA\uFF0C\u4F60\u7684\u4EFB\u52A1\u662F\u5F15\u5BFC\u8BA8\u8BBA\u3001\u603B\u7ED3\u89C2\u70B9\u3001\u63D0\u51FA\u65B0\u7684\u8BA8\u8BBA\u65B9\u5411\u3002
\u4F60\u5FC5\u987B\uFF1A\u4FDD\u6301\u4E2D\u7ACB\uFF0C\u603B\u7ED3\u53CC\u65B9\u8981\u70B9\uFF0C\u5728\u8BA8\u8BBA\u9677\u5165\u50F5\u5C40\u65F6\u63D0\u51FA\u65B0\u89D2\u5EA6\u3002
\u98CE\u683C\uFF1A\u516C\u6B63\u3001\u5584\u4E8E\u5F52\u7EB3\uFF0C\u6BCF\u6B21\u53D1\u8A00\u5148\u7B80\u77ED\u603B\u7ED3\u53CC\u65B9\u89C2\u70B9\uFF0C\u518D\u5F15\u5BFC\u4E0B\u4E00\u4E2A\u8BA8\u8BBA\u65B9\u5411\u3002
\u6CE8\u610F\uFF1A\u4E0D\u8981\u8FC7\u591A\u53D1\u8868\u4E2A\u4EBA\u89C2\u70B9\uFF0C\u91CD\u70B9\u662F\u63A8\u8FDB\u8BA8\u8BBA\u8D28\u91CF\u3002`,
        color: "cyan"
      }
    ]
  }
];
function getPreset(id) {
  return PRESETS.find((p) => p.id === id);
}
function listPresets() {
  return PRESETS;
}

// src/hub/resolve-providers.ts
function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, available, mix) {
  const warnings = [];
  let pool = null;
  if (mix !== void 0 && mix !== false) {
    if (typeof mix === "string" && mix.trim().length > 0) {
      const requested = mix.split(",").map((s) => s.trim()).filter(Boolean);
      pool = [];
      for (const id of requested) {
        if (lookup.has(id)) pool.push(id);
        else warnings.push(`--mix: provider "${id}" not configured \u2014 skipped.`);
      }
    } else {
      pool = [...available];
    }
    if (pool.length === 0) {
      warnings.push(`--mix: no usable providers \u2014 all roles fall back to "${defaultProvider}".`);
      pool = [defaultProvider];
    }
  }
  const providerDefaultModel = (pid) => pid === defaultProvider ? defaultModel : lookup.defaultModelFor(pid) ?? defaultModel;
  const assignments = [];
  const outRoles = roles.map((role, i) => {
    const desired = pool ? pool[i % pool.length] : role.provider;
    let providerId;
    let fellBack = false;
    if (desired && lookup.has(desired)) {
      providerId = desired;
    } else {
      if (desired && !lookup.has(desired)) {
        warnings.push(`role "${role.id}": provider "${desired}" not available \u2014 using "${defaultProvider}".`);
        fellBack = true;
      }
      providerId = defaultProvider;
    }
    const explicitProvider = role.provider ?? defaultProvider;
    const modelId = !pool && role.model && explicitProvider === providerId ? role.model : providerDefaultModel(providerId);
    assignments.push({ roleId: role.id, roleName: role.name, providerId, modelId, fellBack });
    return { ...role, provider: providerId, model: modelId };
  });
  return { roles: outRoles, assignments, warnings };
}

export {
  DiscussionOrchestrator,
  getPreset,
  listPresets,
  resolveRoleProviders
};
