#!/usr/bin/env node
import { createRequire as __cr } from 'node:module'; if (typeof globalThis.require === 'undefined') { globalThis.require = __cr(import.meta.url); }
import {
  formatMcpLifecycleEvent,
  formatMcpSlowToast
} from "./chunk-GNS7BAT2.js";
import {
  buildTransportFromSpec,
  preflightStdioSpec
} from "./chunk-EQATK2L2.js";
import {
  bridgeMcpTools
} from "./chunk-MQWO32ZD.js";
import {
  McpClient,
  inspectMcpServer
} from "./chunk-HIYTRCSW.js";
import {
  wrapper_default
} from "./chunk-FEZK652I.js";
import {
  loadDotenv
} from "./chunk-2UQP6H6T.js";
import {
  t
} from "./chunk-YEF7C4XI.js";
import {
  decideQQAccess,
  describeQQAccess,
  loadQQConfig,
  normalizeMcpConfig,
  normalizeQQAllowlist,
  normalizeQQOpenId,
  overlayMatchedSpec,
  parseMcpSpec,
  readConfig,
  redactQQOpenId,
  specToRaw
} from "./chunk-WMTMMSXU.js";

// src/qq/channel.ts
import { mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
import { homedir } from "os";
import { dirname, join } from "path";

// src/qq/bot.ts
import { EventEmitter } from "events";
var TOKEN_URL = "https://bots.qq.com/app/getAppAccessToken";
var BASE_URL = "https://api.sgroup.qq.com";
var SANDBOX_URL = "https://sandbox.api.sgroup.qq.com";
var INTENT_C2C_GROUP = 1 << 25;
var MIN_HEARTBEAT_INTERVAL_MS = 5e3;
var MAX_HEARTBEAT_INTERVAL_MS = 6e4;
var ALLOWED_GATEWAY_HOSTS = ["api.sgroup.qq.com", "sandbox.api.sgroup.qq.com", "qq.com"];
var QQBot = class extends EventEmitter {
  config;
  token = "";
  tokenExpiresAt = 0;
  ws = null;
  heartbeatTimer = null;
  seq = 0;
  sessionId = "";
  closed = false;
  readyReceived = false;
  constructor(config) {
    super();
    this.config = config;
  }
  get baseUrl() {
    return this.config.sandbox ? SANDBOX_URL : BASE_URL;
  }
  sanitizeHeartbeatInterval(interval) {
    if (typeof interval !== "number" || !Number.isFinite(interval)) {
      return null;
    }
    if (interval < MIN_HEARTBEAT_INTERVAL_MS) {
      return MIN_HEARTBEAT_INTERVAL_MS;
    }
    if (interval > MAX_HEARTBEAT_INTERVAL_MS) {
      return MAX_HEARTBEAT_INTERVAL_MS;
    }
    return Math.trunc(interval);
  }
  validateGatewayUrl(rawUrl) {
    const url = new URL(rawUrl);
    const trustedHost = ALLOWED_GATEWAY_HOSTS.some(
      (host) => url.hostname === host || url.hostname.endsWith(`.${host}`)
    );
    if (url.protocol !== "wss:" || !trustedHost || url.username || url.password || url.search || url.hash) {
      throw new Error(`Unexpected QQ gateway URL: ${rawUrl}`);
    }
    return url.toString();
  }
  async ensureToken() {
    if (this.token && Date.now() < this.tokenExpiresAt - 6e4) {
      return this.token;
    }
    const res = await fetch(TOKEN_URL, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        appId: this.config.appid,
        clientSecret: this.config.secret
      })
    });
    if (!res.ok) {
      const text = await res.text();
      throw new Error(`Failed to get access token (${res.status}): ${text}`);
    }
    const data = await res.json();
    this.token = data.access_token;
    this.tokenExpiresAt = Date.now() + data.expires_in * 1e3;
    return this.token;
  }
  async getGateway() {
    const token = await this.ensureToken();
    const res = await fetch(`${this.baseUrl}/gateway`, {
      headers: { Authorization: `QQBot ${token}` }
    });
    if (!res.ok) {
      const text = await res.text();
      throw new Error(`Failed to get gateway (${res.status}): ${text}`);
    }
    const data = await res.json();
    return this.validateGatewayUrl(data.url);
  }
  sendOp(op, data) {
    if (!this.ws) return;
    this.ws.send(JSON.stringify({ op, d: data ?? {} }));
  }
  async handlePayload(payload) {
    switch (payload.op) {
      case 10: {
        const d = payload.d;
        this.sendOp(2, {
          token: `QQBot ${await this.ensureToken()}`,
          intents: INTENT_C2C_GROUP,
          shard: [0, 1]
        });
        const heartbeatInterval = this.sanitizeHeartbeatInterval(d?.heartbeat_interval);
        if (heartbeatInterval) {
          this.heartbeatTimer = setInterval(() => {
            this.sendOp(1, this.seq || null);
          }, heartbeatInterval);
        }
        break;
      }
      case 0: {
        if (payload.s) this.seq = payload.s;
        if (payload.t === "READY") {
          const d = payload.d;
          this.sessionId = d.session_id;
          this.readyReceived = true;
          this.emit("online");
        } else if (payload.t === "C2C_MESSAGE_CREATE") {
          this.emit("message.private", payload.d);
        } else if (payload.t === "GROUP_AT_MESSAGE_CREATE") {
          this.emit("message.group", payload.d);
        }
        break;
      }
      case 7: {
        this.reconnect();
        break;
      }
      case 9: {
        this.sessionId = "";
        this.sendOp(2, {
          token: `QQBot ${await this.ensureToken()}`,
          intents: INTENT_C2C_GROUP,
          shard: [0, 1]
        });
        break;
      }
    }
  }
  async reconnect() {
    this.cleanup();
    await this.connect();
  }
  cleanup() {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer);
      this.heartbeatTimer = null;
    }
    if (this.ws) {
      this.ws.removeAllListeners();
      this.ws.close();
      this.ws = null;
    }
  }
  async connect() {
    const gatewayUrl = await this.getGateway();
    const token = await this.ensureToken();
    this.ws = new wrapper_default(gatewayUrl, {
      headers: {
        Authorization: `QQBot ${token}`,
        "X-Union-Appid": this.config.appid
      }
    });
    this.ws.on("open", () => {
      if (this.sessionId) {
        this.sendOp(6, {
          token: `QQBot ${this.token}`,
          session_id: this.sessionId,
          seq: this.seq
        });
      }
    });
    this.ws.on("message", (raw) => {
      try {
        const payload = JSON.parse(raw.toString());
        this.handlePayload(payload).catch(() => {
        });
      } catch {
      }
    });
    this.ws.on("close", () => {
      if (!this.closed) {
        if (this.readyReceived) {
          console.error("QQ WebSocket reconnecting...");
          this.cleanup();
          setTimeout(() => this.reconnect(), 3e3);
        } else {
          const msg = "QQ WebSocket closed before authentication completed \u2014 check your appId and appSecret";
          this.emit("bot_error", msg);
          this.closed = true;
        }
      }
    });
    this.ws.on("error", (err) => {
      const msg = `QQ WebSocket error: ${err.message}`;
      console.error(msg);
      this.emit("bot_error", msg);
    });
  }
  async start() {
    this.closed = false;
    this.readyReceived = false;
    await this.connect();
  }
  async stop() {
    this.closed = true;
    this.cleanup();
  }
  async sendPrivateMessage(openid, content, msgId, msgSeq) {
    const token = await this.ensureToken();
    const body = {
      content,
      msg_type: 0
    };
    if (msgId) body.msg_id = msgId;
    if (typeof msgSeq === "number" && Number.isFinite(msgSeq)) body.msg_seq = Math.trunc(msgSeq);
    const res = await fetch(`${this.baseUrl}/v2/users/${encodeURIComponent(openid)}/messages`, {
      method: "POST",
      headers: {
        Authorization: `QQBot ${token}`,
        "Content-Type": "application/json",
        "X-Union-Appid": this.config.appid
      },
      body: JSON.stringify(body)
    });
    if (!res.ok) {
      const text = await res.text();
      const msg = `QQ sendPrivateMessage failed (${res.status}): ${text}`;
      throw new Error(msg);
    }
  }
};

