#!/usr/bin/env node

// src/hub/convergence.ts
function convergenceThreshold(total) {
  if (total <= 0) return Infinity;
  return Math.ceil(total * 2 / 3);
}
function isConverged(convergedCount, total) {
  if (convergedCount <= 0) return false;
  return convergedCount >= convergenceThreshold(total);
}
var CONVERGED_MARKER = /\[CONVERGED\]/i;
function hasConvergedMarker(content) {
  return CONVERGED_MARKER.test(content);
}
function stripConvergedMarker(content) {
  return content.replace(/\s*\[CONVERGED\]\s*/gi, " ").trim();
}

// src/hub/agent.ts
var PASS_MARKER = "[PASS]";
function parseTurn(raw) {
  const trimmed = raw.trim();
  const upper = trimmed.toUpperCase();
  if (upper.startsWith(PASS_MARKER) || upper === PASS_MARKER) {
    return { content: "", passed: true, converged: false };
  }
  const converged = hasConvergedMarker(trimmed);
  return { content: converged ? stripConvergedMarker(trimmed) : trimmed, passed: false, converged };
}
var HubAgent = class {
  role;
  providers;
  defaultProvider;
  defaultModel;
  /** External context documents injected into system prompt */
  context;
  constructor(role, providers, defaultProvider, defaultModel, context) {
    this.role = role;
    this.providers = providers;
    this.defaultProvider = defaultProvider;
    this.defaultModel = defaultModel;
    this.context = context;
  }
  get providerId() {
    return this.role.provider ?? this.defaultProvider;
  }
  get modelId() {
    return this.role.model ?? this.defaultModel;
  }
  /**
   * Generate this agent's response given the full discussion history.
   *
   * Returns a DiscussionMessage, with `passed: true` if the agent outputs [PASS].
   */
  async speak(topic, history, round, maxRounds, opts) {
    const provider = this.providers.get(this.providerId);
    if (!provider) {
      throw new Error(`Provider "${this.providerId}" not available for agent "${this.role.id}"`);
    }
    const systemPrompt = this.buildSystemPrompt(topic, round, maxRounds, opts?.voteConverge);
    const messages = this.buildMessages(history);
    const response = await provider.chat({
      messages,
      model: this.modelId,
      systemPrompt,
      stream: false,
      temperature: 0.7,
      maxTokens: 4096
    });
    const { content, passed, converged } = parseTurn(response.content);
    return {
      speaker: this.role.id,
      speakerName: this.role.name,
      content,
      timestamp: /* @__PURE__ */ new Date(),
      passed,
      converged
    };
  }
  /**
   * Streaming version of speak() — yields tokens as they arrive.
   * Falls back to non-streaming speak() if the provider doesn't support chatStream.
   */
  async speakStream(topic, history, round, maxRounds, onToken, opts) {
    const provider = this.providers.get(this.providerId);
    if (!provider) {
      throw new Error(`Provider "${this.providerId}" not available for agent "${this.role.id}"`);
    }
    if (!provider.chatStream) {
      return this.speak(topic, history, round, maxRounds, opts);
    }
    const systemPrompt = this.buildSystemPrompt(topic, round, maxRounds, opts?.voteConverge);
    const messages = this.buildMessages(history);
    let raw = "";
    const stream = provider.chatStream({
      messages,
      model: this.modelId,
      systemPrompt,
      stream: true,
      temperature: 0.7,
      maxTokens: 4096
    });
    for await (const chunk of stream) {
      if (chunk.delta) {
        raw += chunk.delta;
        onToken?.(chunk.delta);
      }
    }
    const { content, passed, converged } = parseTurn(raw);
    return {
      speaker: this.role.id,
      speakerName: this.role.name,
      content,
      timestamp: /* @__PURE__ */ new Date(),
      passed,
      converged
    };
  }
  /**
   * Generate a summary of the entire discussion from this agent's perspective.
   */
  async summarize(topic, history) {
    const provider = this.providers.get(this.providerId);
    if (!provider) {
      throw new Error(`Provider "${this.providerId}" not available`);
    }
    const messages = [
      {
        role: "user",
        content: this.buildSummaryPrompt(topic, history),
        timestamp: /* @__PURE__ */ new Date()
      }
    ];
    const response = await provider.chat({
      messages,
      model: this.modelId,
      stream: false,
      temperature: 0.3,
      maxTokens: 4096
    });
    return response.content.trim();
  }
  // ── Private ──────────────────────────────────────────────────────
  buildSystemPrompt(topic, round, maxRounds, voteConverge) {
    const contextSection = this.context ? `

## Reference Documents
${this.context}` : "";
    const convergeRule = voteConverge ? `
- If you believe the group has reached a sufficient conclusion and further rounds would add little, append the marker [CONVERGED] at the very end of your message (you may still make your substantive point above it). When a 2/3 majority converges, the discussion ends.` : "";
    return `# Multi-Agent Discussion \u2014 Role: ${this.role.name}

${this.role.persona}

## Discussion Rules
- You are participating in a multi-agent discussion about the topic below.
- You can see what other participants have said. Build on their ideas, challenge them, or add your own perspective.
- Stay in character as ${this.role.name}. Respond from your role's expertise and viewpoint.
- Keep responses concise and focused (2-6 paragraphs). Do not repeat what others have already said.
- If you have nothing meaningful to add (others have covered your points), respond with exactly: [PASS]${convergeRule}
- This is round ${round} of ${maxRounds}. ${round >= maxRounds - 1 ? "This is one of the final rounds \u2014 try to converge on conclusions." : ""}
- Use the language that the topic is written in (if the topic is in Chinese, respond in Chinese).

## Topic
${topic}${contextSection}`;
  }
  buildMessages(history) {
    const messages = [];
    for (const msg of history) {
      if (msg.passed) continue;
      if (msg.speaker === this.role.id) {
        messages.push({
          role: "assistant",
          content: msg.content,
          timestamp: msg.timestamp
        });
      } else {
        const prefix = `[${msg.speakerName} (${msg.speaker})]:`;
        messages.push({
          role: "user",
          content: `${prefix}
${msg.content}`,
          timestamp: msg.timestamp
        });
      }
    }
    if (messages.length === 0) {
      messages.push({
        role: "user",
        content: "Please share your initial thoughts on the topic described in the system prompt. You are the first to speak.",
        timestamp: /* @__PURE__ */ new Date()
      });
    } else {
      messages.push({
        role: "user",
        content: "It is now your turn to respond. Consider what the other participants have said and share your perspective. If you have nothing to add, respond with [PASS].",
        timestamp: /* @__PURE__ */ new Date()
      });
    }
    return messages;
  }
  buildSummaryPrompt(topic, history) {
    const transcript = history.filter((m) => !m.passed).map((m) => `**${m.speakerName}** (${m.speaker}):
${m.content}`).join("\n\n---\n\n");
    return `Please synthesize the following multi-agent discussion into a clear, structured summary.

## Discussion Topic
${topic}

## Full Transcript
${transcript}

## Instructions
Produce a structured, decision-oriented synthesis with these exact sections:

1. **Decision / Recommendation** \u2014 the single clearest recommendation the discussion points to. If the group genuinely did not converge, say so and give the leading option plus what would settle it.
2. **Key Points of Agreement** \u2014 what participants agreed on.
3. **Points of Debate** \u2014 where they disagreed, with the competing perspectives.
4. **Action Items** \u2014 concrete next steps as a checklist. For each, suggest which role/expertise should own it, e.g. "- [ ] (\u67B6\u6784\u5E08) \u8BC4\u4F30\u670D\u52A1\u62C6\u5206\u8FB9\u754C".
5. **Risks & Open Questions** \u2014 unresolved issues and what to watch.

Keep it tight (within ~600 words). Use the same language as the discussion.`;
  }
};

export {
  isConverged,
  HubAgent
};
