agent-bridge.js
6.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
/**
* 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 };