agent-bridge.js 6.06 KB
/**
 * agent-bridge.js — Agent 路由桥接
 * 
 * 把 OpenClaw 的 Agent 分发流程桥接到我们的 orchestrator。
 * 
 * OpenClaw 的消息处理链路:
 *   handleFeishuMessage() → resolveAgentRoute() → dispatchReplyFromConfig()
 *   → Agent Runtime 处理消息 → 通过 reply-dispatcher 回复
 * 
 * 我们的桥接方式:
 *   在 feishu 的 channel monitor 收到消息后,消息会被传给 dispatchReplyFromConfig(),
 *   dispatchReplyFromConfig() 内部会调用 Agent 来处理。
 *   
 *   我们通过配置 Agent 路由,让消息路由到我们自定义的 "agent",
 *   这个 agent 内部调用 orchestrator 来处理消息。
 * 
 * 用法:
 *   const { createAgentBridge } = require('./plugins/agent-bridge');
 *   const bridge = createAgentBridge(orchestrator);
 */

/**
 * 创建 Agent 桥接器。
 * 
 * 桥接器提供一个统一的消息处理接口,
 * 把来自任何 OpenClaw channel(feishu/discord/slack 等)的消息
 * 转发给我们的 orchestrator 进行多 Agent 编排。
 * 
 * @param {Object} orchestrator - 我们的编排器实例
 * @param {Function} orchestrator.runOrchestration - 编排执行函数
 * @param {Object} [options] - 选项
 * @param {Object} [options.logger] - 日志接口
 * @returns {Object} 桥接器
 */
function createAgentBridge(orchestrator, options = {}) {
  const logger = options.logger || { info: console.log, warn: console.warn, error: console.error, debug: () => { } };

  // 消息处理记录
  const history = [];
  const MAX_HISTORY = 100;

  /**
   * 处理来自 OpenClaw channel 的入站消息。
   * 
   * @param {Object} ctx - 消息上下文(OpenClaw InboundContext 格式)
   * @param {string} ctx.Body - 消息正文
   * @param {string} [ctx.BodyForAgent] - 格式化后的消息正文
   * @param {string} ctx.SessionKey - 会话标识
   * @param {string} [ctx.SenderId] - 发送者 ID
   * @param {string} [ctx.SenderName] - 发送者名称
   * @param {string} [ctx.Channel] - 通道名称
   * @param {Object} [ctx.Media] - 媒体附件
   * @returns {Promise<Object>} 处理结果 { text, media }
   */
  async function handleInbound(ctx) {
    const userMessage = ctx.BodyForAgent || ctx.Body || '';
    const channel = ctx.Channel || 'unknown';
    const sessionKey = ctx.SessionKey || 'default';
    const senderId = ctx.SenderId || 'unknown';
    const senderName = ctx.SenderName || senderId;

    const startTime = Date.now();

    logger.info(`[agent-bridge] 收到消息: channel=${channel}, sender=${senderName}, length=${userMessage.length}`);
    logger.debug(`[agent-bridge] 消息内容: ${userMessage.substring(0, 200)}...`);

    try {
      // 调用编排器
      const result = await orchestrator.runOrchestration({
        task: userMessage,
        sessionKey,
        context: {
          channel,
          sender: senderId,
          senderName,
          media: ctx.Media || null,
        },
      });

      const elapsed = Date.now() - startTime;

      // 提取回复文本
      const replyText = result.summary || result.output || result.text ||
        (typeof result === 'string' ? result : JSON.stringify(result));

      // 记录历史
      const record = {
        timestamp: new Date().toISOString(),
        channel,
        sessionKey,
        senderId,
        inputLength: userMessage.length,
        outputLength: replyText.length,
        elapsedMs: elapsed,
        status: 'success',
      };
      history.push(record);
      if (history.length > MAX_HISTORY) history.shift();

      logger.info(`[agent-bridge] 完成: ${elapsed}ms, output=${replyText.length}字符`);

      return {
        text: replyText,
        media: result.media || null,
      };
    } catch (err) {
      const elapsed = Date.now() - startTime;

      logger.error(`[agent-bridge] 编排失败: ${err.message}`);

      // 记录错误
      const record = {
        timestamp: new Date().toISOString(),
        channel,
        sessionKey,
        senderId,
        inputLength: userMessage.length,
        elapsedMs: elapsed,
        status: 'error',
        error: err.message,
      };
      history.push(record);
      if (history.length > MAX_HISTORY) history.shift();

      return {
        text: `处理失败: ${err.message}`,
        media: null,
      };
    }
  }

  /**
   * 创建一个 OpenClaw 兼容的 "Agent Provider",
   * 用于替换 OpenClaw 内置的 Agent(如 Claude/GPT),
   * 把消息路由到我们的编排器。
   * 
   * 当 OpenClaw 的 dispatchReplyFromConfig 内部需要调用 Agent 时,
   * 它会通过 Agent Provider 来获取回复。
   * 我们的 provider 拦截这个调用,桥接到 orchestrator。
   */
  function createProvider() {
    return {
      id: 'multi-agent-orchestrator',
      name: 'Multi-Agent Orchestrator',

      async chat(messages, options) {
        // 从 messages 中提取最后一条用户消息
        const lastUserMsg = messages
          .filter(m => m.role === 'user')
          .pop();

        if (!lastUserMsg) {
          return { content: '(无有效输入)' };
        }

        const result = await handleInbound({
          Body: typeof lastUserMsg.content === 'string'
            ? lastUserMsg.content
            : JSON.stringify(lastUserMsg.content),
          SessionKey: options?.sessionKey || 'default',
          Channel: options?.channel || 'api',
        });

        return { content: result.text };
      },
    };
  }

  return {
    handleInbound,
    createProvider,

    /** 获取处理历史 */
    getHistory() {
      return [...history];
    },

    /** 获取统计信息 */
    getStats() {
      const successCount = history.filter(h => h.status === 'success').length;
      const errorCount = history.filter(h => h.status === 'error').length;
      const avgMs = successCount > 0
        ? Math.round(history.filter(h => h.status === 'success').reduce((s, h) => s + h.elapsedMs, 0) / successCount)
        : 0;

      return {
        total: history.length,
        success: successCount,
        error: errorCount,
        avgResponseMs: avgMs,
      };
    },
  };
}

module.exports = { createAgentBridge };