// src/qq/strings.ts
function formatQQModeLabel(codeMode) {
  return t(codeMode ? "handlers.qq.modeCode" : "handlers.qq.modeChat");
}
function formatQQAccessSummary(config) {
  const ownerOpenId = normalizeQQOpenId(config.ownerOpenId);
  const allowlist = normalizeQQAllowlist(config.allowlist) ?? [];
  const runtimeBoundOpenId = normalizeQQOpenId(config.runtimeBoundOpenId);
  if (ownerOpenId) {
    if (allowlist.length > 0) {
      return t("handlers.qq.accessOwnerWithAllowlist", {
        owner: redactQQOpenId(ownerOpenId),
        count: allowlist.length
      });
    }
    return t("handlers.qq.accessOwner", {
      owner: redactQQOpenId(ownerOpenId)
    });
  }
  if (allowlist.length > 0) {
    return t("handlers.qq.accessAllowlist", { count: allowlist.length });
  }
  if (runtimeBoundOpenId) {
    return t("handlers.qq.accessRuntime", {
      owner: redactQQOpenId(runtimeBoundOpenId)
    });
  }
  return t("handlers.qq.accessOpen");
}
function formatQQSetupPrompt(step) {
  return t(step === "appId" ? "handlers.qq.promptAppId" : "handlers.qq.promptAppSecret");
}
function formatQQSetupWaiting(step) {
  return t(
    step === "appId" ? "handlers.qq.setupWaitingAppId" : "handlers.qq.setupWaitingAppSecret"
  );
}

