evolclaw 2.8.3 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -12
- package/bin/ec.js +29 -0
- package/dist/agents/baseagent-normalize.js +19 -0
- package/dist/agents/claude-runner.js +108 -46
- package/dist/agents/codex-runner.js +13 -14
- package/dist/agents/gemini-runner.js +15 -17
- package/dist/agents/kit-renderer.js +281 -0
- package/dist/agents/resolve.js +134 -0
- package/dist/aun/aid/agentmd.js +186 -0
- package/dist/aun/aid/client.js +134 -0
- package/dist/aun/aid/identity.js +159 -0
- package/dist/aun/aid/index.js +3 -0
- package/dist/aun/aid/lifecycle-log.js +33 -0
- package/dist/aun/aid/types.js +1 -0
- package/dist/aun/aid/validation.js +21 -0
- package/dist/aun/msg/group.js +293 -0
- package/dist/aun/msg/index.js +4 -0
- package/dist/aun/msg/p2p.js +147 -0
- package/dist/aun/msg/payload-type.js +27 -0
- package/dist/aun/msg/upload.js +98 -0
- package/dist/aun/outbox.js +138 -0
- package/dist/aun/rpc/caller.js +42 -0
- package/dist/aun/rpc/connection.js +34 -0
- package/dist/aun/rpc/index.js +2 -0
- package/dist/aun/storage/download.js +29 -0
- package/dist/aun/storage/index.js +3 -0
- package/dist/aun/storage/manage.js +10 -0
- package/dist/aun/storage/upload.js +35 -0
- package/dist/channels/aun.js +1340 -349
- package/dist/channels/dingtalk.js +59 -5
- package/dist/channels/feishu.js +381 -32
- package/dist/channels/qqbot.js +68 -12
- package/dist/channels/wechat.js +63 -4
- package/dist/channels/wecom.js +59 -5
- package/dist/cli/agent.js +800 -0
- package/dist/cli/bench.js +1219 -0
- package/dist/cli/index.js +4513 -0
- package/dist/{utils → cli}/init-channel.js +211 -621
- package/dist/cli/init.js +178 -0
- package/dist/cli/link-rules.js +245 -0
- package/dist/cli/net-check.js +640 -0
- package/dist/cli/watch-msg.js +589 -0
- package/dist/config-store.js +645 -0
- package/dist/core/{agent-loader.js → baseagent-loader.js} +6 -12
- package/dist/core/channel-loader.js +176 -12
- package/dist/core/command-handler.js +883 -848
- package/dist/core/evolagent-registry.js +191 -371
- package/dist/core/evolagent.js +202 -238
- package/dist/core/interaction-router.js +52 -5
- package/dist/core/message/im-renderer.js +486 -0
- package/dist/core/message/items-formatter.js +68 -0
- package/dist/core/message/message-bridge.js +109 -56
- package/dist/core/message/message-log.js +93 -0
- package/dist/core/message/message-processor.js +430 -212
- package/dist/core/message/message-queue.js +13 -6
- package/dist/core/permission.js +116 -11
- package/dist/core/session/adapters/codex-session-file-adapter.js +24 -2
- package/dist/core/session/session-fs-store.js +230 -0
- package/dist/core/session/session-manager.js +740 -777
- package/dist/core/session/session-mapper.js +87 -0
- package/dist/core/trigger/manager.js +122 -0
- package/dist/core/trigger/parser.js +128 -0
- package/dist/core/trigger/scheduler.js +224 -0
- package/dist/data/error-dict.json +118 -0
- package/dist/eck/baseagent-caps.js +18 -0
- package/dist/eck/detect.js +47 -0
- package/dist/eck/init.js +77 -0
- package/dist/eck/rules-loader.js +28 -0
- package/dist/index.js +560 -283
- package/dist/ipc.js +49 -0
- package/dist/net-check.js +640 -0
- package/dist/paths.js +73 -9
- package/dist/types.js +8 -2
- package/dist/utils/aid-lifecycle-log.js +33 -0
- package/dist/utils/atomic-write.js +89 -0
- package/dist/utils/channel-helpers.js +46 -0
- package/dist/utils/cross-platform.js +17 -26
- package/dist/utils/error-utils.js +10 -2
- package/dist/utils/instance-registry.js +434 -0
- package/dist/utils/log-writer.js +217 -0
- package/dist/utils/logger.js +34 -77
- package/dist/utils/media-cache.js +23 -0
- package/dist/utils/npm-ops.js +163 -0
- package/dist/utils/process-introspect.js +122 -0
- package/dist/utils/stats.js +192 -0
- package/dist/watch-msg.js +544 -0
- package/evolclaw-install-aun.md +127 -47
- package/kits/docs/GUIDE.md +20 -0
- package/kits/docs/INDEX.md +52 -0
- package/kits/docs/aun/CHEATSHEET.md +17 -0
- package/kits/docs/aun/SYNC_PROTOCOL.md +15 -0
- package/kits/docs/channels/aun.md +25 -0
- package/kits/docs/channels/feishu.md +27 -0
- package/kits/docs/eck_templates/GUIDE.template.md +22 -0
- package/kits/docs/eck_templates/INDEX.template.md +28 -0
- package/kits/docs/eck_templates/path-registry.template.md +33 -0
- package/kits/docs/eck_templates/runtime.template.md +19 -0
- package/kits/docs/evolclaw/AGENT_CMD.md +31 -0
- package/kits/docs/evolclaw/MSG_GROUP.md +30 -0
- package/kits/docs/evolclaw/MSG_PRIVATE.md +25 -0
- package/kits/docs/evolclaw/self-summary.md +29 -0
- package/kits/docs/evolclaw/tools.md +25 -0
- package/kits/docs/identity/AID_PROFILE_SPEC.md +27 -0
- package/kits/docs/identity/PATH_OPS.md +16 -0
- package/kits/docs/identity/ROLE_DETAIL.md +20 -0
- package/kits/docs/identity/identity-tools.md +26 -0
- package/kits/docs/path-registry.md +43 -0
- package/kits/eck_manifest.json +95 -0
- package/kits/rules/01-overview.md +120 -0
- package/kits/rules/02-navigation.md +75 -0
- package/kits/rules/03-identity.md +34 -0
- package/kits/rules/04-relation.md +49 -0
- package/kits/rules/05-venue.md +45 -0
- package/kits/rules/06-channel.md +43 -0
- package/kits/templates/system-fragments/baseagent.md +2 -0
- package/kits/templates/system-fragments/channel.md +10 -0
- package/kits/templates/system-fragments/identity.md +12 -0
- package/kits/templates/system-fragments/relation.md +9 -0
- package/kits/templates/system-fragments/runtime.md +19 -0
- package/kits/templates/system-fragments/venue.md +5 -0
- package/package.json +10 -6
- package/data/evolclaw.sample.json +0 -60
- package/dist/agents/templates.js +0 -122
- package/dist/channels/aun-ops.js +0 -275
- package/dist/cli.js +0 -2178
- package/dist/config.js +0 -591
- package/dist/core/agent-registry.js +0 -450
- package/dist/core/evolagent-schema.js +0 -72
- package/dist/core/message/stream-flusher.js +0 -238
- package/dist/core/message/thought-emitter.js +0 -162
- package/dist/core/reload-hooks.js +0 -87
- package/dist/prompts/templates.js +0 -122
- package/dist/templates/prompts.md +0 -104
- package/dist/templates/skills.md +0 -66
- package/dist/utils/channel-fingerprint.js +0 -59
- package/dist/utils/error-dict.js +0 -63
- package/dist/utils/format.js +0 -32
- package/dist/utils/init.js +0 -645
- package/dist/utils/migrate-project.js +0 -122
- package/dist/utils/reload-hooks.js +0 -87
- package/dist/utils/stats-collector.js +0 -99
- package/dist/utils/upgrade.js +0 -100
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { randomBytes } from 'crypto';
|
|
1
2
|
import { logger } from '../../utils/logger.js';
|
|
2
3
|
import { StreamDebouncer } from './stream-debouncer.js';
|
|
4
|
+
import { appendMessageLog, buildInboundEntry } from './message-log.js';
|
|
5
|
+
import { buildEnvelope } from './message-processor.js';
|
|
3
6
|
/**
|
|
4
7
|
* MessageBridge — Channel 与 Core 之间的消息桥梁
|
|
5
8
|
*
|
|
@@ -8,7 +11,7 @@ import { StreamDebouncer } from './stream-debouncer.js';
|
|
|
8
11
|
* 出站:命令响应通过 sendReply 回调直接发送到渠道
|
|
9
12
|
*/
|
|
10
13
|
export class MessageBridge {
|
|
11
|
-
|
|
14
|
+
defaultProjectPath;
|
|
12
15
|
sessionManager;
|
|
13
16
|
processor;
|
|
14
17
|
messageQueue;
|
|
@@ -17,14 +20,14 @@ export class MessageBridge {
|
|
|
17
20
|
debouncers = new Map();
|
|
18
21
|
defaultDebounce;
|
|
19
22
|
agentRegistry;
|
|
20
|
-
constructor(
|
|
21
|
-
this.
|
|
23
|
+
constructor(defaultProjectPath, sessionManager, processor, messageQueue, cmdHandler, eventBus, defaultDebounce) {
|
|
24
|
+
this.defaultProjectPath = defaultProjectPath;
|
|
22
25
|
this.sessionManager = sessionManager;
|
|
23
26
|
this.processor = processor;
|
|
24
27
|
this.messageQueue = messageQueue;
|
|
25
28
|
this.cmdHandler = cmdHandler;
|
|
26
29
|
this.eventBus = eventBus;
|
|
27
|
-
this.defaultDebounce =
|
|
30
|
+
this.defaultDebounce = defaultDebounce ?? 2;
|
|
28
31
|
}
|
|
29
32
|
/** Inject EvolAgentRegistry so owner lookups/writes route to agent.json for agent-owned channels. */
|
|
30
33
|
setAgentRegistry(registry) {
|
|
@@ -33,19 +36,13 @@ export class MessageBridge {
|
|
|
33
36
|
getDebouncer(channelName, channelType) {
|
|
34
37
|
let d = this.debouncers.get(channelName);
|
|
35
38
|
if (!d) {
|
|
39
|
+
// 从 owning agent 的 channel 配置取 debounce,找不到用全局默认
|
|
36
40
|
let seconds = this.defaultDebounce;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const inst = raw.find((i) => (i.name || type) === channelName);
|
|
43
|
-
if (inst?.debounce !== undefined)
|
|
44
|
-
seconds = inst.debounce;
|
|
45
|
-
}
|
|
46
|
-
else if (raw.debounce !== undefined) {
|
|
47
|
-
seconds = raw.debounce;
|
|
48
|
-
}
|
|
41
|
+
const agent = this.agentRegistry?.resolveByChannel(channelName);
|
|
42
|
+
if (agent) {
|
|
43
|
+
const merged = agent.config;
|
|
44
|
+
if (merged?.debounce !== undefined)
|
|
45
|
+
seconds = merged.debounce;
|
|
49
46
|
}
|
|
50
47
|
d = new StreamDebouncer(seconds);
|
|
51
48
|
this.debouncers.set(channelName, d);
|
|
@@ -66,6 +63,8 @@ export class MessageBridge {
|
|
|
66
63
|
onMessage(async (msg) => {
|
|
67
64
|
try {
|
|
68
65
|
let content = msg.content.trim();
|
|
66
|
+
// 渠道入站日志
|
|
67
|
+
logger.channelIn({ channel: channelName, channelId: msg.channelId, peerId: msg.peerId, peerName: msg.peerName, chatType: msg.chatType, msgId: msg.messageId, threadId: msg.threadId, content, images: msg.images?.length ?? 0, mentions: msg.mentions, replyContext: msg.replyContext });
|
|
69
68
|
// 0. 自定义消息快速路径(menu.query 等)
|
|
70
69
|
if (await this.handleCustomPayload(content, channelName, msg, sendReply, adapter))
|
|
71
70
|
return;
|
|
@@ -78,7 +77,10 @@ export class MessageBridge {
|
|
|
78
77
|
if (this.cmdHandler.isCommand(cmdContent)) {
|
|
79
78
|
logger.debug(`[MessageBridge] Command detected: "${cmdContent}", routing to handler`);
|
|
80
79
|
}
|
|
81
|
-
if (await this.handleCommand(cmdContent, channelName, msg.channelId, (text) =>
|
|
80
|
+
if (await this.handleCommand(cmdContent, channelName, msg.channelId, (text) => {
|
|
81
|
+
logger.channelOut({ channel: channelName, channelId: msg.channelId, taskId: `cmd-${msg.messageId || Date.now()}`, payload: { kind: 'command.result', text } });
|
|
82
|
+
return sendReply(msg.channelId, text, msg.replyContext);
|
|
83
|
+
}, msg.peerId, msg.threadId, msg.chatType, msg.source, msg.replyContext))
|
|
82
84
|
return;
|
|
83
85
|
// 3. session 解析(使用 Channel 层填充的 chatType)
|
|
84
86
|
const chatType = msg.chatType || 'private';
|
|
@@ -93,13 +95,21 @@ export class MessageBridge {
|
|
|
93
95
|
if (msg.peerName)
|
|
94
96
|
metadata.peerName = msg.peerName;
|
|
95
97
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
+
if (chatType === 'group') {
|
|
99
|
+
// 群聊:peerId 是当前消息发送者;groupId 在 channel 提供时存到 metadata
|
|
100
|
+
if (msg.peerId)
|
|
101
|
+
metadata.peerId = msg.peerId;
|
|
102
|
+
if (msg.peerName)
|
|
103
|
+
metadata.peerName = msg.peerName;
|
|
104
|
+
if (msg.groupId)
|
|
105
|
+
metadata.groupId = msg.groupId;
|
|
106
|
+
}
|
|
107
|
+
// Resolve effective project path: 用通道所属 agent 的 projectPath;
|
|
108
|
+
// 通道找不到归属时退回到 globalConfig(一般是测试场景)
|
|
98
109
|
const owningAgent = this.agentRegistry?.resolveByChannel(channelName);
|
|
99
|
-
const effectiveProjectPath =
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const session = await this.sessionManager.getOrCreateSession(channelName, msg.channelId, effectiveProjectPath, msg.threadId, Object.keys(metadata).length ? metadata : undefined, undefined, msg.peerId, chatType);
|
|
110
|
+
const effectiveProjectPath = owningAgent?.projectPath
|
|
111
|
+
?? this.defaultProjectPath;
|
|
112
|
+
const session = await this.sessionManager.getOrCreateSession(channelName, msg.channelId, effectiveProjectPath, msg.threadId, Object.keys(metadata).length ? metadata : undefined, undefined, msg.peerId, chatType, undefined, msg.selfId, msg.channelType || effectiveChannelType, msg.peerType);
|
|
103
113
|
// 4. 消息前缀(由 policy 决定)
|
|
104
114
|
const channelInfo = this.processor.getChannelInfo?.(channelName);
|
|
105
115
|
if (channelInfo?.policy) {
|
|
@@ -109,7 +119,10 @@ export class MessageBridge {
|
|
|
109
119
|
}
|
|
110
120
|
// 5. 构造完整消息(channel 字段存实例名,用于 session 精确匹配)
|
|
111
121
|
const fullMessage = {
|
|
112
|
-
channel: channelName,
|
|
122
|
+
channel: channelName,
|
|
123
|
+
channelType: msg.channelType || effectiveChannelType,
|
|
124
|
+
channelId: msg.channelId, content,
|
|
125
|
+
selfId: msg.selfId,
|
|
113
126
|
chatType,
|
|
114
127
|
images: msg.images, timestamp: Date.now(),
|
|
115
128
|
peerId: msg.peerId, peerName: msg.peerName,
|
|
@@ -118,6 +131,19 @@ export class MessageBridge {
|
|
|
118
131
|
mentions: msg.mentions, threadId: msg.threadId,
|
|
119
132
|
replyContext: msg.replyContext,
|
|
120
133
|
};
|
|
134
|
+
// 5.5 写入消息记录(入方向)
|
|
135
|
+
const chatDir = this.sessionManager.getChatDir(session);
|
|
136
|
+
appendMessageLog(chatDir, buildInboundEntry({
|
|
137
|
+
from: msg.peerId || 'unknown',
|
|
138
|
+
to: msg.selfId || 'self',
|
|
139
|
+
chatType,
|
|
140
|
+
groupId: msg.groupId ?? null,
|
|
141
|
+
msgId: msg.messageId ?? null,
|
|
142
|
+
content,
|
|
143
|
+
replyTo: msg.replyContext?.replyToMessageId ?? null,
|
|
144
|
+
permMode: session.identity?.role ?? null,
|
|
145
|
+
timestamp: fullMessage.timestamp,
|
|
146
|
+
}));
|
|
121
147
|
// 6. ACK + debounce/enqueue
|
|
122
148
|
// ACK 在到达时立即做(每条独立 ACK),不等合并
|
|
123
149
|
// Interrupt 模式(单聊)→ 入队前 debounce 合并
|
|
@@ -125,7 +151,7 @@ export class MessageBridge {
|
|
|
125
151
|
if (fullMessage.messageId)
|
|
126
152
|
adapter?.acknowledge?.(fullMessage.messageId).catch(() => { });
|
|
127
153
|
const isInterrupt = chatType !== 'group';
|
|
128
|
-
const enqueueAgentName =
|
|
154
|
+
const enqueueAgentName = owningAgent?.name ?? '<unknown>';
|
|
129
155
|
const doEnqueue = async (m) => {
|
|
130
156
|
return this.messageQueue.enqueue(session.id, m, session.projectPath, {
|
|
131
157
|
interruptible: isInterrupt,
|
|
@@ -169,71 +195,98 @@ export class MessageBridge {
|
|
|
169
195
|
const result = await this.cmdHandler.execMenu(parsed.cmd, parsed.mode, channel, msg.channelId, msg.peerId);
|
|
170
196
|
const base = { type: 'menu.response', cmd: parsed.cmd };
|
|
171
197
|
const response = JSON.stringify('error' in result ? { ...base, error: result.error } : { ...base, data: result.data });
|
|
172
|
-
|
|
173
|
-
adapter.sendCustomPayload(msg.channelId, response);
|
|
174
|
-
}
|
|
175
|
-
else {
|
|
176
|
-
await sendReply(msg.channelId, response);
|
|
177
|
-
}
|
|
198
|
+
await this.sendCustomResponse(adapter, channel, msg.channelId, response, sendReply);
|
|
178
199
|
}
|
|
179
200
|
else if (parsed.cmd) {
|
|
180
201
|
// 动态子菜单查询
|
|
181
202
|
const items = await this.cmdHandler.getSubMenuItems(parsed.cmd, channel, msg.channelId, msg.peerId);
|
|
182
203
|
const response = JSON.stringify({ type: 'menu.response', cmd: parsed.cmd, items: items ?? [] });
|
|
183
|
-
|
|
184
|
-
adapter.sendCustomPayload(msg.channelId, response);
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
await sendReply(msg.channelId, response);
|
|
188
|
-
}
|
|
204
|
+
await this.sendCustomResponse(adapter, channel, msg.channelId, response, sendReply);
|
|
189
205
|
}
|
|
190
206
|
else {
|
|
191
207
|
// 全量菜单
|
|
192
208
|
const identity = this.sessionManager.resolveIdentity(channel, msg.peerId);
|
|
193
209
|
const items = this.cmdHandler.getMenuItems(identity.role, msg.chatType || 'private');
|
|
194
210
|
const response = JSON.stringify({ type: 'menu.response', items });
|
|
195
|
-
|
|
196
|
-
adapter.sendCustomPayload(msg.channelId, response);
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
await sendReply(msg.channelId, response);
|
|
200
|
-
}
|
|
211
|
+
await this.sendCustomResponse(adapter, channel, msg.channelId, response, sendReply);
|
|
201
212
|
}
|
|
202
213
|
return true;
|
|
203
214
|
}
|
|
204
215
|
return false;
|
|
205
216
|
}
|
|
206
|
-
/**
|
|
217
|
+
/** menu.query 响应:优先走 adapter.send(custom),降级 sendReply */
|
|
218
|
+
async sendCustomResponse(adapter, channel, channelId, response, sendReply) {
|
|
219
|
+
if (adapter?.send) {
|
|
220
|
+
const agentName = this.agentRegistry?.resolveByChannel(channel)?.name ?? '<unknown>';
|
|
221
|
+
const envelope = buildEnvelope({
|
|
222
|
+
taskId: `menu-${randomBytes(4).toString('hex')}`,
|
|
223
|
+
channel,
|
|
224
|
+
channelId,
|
|
225
|
+
agentName,
|
|
226
|
+
});
|
|
227
|
+
await adapter.send(envelope, { kind: 'custom', channelType: channel, payload: response });
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
await sendReply(channelId, response);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/** 首次交互自动绑定 owner —— 通过 channel-routed self-agent 完成 */
|
|
207
234
|
async autoBindOwner(channel, userId) {
|
|
208
|
-
|
|
209
|
-
// Falls back to evolclaw.json for default-agent channels.
|
|
210
|
-
const { getOwner, setOwner } = await import('../../config.js');
|
|
211
|
-
const currentOwner = this.agentRegistry?.getOwner?.(channel) ?? getOwner(this.config, channel);
|
|
212
|
-
// currentOwner === undefined means either no owner set, or instance not found
|
|
213
|
-
// In both cases, try to set — setOwner is a no-op for unknown instances
|
|
235
|
+
const currentOwner = this.agentRegistry?.getOwner?.(channel);
|
|
214
236
|
if (currentOwner === undefined) {
|
|
215
237
|
if (this.agentRegistry?.setChannelOwner) {
|
|
216
238
|
this.agentRegistry.setChannelOwner(channel, userId);
|
|
217
239
|
}
|
|
218
240
|
else {
|
|
219
|
-
|
|
241
|
+
logger.warn(`[Owner] no agentRegistry; skip auto-bind for ${channel}`);
|
|
242
|
+
return;
|
|
220
243
|
}
|
|
221
244
|
logger.info(`[Owner] Auto-bound ${channel} owner: ${userId}`);
|
|
222
245
|
this.eventBus.publish({ type: 'channel:owner-bound', channel, userId });
|
|
223
246
|
}
|
|
224
247
|
}
|
|
225
248
|
/** 命令快速路径:返回 true 表示已处理 */
|
|
226
|
-
async handleCommand(content, channel, channelId, sendReply, userId, threadId) {
|
|
249
|
+
async handleCommand(content, channel, channelId, sendReply, userId, threadId, chatType, source, replyContext) {
|
|
227
250
|
if (!this.cmdHandler.isCommand(content))
|
|
228
251
|
return false;
|
|
229
|
-
logger.info(`[${channel}] ${channelId}: ${content}`);
|
|
230
|
-
const cmdResult = await this.cmdHandler.handle(content, channel, channelId, (_cid, text, opts) => sendReply(text), userId, threadId);
|
|
231
|
-
logger.debug(`[MessageBridge] handleCommand: result type=${typeof cmdResult}
|
|
252
|
+
logger.info(`[${channel}] ${channelId}: ${content}${source === 'card-trigger' ? ' [card]' : ''}`);
|
|
253
|
+
const cmdResult = await this.cmdHandler.handle(content, channel, channelId, (_cid, text, opts) => sendReply(text), userId, threadId, chatType, source);
|
|
254
|
+
logger.debug(`[MessageBridge] handleCommand: result type=${typeof cmdResult}`);
|
|
232
255
|
if (cmdResult === undefined)
|
|
233
256
|
return false;
|
|
234
257
|
if (cmdResult) {
|
|
258
|
+
// 规范化为 OutboundPayload:string → command.result 包装;object → 透传
|
|
259
|
+
let payload;
|
|
260
|
+
if (typeof cmdResult === 'string') {
|
|
261
|
+
payload = { kind: 'command.result', text: cmdResult };
|
|
262
|
+
}
|
|
263
|
+
else if (typeof cmdResult === 'object' && cmdResult !== null && 'kind' in cmdResult) {
|
|
264
|
+
payload = cmdResult;
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
// 不识别的返回值,按已处理但无回显处理
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
// 出站走 adapter.send 统一入口
|
|
271
|
+
const adapter = this.processor.getChannelInfo?.(channel)?.adapter;
|
|
272
|
+
const envelope = buildEnvelope({
|
|
273
|
+
taskId: `cmd-${randomBytes(5).toString('hex')}`,
|
|
274
|
+
channel,
|
|
275
|
+
channelId,
|
|
276
|
+
agentName: this.agentRegistry?.resolveByChannel(channel)?.name ?? '<unknown>',
|
|
277
|
+
chatmode: 'interactive',
|
|
278
|
+
replyContext,
|
|
279
|
+
});
|
|
235
280
|
try {
|
|
236
|
-
|
|
281
|
+
if (adapter?.send) {
|
|
282
|
+
await adapter.send(envelope, payload);
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
// 降级路径:渠道未实现 send 时回退到原有 sendReply(仅文本)
|
|
286
|
+
const fallbackText = ('text' in payload && typeof payload.text === 'string') ? payload.text : '';
|
|
287
|
+
if (fallbackText)
|
|
288
|
+
await sendReply(fallbackText);
|
|
289
|
+
}
|
|
237
290
|
}
|
|
238
291
|
catch (error) {
|
|
239
292
|
logger.error(`[${channel}] Failed to send command response:`, error);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { appendJsonl, chatDirPath } from '../session/session-fs-store.js';
|
|
3
|
+
import { logger } from '../../utils/logger.js';
|
|
4
|
+
const MESSAGE_LOG_FILE = 'messages.jsonl';
|
|
5
|
+
// 入方向去重:最近 msgId 缓存(LRU 风格,最多 200 条)
|
|
6
|
+
const recentMsgIds = new Set();
|
|
7
|
+
const DEDUP_MAX = 200;
|
|
8
|
+
function isDuplicate(msgId) {
|
|
9
|
+
if (!msgId)
|
|
10
|
+
return false;
|
|
11
|
+
if (recentMsgIds.has(msgId))
|
|
12
|
+
return true;
|
|
13
|
+
if (recentMsgIds.size >= DEDUP_MAX) {
|
|
14
|
+
const first = recentMsgIds.values().next().value;
|
|
15
|
+
recentMsgIds.delete(first);
|
|
16
|
+
}
|
|
17
|
+
recentMsgIds.add(msgId);
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
function formatTimestampMs(epochMs) {
|
|
21
|
+
const d = new Date(epochMs);
|
|
22
|
+
const yyyy = d.getFullYear();
|
|
23
|
+
const mo = String(d.getMonth() + 1).padStart(2, '0');
|
|
24
|
+
const dd = String(d.getDate()).padStart(2, '0');
|
|
25
|
+
const hh = String(d.getHours()).padStart(2, '0');
|
|
26
|
+
const mi = String(d.getMinutes()).padStart(2, '0');
|
|
27
|
+
const ss = String(d.getSeconds()).padStart(2, '0');
|
|
28
|
+
const ms = String(d.getMilliseconds()).padStart(3, '0');
|
|
29
|
+
return `${yyyy}-${mo}-${dd} ${hh}:${mi}:${ss}.${ms}`;
|
|
30
|
+
}
|
|
31
|
+
export function messageLogPath(chatDir) {
|
|
32
|
+
return path.join(chatDir, MESSAGE_LOG_FILE);
|
|
33
|
+
}
|
|
34
|
+
export function resolveChatDir(sessionsDir, channelType, channelId, selfId) {
|
|
35
|
+
return chatDirPath(sessionsDir, channelType, channelId, selfId);
|
|
36
|
+
}
|
|
37
|
+
export function appendMessageLog(chatDir, entry) {
|
|
38
|
+
if (entry.dir === 'in' && isDuplicate(entry.msgId)) {
|
|
39
|
+
logger.debug(`[MessageLog] Duplicate msgId skipped: ${entry.msgId}`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
appendJsonl(messageLogPath(chatDir), entry);
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
logger.warn(`[MessageLog] Failed to write message log: ${e}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function buildInboundEntry(opts) {
|
|
50
|
+
const ts = opts.timestamp || Date.now();
|
|
51
|
+
const isCommand = opts.content.startsWith('/');
|
|
52
|
+
return {
|
|
53
|
+
ts,
|
|
54
|
+
time: formatTimestampMs(ts),
|
|
55
|
+
dir: 'in',
|
|
56
|
+
from: opts.from,
|
|
57
|
+
to: opts.to,
|
|
58
|
+
chatType: opts.chatType,
|
|
59
|
+
groupId: opts.groupId ?? null,
|
|
60
|
+
msgId: opts.msgId ?? null,
|
|
61
|
+
msgType: isCommand ? 'command' : 'text',
|
|
62
|
+
content: opts.content,
|
|
63
|
+
replyTo: opts.replyTo ?? null,
|
|
64
|
+
agent: null,
|
|
65
|
+
model: null,
|
|
66
|
+
permMode: opts.permMode ?? null,
|
|
67
|
+
cmdParsed: isCommand ? opts.content.split(/\s/)[0] : null,
|
|
68
|
+
durationMs: null,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export function buildOutboundEntry(opts) {
|
|
72
|
+
const ts = opts.timestamp || Date.now();
|
|
73
|
+
return {
|
|
74
|
+
ts,
|
|
75
|
+
time: formatTimestampMs(ts),
|
|
76
|
+
dir: 'out',
|
|
77
|
+
from: opts.from,
|
|
78
|
+
to: opts.to,
|
|
79
|
+
chatType: opts.chatType,
|
|
80
|
+
groupId: opts.groupId ?? null,
|
|
81
|
+
msgId: opts.msgId ?? null,
|
|
82
|
+
msgType: 'text',
|
|
83
|
+
content: opts.content,
|
|
84
|
+
replyTo: opts.replyTo ?? null,
|
|
85
|
+
agent: opts.agent ?? null,
|
|
86
|
+
model: opts.model ?? null,
|
|
87
|
+
permMode: null,
|
|
88
|
+
cmdParsed: null,
|
|
89
|
+
durationMs: opts.durationMs ?? null,
|
|
90
|
+
numTurns: opts.numTurns ?? null,
|
|
91
|
+
usage: opts.usage ?? null,
|
|
92
|
+
};
|
|
93
|
+
}
|