// src/qq/channel.ts
var QQ_LOCK_FILE = join(homedir(), ".reasonix", "qq-channel.pid");
var QQ_MAX_CHUNK_BYTES = 1500;
var NATURAL_SPLIT_MIN_FRACTION = 0.6;
function fitUtf8Slice(text, maxBytes) {
  let end = 0;
  let bytes = 0;
  for (const char of text) {
    const nextBytes = Buffer.byteLength(char, "utf8");
    if (bytes > 0 && bytes + nextBytes > maxBytes) break;
    end += char.length;
    bytes += nextBytes;
  }
  return end > 0 ? text.slice(0, end) : text.slice(0, 1);
}
function pickNaturalSplit(candidate) {
  const minSplit = Math.floor(candidate.length * NATURAL_SPLIT_MIN_FRACTION);
  const splitters = ["\n\n", "\n", " "];
  for (const splitter of splitters) {
    const at = candidate.lastIndexOf(splitter);
    if (at >= minSplit) return at + splitter.length;
  }
  return candidate.length;
}
function splitQQMessage(text, maxBytes = QQ_MAX_CHUNK_BYTES) {
  const chunks = [];
  let remaining = text;
  while (remaining.length > 0) {
    if (Buffer.byteLength(remaining, "utf8") <= maxBytes) {
      chunks.push(remaining);
      break;
    }
    const candidate = fitUtf8Slice(remaining, maxBytes);
    const splitAt = pickNaturalSplit(candidate);
    chunks.push(candidate.slice(0, splitAt));
    remaining = remaining.slice(splitAt).trimStart();
  }
  return chunks;
}
var QQChannel = class {
  constructor(callbacks) {
    this.callbacks = callbacks;
  }
  callbacks;
  bot = null;
  qqUserId = null;
  qqMessageId = null;
  ownerOpenId;
  allowlist;
  runtimeBoundOpenId = null;
  processedMsgIds = /* @__PURE__ */ new Set();
  processedMsgIdQueue = [];
  lockAcquired = false;
  nextOutboundMsgSeq = 1;
  rememberMessage(id) {
    if (this.processedMsgIds.has(id)) return false;
    this.processedMsgIds.add(id);
    this.processedMsgIdQueue.push(id);
    if (this.processedMsgIdQueue.length > 200) {
      const oldest = this.processedMsgIdQueue.shift();
      if (oldest) this.processedMsgIds.delete(oldest);
    }
    return true;
  }
  acquireLock() {
    try {
      const existing = Number(readFileSync(QQ_LOCK_FILE, "utf8").trim());
      if (Number.isInteger(existing) && existing > 0 && existing !== process.pid) {
        try {
          process.kill(existing, 0);
          throw new Error(t("handlers.qq.lockAlreadyRunning", { pid: existing }));
        } catch (err) {
          const e = err;
          if (e.code !== "ESRCH") throw err;
        }
      }
    } catch (err) {
      const e = err;
      if (e.code !== "ENOENT") throw err;
    }
    mkdirSync(dirname(QQ_LOCK_FILE), { recursive: true });
    writeFileSync(QQ_LOCK_FILE, String(process.pid), "utf8");
    this.lockAcquired = true;
  }
  releaseLock() {
    if (!this.lockAcquired) return;
    try {
      const existing = Number(readFileSync(QQ_LOCK_FILE, "utf8").trim());
      if (existing === process.pid) unlinkSync(QQ_LOCK_FILE);
    } catch {
    }
    this.lockAcquired = false;
  }
  applyAccessConfig(config) {
    this.ownerOpenId = config.ownerOpenId;
    this.allowlist = config.allowlist;
    if (this.ownerOpenId || (this.allowlist?.length ?? 0) > 0) {
      this.runtimeBoundOpenId = null;
    }
  }
  handlePrivateMessage(msg) {
    const text = msg.content?.trim();
    if (!text) return;
    if (!this.rememberMessage(msg.id)) return;
    const openid = msg.author.user_openid;
    const verdict = decideQQAccess(
      {
        ownerOpenId: this.ownerOpenId,
        allowlist: this.allowlist,
        runtimeBoundOpenId: this.runtimeBoundOpenId
      },
      openid
    );
    if (!verdict.accept) {
      this.callbacks.onError?.(
        t("handlers.qq.unauthorizedMessage", {
          openid: redactQQOpenId(openid),
          access: formatQQAccessSummary({
            ownerOpenId: this.ownerOpenId,
            allowlist: this.allowlist,
            runtimeBoundOpenId: this.runtimeBoundOpenId
          })
        })
      );
      return;
    }
    if (verdict.bindRuntime) {
      this.runtimeBoundOpenId = openid;
      this.callbacks.onError?.(
        t("handlers.qq.runtimeBound", {
          openid: redactQQOpenId(openid)
        })
      );
    }
    this.qqUserId = openid;
    this.qqMessageId = msg.id;
    this.callbacks.onSubmitMessage(`[QQ] ${text}`);
  }
  refreshAccessConfig() {
    this.applyAccessConfig(loadQQConfig());
  }
  describeAccess() {
    return describeQQAccess({
      ownerOpenId: this.ownerOpenId,
      allowlist: this.allowlist,
      runtimeBoundOpenId: this.runtimeBoundOpenId
    });
  }
  getRuntimeBoundOpenId() {
    return this.runtimeBoundOpenId;
  }
  async start() {
    loadDotenv();
    this.acquireLock();
    const config = loadQQConfig();
    if (!config.appId) {
      this.releaseLock();
      throw new Error(t("handlers.qq.missingAppId"));
    }
    if (!config.appSecret) {
      this.releaseLock();
      throw new Error(t("handlers.qq.missingAppSecret"));
    }
    this.applyAccessConfig(config);
    const bot = new QQBot({
      appid: config.appId,
      secret: config.appSecret,
      sandbox: config.sandbox ?? false
    });
    bot.on("online", () => {
      process.stderr.write("QQ bot is online!\n");
    });
    bot.on("bot_error", (msg) => {
      this.callbacks.onError?.(msg);
    });
    bot.on("message.private", (msg) => {
      this.handlePrivateMessage(msg);
    });
    this.bot = bot;
    try {
      await bot.start();
      const readyOrError = await Promise.race([
        new Promise((resolve) => bot.once("online", () => resolve("ready"))),
        new Promise((resolve) => bot.once("bot_error", () => resolve("error"))),
        new Promise((resolve) => setTimeout(() => resolve("timeout"), 15e3))
      ]);
      if (readyOrError === "error") {
        throw new Error(t("handlers.qq.authFailed"));
      }
      if (readyOrError === "timeout") {
        throw new Error(t("handlers.qq.readyTimeout"));
      }
    } catch (err) {
      this.releaseLock();
      throw err;
    }
  }
  async sendResponse(text) {
    if (!this.bot || !this.qqUserId) return;
    const chunks = splitQQMessage(text);
    for (let index = 0; index < chunks.length; index++) {
      const chunk = chunks[index];
      if (!chunk) continue;
      try {
        await this.bot.sendPrivateMessage(
          this.qqUserId,
          chunk,
          this.qqMessageId ?? void 0,
          this.nextOutboundMsgSeq++
        );
      } catch (err) {
        const msg = `QQ sendResponse chunk ${index + 1}/${chunks.length} failed: ${err.message}`;
        this.callbacks.onError?.(msg);
        break;
      }
    }
  }
  async stop() {
    await this.bot?.stop();
    this.releaseLock();
  }
};

// src/mcp/summary.ts
function buildMcpServerSummary(opts) {
  return {
    label: opts.label,
    spec: opts.spec,
    toolCount: opts.toolCount,
    report: opts.report,
    host: opts.host,
    bridgeEnv: opts.bridgeEnv,
    readResource(uri) {
      return opts.host.client.readResource(uri);
    },
    getPrompt(name, args) {
      return args !== void 0 ? opts.host.client.getPrompt(name, args) : opts.host.client.getPrompt(name);
    }
  };
}

// src/cli/commands/mcp-runtime.ts
var stderrLifecycleSink = (n) => {
  if (n.kind === "slow") {
    process.stderr.write(
      `${formatMcpSlowToast({ name: n.serverName, p95Ms: n.p95Ms, sampleSize: n.sampleSize })}
`
    );
    return;
  }
  if (n.kind === "failed") {
    process.stderr.write(
      `${formatMcpLifecycleEvent({ state: "failed", name: n.name, reason: n.reason })}
  \u2192 ${t("mcpLifecycle.failedSetupHint")}
`
    );
    return;
  }
  if (n.kind === "connected") {
    process.stderr.write(
      `${formatMcpLifecycleEvent({
        state: "connected",
        name: n.name,
        tools: n.tools,
        resources: n.resources,
        prompts: n.prompts,
        ms: n.ms
      })}
`
    );
    return;
  }
  if (n.kind === "tools-ready") {
    process.stderr.write(
      `${formatMcpLifecycleEvent({ state: "tools-ready", name: n.name, tools: n.tools, ms: n.ms })}
`
    );
    return;
  }
  if (n.kind === "warn") {
    process.stderr.write(
      `${formatMcpLifecycleEvent({ state: "warn", name: n.name, reason: n.reason })}
`
    );
    return;
  }
  process.stderr.write(
    `${formatMcpLifecycleEvent({ state: n.kind, name: n.name })}
`
  );
};
function createMcpRuntime(ctx) {
  const records = /* @__PURE__ */ new Map();
  const insertionOrder = [];
  const failureMap = /* @__PURE__ */ new Map();
  let sink = stderrLifecycleSink;
  async function addSpec(raw, loop, signal) {
    if (records.has(raw)) {
      return { ok: true, summary: records.get(raw).summary };
    }
    failureMap.delete(raw);
    const tools = ctx.getTools();
    if (!tools) return { ok: false, reason: "no tool registry available" };
    const cfg = readConfig();
    const normalized = normalizeMcpConfig(cfg);
    let label = "anon";
    let mcp;
    let resolveReady;
    let rejectReady;
    const ready = new Promise((resolve, reject) => {
      resolveReady = resolve;
      rejectReady = reject;
    });
    ready.catch(() => void 0);
    try {
      const parsed = parseMcpSpec(raw);
      label = parsed.name ?? "anon";
      const matched = parsed.name ? normalized.find((s) => s.name === parsed.name) : void 0;
      const spec = overlayMatchedSpec(parsed, matched);
      if (spec.disabled) {
        sink({ kind: "disabled", name: label });
        rejectReady(new Error(`MCP server "${label}" is disabled`));
        failureMap.set(raw, { spec: raw, name: label, reason: "disabled by user", at: Date.now() });
        return { ok: false, reason: "disabled by user" };
      }
      sink({ kind: "handshake", name: label });
      const t0 = Date.now();
      const namePrefix = spec.name ? `${spec.name}_` : ctx.getRequestedCount() === 1 && ctx.getMcpPrefix() ? ctx.getMcpPrefix() : "";
      if (spec.transport === "stdio") preflightStdioSpec(spec);
      const transport = buildTransportFromSpec(spec);
      mcp = new McpClient({ transport });
      await mcp.initialize({ signal });
      const host = { client: mcp };
      const bridge = await bridgeMcpTools(mcp, {
        registry: tools,
        namePrefix,
        serverName: label,
        host,
        ready,
        onProgress: (info) => ctx.progressSink.current?.(info),
        onSlow: (info) => sink({
          kind: "slow",
          serverName: info.serverName,
          p95Ms: info.p95Ms,
          sampleSize: info.sampleSize
        })
      });
      const ms = Date.now() - t0;
      const allSpecs = tools.specs();
      const registeredSpecs = allSpecs.filter(
        (s) => bridge.registeredNames.includes(s.function.name)
      );
      records.set(raw, {
        spec: raw,
        client: mcp,
        summary: buildMcpServerSummary({
          label,
          spec: raw,
          toolCount: bridge.registeredNames.length,
          report: {
            protocolVersion: mcp.protocolVersion,
            serverInfo: mcp.serverInfo,
            capabilities: mcp.serverCapabilities ?? {},
            tools: { supported: true, items: [] },
            resources: { supported: false, reason: "still inspecting" },
            prompts: { supported: false, reason: "still inspecting" },
            elapsedMs: ms
          },
          host,
          bridgeEnv: bridge.env
        }),
        registeredNames: bridge.registeredNames,
        registeredSpecs
      });
      insertionOrder.push(raw);
      resolveReady();
      sink({
        kind: "tools-ready",
        name: label,
        tools: bridge.registeredNames.length,
        ms
      });
      let report;
      try {
        report = await inspectMcpServer(mcp);
      } catch {
        report = {
          protocolVersion: mcp.protocolVersion,
          serverInfo: mcp.serverInfo,
          capabilities: mcp.serverCapabilities ?? {},
          tools: { supported: true, items: [] },
          resources: { supported: false, reason: "inspect failed" },
          prompts: { supported: false, reason: "inspect failed" },
          elapsedMs: 0
        };
      }
      const resourceCount = report.resources.supported ? report.resources.items.length : 0;
      const promptCount = report.prompts.supported ? report.prompts.items.length : 0;
      sink({
        kind: "connected",
        name: label,
        tools: bridge.registeredNames.length,
        resources: resourceCount,
        prompts: promptCount,
        ms
      });
      const summary = buildMcpServerSummary({
        label,
        spec: raw,
        toolCount: bridge.registeredNames.length,
        report,
        host,
        bridgeEnv: bridge.env
      });
      records.set(raw, {
        spec: raw,
        client: mcp,
        summary,
        registeredNames: bridge.registeredNames,
        registeredSpecs
      });
      if (loop)
        for (const s of registeredSpecs)
          try {
            loop.prefix.addTool(s);
          } catch (err) {
            sink({
              kind: "warn",
              name: label,
              reason: `addTool failed for ${s.function.name}: ${err.message}`
            });
          }
      return { ok: true, summary };
    } catch (err) {
      const reason = err.message;
      if (!records.has(raw)) {
        await mcp?.close().catch(() => void 0);
        rejectReady(new Error(`MCP server "${label}" failed to start: ${reason}`));
        sink({ kind: "failed", name: label, reason });
        failureMap.set(raw, { spec: raw, name: label, reason, at: Date.now() });
        return { ok: false, reason };
      }
      sink({ kind: "warn", name: label, reason });
      return { ok: true, summary: records.get(raw).summary };
    }
  }
  async function removeSpec(raw, loop) {
    failureMap.delete(raw);
    const record = records.get(raw);
    if (!record) return false;
    await record.client.close().catch(() => void 0);
    const tools = ctx.getTools();
    for (const name of record.registeredNames) {
      tools?.unregister(name);
      loop?.prefix.removeTool(name);
    }
    records.delete(raw);
    const idx = insertionOrder.indexOf(raw);
    if (idx >= 0) insertionOrder.splice(idx, 1);
    return true;
  }
  async function reloadFromConfig(loop) {
    const normalized = normalizeMcpConfig(readConfig());
    const desired = normalized.map(specToRaw);
    const desiredSet = new Set(desired);
    const currentSet = new Set(records.keys());
    const added = [];
    const removed = [];
    const failed = [];
    for (const spec of [...currentSet]) {
      if (!desiredSet.has(spec)) {
        await removeSpec(spec, loop);
        removed.push(spec);
      }
    }
    for (const spec of desired) {
      if (currentSet.has(spec)) continue;
      const result = await addSpec(spec, loop);
      if (result.ok) added.push(spec);
      else failed.push({ spec, reason: result.reason });
    }
    return { added, removed, failed, summaries: summaries() };
  }
  function specs() {
    return [...insertionOrder];
  }
  function summaries() {
    return insertionOrder.map((s) => records.get(s)?.summary).filter((s) => Boolean(s));
  }
  async function closeAll() {
    for (const r of records.values()) await r.client.close().catch(() => void 0);
    records.clear();
    insertionOrder.length = 0;
    failureMap.clear();
  }
  function failures() {
    return [...failureMap.values()];
  }
  function setLifecycleSink(s) {
    sink = s;
  }
  return {
    size: () => records.size,
    specs,
    summaries,
    failures,
    addSpec,
    removeSpec,
    reloadFromConfig,
    closeAll,
    setLifecycleSink
  };
}

export {
  formatQQModeLabel,
  formatQQAccessSummary,
  formatQQSetupPrompt,
  formatQQSetupWaiting,
  QQChannel,
  createMcpRuntime
};
//# sourceMappingURL=chunk-U5XQDCK7.js.map