evolclaw 3.1.0 → 3.1.1
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/dist/agents/claude-runner.js +40 -3
- package/dist/aun/msg/p2p.js +38 -0
- package/dist/channels/aun.js +80 -27
- package/dist/channels/dingtalk.js +1 -0
- package/dist/channels/feishu.js +10 -2
- package/dist/channels/qqbot.js +1 -0
- package/dist/channels/wechat.js +1 -0
- package/dist/channels/wecom.js +1 -0
- package/dist/cli/index.js +147 -29
- package/dist/cli/init.js +3 -4
- package/dist/cli/watch-msg.js +107 -30
- package/dist/config-store.js +45 -0
- package/dist/core/command-handler.js +86 -82
- package/dist/core/message/im-renderer.js +43 -4
- package/dist/core/message/message-bridge.js +4 -0
- package/dist/core/message/message-log.js +6 -1
- package/dist/core/message/message-processor.js +50 -34
- package/dist/core/relation/peer-identity.js +161 -0
- package/dist/core/session/session-manager.js +7 -3
- package/dist/core/trigger/manager.js +16 -0
- package/dist/core/trigger/parser.js +110 -0
- package/dist/core/trigger/scheduler.js +6 -0
- package/dist/index.js +49 -3
- package/dist/utils/error-utils.js +17 -13
- package/dist/utils/stats.js +216 -2
- package/kits/docs/evolclaw/MSG_PRIVATE.md +53 -6
- package/kits/rules/06-channel.md +30 -0
- package/package.json +2 -2
|
@@ -245,27 +245,29 @@ export function isRetryableError(error) {
|
|
|
245
245
|
return true;
|
|
246
246
|
return false;
|
|
247
247
|
}
|
|
248
|
-
export function getErrorMessage(error, terminalReason) {
|
|
248
|
+
export function getErrorMessage(error, terminalReason, includeEmoji = true) {
|
|
249
249
|
// terminalReason 提供更精确的错误提示(SDK 0.2.100+)
|
|
250
250
|
if (terminalReason) {
|
|
251
|
+
const prefix = includeEmoji ? '❌ ' : '';
|
|
252
|
+
const warnPrefix = includeEmoji ? '⚠️ ' : '';
|
|
251
253
|
switch (terminalReason) {
|
|
252
254
|
case 'max_turns':
|
|
253
|
-
return
|
|
255
|
+
return `${prefix}任务达到最大轮次限制,请简化需求或分步执行`;
|
|
254
256
|
case 'prompt_too_long':
|
|
255
|
-
return
|
|
257
|
+
return `${warnPrefix}输入过长,请精简提问或使用 /compact 压缩上下文`;
|
|
256
258
|
case 'rapid_refill_breaker':
|
|
257
|
-
return
|
|
259
|
+
return `${warnPrefix}API 限流中,请稍后重试`;
|
|
258
260
|
case 'context_compact_failed':
|
|
259
|
-
return
|
|
261
|
+
return `${warnPrefix}上下文过长,自动压缩失败,请手动输入 /compact 重试`;
|
|
260
262
|
case 'model_error':
|
|
261
|
-
return
|
|
263
|
+
return `${prefix}模型服务异常,请稍后重试`;
|
|
262
264
|
case 'tool_error':
|
|
263
|
-
return
|
|
265
|
+
return `${prefix}工具执行失败,请检查操作或重试`;
|
|
264
266
|
case 'permission_denied':
|
|
265
|
-
return
|
|
267
|
+
return `${prefix}权限被拒绝,操作已取消`;
|
|
266
268
|
case 'aborted_streaming':
|
|
267
269
|
case 'aborted_tools':
|
|
268
|
-
return
|
|
270
|
+
return `${prefix}任务已中断`;
|
|
269
271
|
}
|
|
270
272
|
}
|
|
271
273
|
// 回退到原有的错误消息匹配逻辑
|
|
@@ -275,15 +277,17 @@ export function getErrorMessage(error, terminalReason) {
|
|
|
275
277
|
if (rule?.message)
|
|
276
278
|
return rule.message;
|
|
277
279
|
// 内置兜底规则(结构性错误)
|
|
280
|
+
const warnPrefix = includeEmoji ? '⚠️ ' : '';
|
|
281
|
+
const errPrefix = includeEmoji ? '❌ ' : '';
|
|
278
282
|
if (msg.includes('CONTEXT_COMPACT_FAILED') || msg.includes('context_length_exceeded')
|
|
279
283
|
|| msg.includes('Context limit')) {
|
|
280
|
-
return
|
|
284
|
+
return `${warnPrefix}上下文过长,自动压缩失败,请手动输入 /compact 重试`;
|
|
281
285
|
}
|
|
282
286
|
if (msg.includes('401') || msg.includes('authentication_error')) {
|
|
283
|
-
return
|
|
287
|
+
return `${errPrefix}API Key 无效,请检查密钥配置。使用 /status 查看当前配置`;
|
|
284
288
|
}
|
|
285
289
|
if (msg.includes('timeout')) {
|
|
286
|
-
return
|
|
290
|
+
return `${warnPrefix}请求超时,请重试`;
|
|
287
291
|
}
|
|
288
|
-
return
|
|
292
|
+
return `${errPrefix}处理消息时出错,请稍后重试`;
|
|
289
293
|
}
|
package/dist/utils/stats.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
1
3
|
export class StatsCollector {
|
|
2
4
|
events = [];
|
|
3
5
|
startTime;
|
|
@@ -100,6 +102,71 @@ export class StatsCollector {
|
|
|
100
102
|
export class AidStatsCollector {
|
|
101
103
|
entries = new Map();
|
|
102
104
|
queueStatsProvider;
|
|
105
|
+
/** sessionId → 当前正在跑该 session 的 agent,task:started 写入,task:completed/error 清除 */
|
|
106
|
+
sessionToAgent = new Map();
|
|
107
|
+
constructor(eventBus) {
|
|
108
|
+
if (!eventBus)
|
|
109
|
+
return;
|
|
110
|
+
eventBus.subscribe('task:started', (event) => {
|
|
111
|
+
const e = event;
|
|
112
|
+
if (e.agentName)
|
|
113
|
+
this.onTaskStart(e.agentName, e.encrypt, e.chatmode);
|
|
114
|
+
if (e.agentName && e.sessionId)
|
|
115
|
+
this.sessionToAgent.set(e.sessionId, e.agentName);
|
|
116
|
+
});
|
|
117
|
+
eventBus.subscribe('task:completed', (event) => {
|
|
118
|
+
const e = event;
|
|
119
|
+
if (e.agentName)
|
|
120
|
+
this.onTaskEnd(e.agentName, 'completed', undefined, e.finalText, e.numTurns);
|
|
121
|
+
if (e.sessionId)
|
|
122
|
+
this.sessionToAgent.delete(e.sessionId);
|
|
123
|
+
});
|
|
124
|
+
eventBus.subscribe('task:error', (event) => {
|
|
125
|
+
const e = event;
|
|
126
|
+
if (e.agentName)
|
|
127
|
+
this.onTaskEnd(e.agentName, 'error', e.errorType);
|
|
128
|
+
if (e.sessionId)
|
|
129
|
+
this.sessionToAgent.delete(e.sessionId);
|
|
130
|
+
});
|
|
131
|
+
// thought.put 次数 + 最后一次 thought 文本
|
|
132
|
+
// 注意:thought.put 是 fire-and-forget async,可能在 task:completed 之后才到达,
|
|
133
|
+
// 所以同时累加到 currentTask(task 进行中)或 lastTaskEnd(task 已结束但 thought 属于它)
|
|
134
|
+
eventBus.subscribe('message:thought-put', (event) => {
|
|
135
|
+
const e = event;
|
|
136
|
+
if (!e.agentName)
|
|
137
|
+
return;
|
|
138
|
+
const entry = this.entries.get(e.agentName);
|
|
139
|
+
if (!entry)
|
|
140
|
+
return;
|
|
141
|
+
if (entry.currentTaskStartAt != null) {
|
|
142
|
+
// task 进行中
|
|
143
|
+
entry.currentTaskThoughtPutCount++;
|
|
144
|
+
if (e.text)
|
|
145
|
+
entry.currentTaskLastThoughtText = e.text;
|
|
146
|
+
}
|
|
147
|
+
else if (entry.lastTaskEnd) {
|
|
148
|
+
// task 已结束,回填到最近一次 task
|
|
149
|
+
entry.lastTaskEnd.thoughtPutCount++;
|
|
150
|
+
entry.lastTaskEnd.thoughtDuringTask = true;
|
|
151
|
+
if (e.text) {
|
|
152
|
+
const t = e.text.length > 100 ? e.text.slice(0, 100) + '…' : e.text;
|
|
153
|
+
entry.lastTaskEnd.lastThoughtText = t;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
// 工具调用次数(tool:use 事件)
|
|
158
|
+
eventBus.subscribe('tool:use', (event) => {
|
|
159
|
+
const e = event;
|
|
160
|
+
if (!e.sessionId)
|
|
161
|
+
return;
|
|
162
|
+
const agent = this.sessionToAgent.get(e.sessionId);
|
|
163
|
+
if (!agent)
|
|
164
|
+
return;
|
|
165
|
+
const entry = this.entries.get(agent);
|
|
166
|
+
if (entry && entry.currentTaskStartAt != null)
|
|
167
|
+
entry.currentTaskToolUseCount++;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
103
170
|
setQueueStatsProvider(provider) {
|
|
104
171
|
this.queueStatsProvider = provider;
|
|
105
172
|
}
|
|
@@ -119,19 +186,145 @@ export class AidStatsCollector {
|
|
|
119
186
|
lastSentAt: null,
|
|
120
187
|
lastReceivedText: null,
|
|
121
188
|
lastReceivedFrom: null,
|
|
189
|
+
lastReceivedEncrypt: null,
|
|
190
|
+
lastReceivedChatmode: null,
|
|
122
191
|
lastSentText: null,
|
|
123
192
|
lastSentTo: null,
|
|
193
|
+
lastSentEncrypt: null,
|
|
194
|
+
lastSentChatmode: null,
|
|
124
195
|
uniquePeers: new Set(),
|
|
196
|
+
currentTaskStartAt: null,
|
|
197
|
+
currentTaskReplyCount: 0,
|
|
198
|
+
currentTaskThoughtPutCount: 0,
|
|
199
|
+
currentTaskToolUseCount: 0,
|
|
200
|
+
currentTaskNumTurns: 0,
|
|
201
|
+
currentTaskLastThoughtText: null,
|
|
202
|
+
currentTaskSessionId: null,
|
|
203
|
+
currentTaskChatmode: null,
|
|
204
|
+
currentTaskEncrypt: null,
|
|
205
|
+
lastTaskEnd: undefined,
|
|
125
206
|
};
|
|
126
207
|
this.entries.set(aid, entry);
|
|
127
208
|
}
|
|
128
209
|
return entry;
|
|
129
210
|
}
|
|
211
|
+
sessionsDir;
|
|
212
|
+
setSessionsDir(dir) {
|
|
213
|
+
this.sessionsDir = dir;
|
|
214
|
+
}
|
|
215
|
+
onTaskStart(aid, encrypt, chatmode) {
|
|
216
|
+
const entry = this.getOrCreate(aid);
|
|
217
|
+
entry.currentTaskStartAt = Date.now();
|
|
218
|
+
entry.currentTaskReplyCount = 0;
|
|
219
|
+
entry.currentTaskThoughtPutCount = 0;
|
|
220
|
+
entry.currentTaskToolUseCount = 0;
|
|
221
|
+
entry.currentTaskNumTurns = 0;
|
|
222
|
+
entry.currentTaskLastThoughtText = null;
|
|
223
|
+
entry.currentTaskChatmode = chatmode ?? null;
|
|
224
|
+
entry.currentTaskEncrypt = encrypt ?? null;
|
|
225
|
+
}
|
|
226
|
+
onTaskEnd(aid, status, errorType, finalText, numTurns) {
|
|
227
|
+
const entry = this.getOrCreate(aid);
|
|
228
|
+
const startedAt = entry.currentTaskStartAt;
|
|
229
|
+
const taskEndTs = Date.now();
|
|
230
|
+
// 先用内存计数写入初始值(立即可用)
|
|
231
|
+
const buildTaskEnd = (msgCount, thoughtCount, lastThought) => ({
|
|
232
|
+
ts: taskEndTs,
|
|
233
|
+
status,
|
|
234
|
+
errorType,
|
|
235
|
+
sentDuringTask: msgCount > 0,
|
|
236
|
+
thoughtDuringTask: thoughtCount > 0,
|
|
237
|
+
lastThoughtText: lastThought,
|
|
238
|
+
replyCount: msgCount,
|
|
239
|
+
thoughtPutCount: thoughtCount,
|
|
240
|
+
toolUseCount: entry.currentTaskToolUseCount,
|
|
241
|
+
numTurns: numTurns ?? entry.currentTaskNumTurns,
|
|
242
|
+
finalText: finalText ? (finalText.length > 100 ? finalText.slice(0, 100) + '…' : finalText) : undefined,
|
|
243
|
+
chatmode: entry.currentTaskChatmode ?? undefined,
|
|
244
|
+
encrypt: entry.currentTaskEncrypt ?? undefined,
|
|
245
|
+
});
|
|
246
|
+
entry.lastTaskEnd = buildTaskEnd(entry.currentTaskReplyCount, entry.currentTaskThoughtPutCount, entry.currentTaskLastThoughtText ?? undefined);
|
|
247
|
+
// 500ms 后从 jsonl 重新统计(覆盖 thought.put 异步延迟问题)
|
|
248
|
+
if (this.sessionsDir && startedAt != null) {
|
|
249
|
+
const sessionsDir = this.sessionsDir;
|
|
250
|
+
const toolUseCount = entry.currentTaskToolUseCount;
|
|
251
|
+
const resolvedNumTurns = numTurns ?? entry.currentTaskNumTurns;
|
|
252
|
+
const chatmode = entry.currentTaskChatmode;
|
|
253
|
+
const encrypt = entry.currentTaskEncrypt;
|
|
254
|
+
setTimeout(() => {
|
|
255
|
+
try {
|
|
256
|
+
const { chatDirPath } = require('../core/session/session-fs-store.js');
|
|
257
|
+
// 找该 aid 下所有 peer 的 messages.jsonl,统计 ts >= startedAt 的出站条目
|
|
258
|
+
const aidDir = path.join(sessionsDir, 'aun', aid.replace(/[/%\\:*?"<>|]/g, ch => '%' + ch.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0')));
|
|
259
|
+
if (!fs.existsSync(aidDir))
|
|
260
|
+
return;
|
|
261
|
+
let msgCount = 0, thoughtCount = 0;
|
|
262
|
+
let lastThoughtText;
|
|
263
|
+
let lastMsgText;
|
|
264
|
+
for (const peerDir of fs.readdirSync(aidDir, { withFileTypes: true })) {
|
|
265
|
+
if (!peerDir.isDirectory() || peerDir.name.startsWith('_'))
|
|
266
|
+
continue;
|
|
267
|
+
const msgFile = path.join(aidDir, peerDir.name, 'messages.jsonl');
|
|
268
|
+
if (!fs.existsSync(msgFile))
|
|
269
|
+
continue;
|
|
270
|
+
const lines = fs.readFileSync(msgFile, 'utf-8').split('\n').filter(Boolean);
|
|
271
|
+
for (const line of lines) {
|
|
272
|
+
try {
|
|
273
|
+
const rec = JSON.parse(line);
|
|
274
|
+
if (rec.dir !== 'out' || rec.ts < startedAt || rec.ts > taskEndTs + 2000)
|
|
275
|
+
continue;
|
|
276
|
+
if (rec.msgType === 'thought') {
|
|
277
|
+
thoughtCount++;
|
|
278
|
+
if (rec.content)
|
|
279
|
+
lastThoughtText = rec.content.length > 100 ? rec.content.slice(0, 100) + '…' : rec.content;
|
|
280
|
+
}
|
|
281
|
+
else if (rec.msgType === 'text') {
|
|
282
|
+
msgCount++;
|
|
283
|
+
if (rec.content)
|
|
284
|
+
lastMsgText = rec.content.length > 100 ? rec.content.slice(0, 100) + '…' : rec.content;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
catch { }
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const currentEntry = this.entries.get(aid);
|
|
291
|
+
if (currentEntry?.lastTaskEnd?.ts === taskEndTs) {
|
|
292
|
+
currentEntry.lastTaskEnd = {
|
|
293
|
+
ts: taskEndTs,
|
|
294
|
+
status,
|
|
295
|
+
errorType,
|
|
296
|
+
sentDuringTask: msgCount > 0,
|
|
297
|
+
thoughtDuringTask: thoughtCount > 0,
|
|
298
|
+
lastThoughtText: lastThoughtText,
|
|
299
|
+
replyCount: msgCount,
|
|
300
|
+
thoughtPutCount: thoughtCount,
|
|
301
|
+
toolUseCount,
|
|
302
|
+
numTurns: resolvedNumTurns,
|
|
303
|
+
finalText: finalText ? (finalText.length > 100 ? finalText.slice(0, 100) + '…' : finalText) : undefined,
|
|
304
|
+
chatmode: chatmode ?? undefined,
|
|
305
|
+
encrypt: encrypt ?? undefined,
|
|
306
|
+
};
|
|
307
|
+
// 更新 lastSentText 为最后一条 msg(如果有)
|
|
308
|
+
if (lastMsgText && msgCount > 0) {
|
|
309
|
+
currentEntry.lastSentText = lastMsgText;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
catch { }
|
|
314
|
+
}, 500);
|
|
315
|
+
}
|
|
316
|
+
entry.currentTaskStartAt = null;
|
|
317
|
+
entry.currentTaskReplyCount = 0;
|
|
318
|
+
entry.currentTaskThoughtPutCount = 0;
|
|
319
|
+
entry.currentTaskToolUseCount = 0;
|
|
320
|
+
entry.currentTaskNumTurns = 0;
|
|
321
|
+
entry.currentTaskLastThoughtText = null;
|
|
322
|
+
}
|
|
130
323
|
setSelfName(aid, name) {
|
|
131
324
|
const entry = this.getOrCreate(aid);
|
|
132
325
|
entry.selfName = name;
|
|
133
326
|
}
|
|
134
|
-
recordInbound(aid, fromPeer, byteLength, text, isSystem = false) {
|
|
327
|
+
recordInbound(aid, fromPeer, byteLength, text, isSystem = false, encrypt, chatmode) {
|
|
135
328
|
const entry = this.getOrCreate(aid);
|
|
136
329
|
if (isSystem) {
|
|
137
330
|
entry.systemReceived++;
|
|
@@ -142,11 +335,15 @@ export class AidStatsCollector {
|
|
|
142
335
|
entry.lastReceivedFrom = fromPeer;
|
|
143
336
|
if (text)
|
|
144
337
|
entry.lastReceivedText = text.length > 100 ? text.slice(0, 100) + '…' : text;
|
|
338
|
+
if (encrypt != null)
|
|
339
|
+
entry.lastReceivedEncrypt = encrypt;
|
|
340
|
+
if (chatmode)
|
|
341
|
+
entry.lastReceivedChatmode = chatmode;
|
|
145
342
|
}
|
|
146
343
|
entry.bytesReceived += byteLength;
|
|
147
344
|
entry.uniquePeers.add(fromPeer);
|
|
148
345
|
}
|
|
149
|
-
recordOutbound(aid, toPeer, byteLength, text, isSystem = false) {
|
|
346
|
+
recordOutbound(aid, toPeer, byteLength, text, isSystem = false, encrypt, chatmode) {
|
|
150
347
|
const entry = this.getOrCreate(aid);
|
|
151
348
|
if (isSystem) {
|
|
152
349
|
entry.systemSent++;
|
|
@@ -157,6 +354,18 @@ export class AidStatsCollector {
|
|
|
157
354
|
entry.lastSentTo = toPeer;
|
|
158
355
|
if (text)
|
|
159
356
|
entry.lastSentText = text.length > 100 ? text.slice(0, 100) + '…' : text;
|
|
357
|
+
if (encrypt != null)
|
|
358
|
+
entry.lastSentEncrypt = encrypt;
|
|
359
|
+
if (chatmode)
|
|
360
|
+
entry.lastSentChatmode = chatmode;
|
|
361
|
+
// 累计当前 task 的回复数
|
|
362
|
+
if (entry.currentTaskStartAt != null) {
|
|
363
|
+
entry.currentTaskReplyCount++;
|
|
364
|
+
if (chatmode)
|
|
365
|
+
entry.currentTaskChatmode = chatmode;
|
|
366
|
+
if (encrypt != null)
|
|
367
|
+
entry.currentTaskEncrypt = encrypt;
|
|
368
|
+
}
|
|
160
369
|
}
|
|
161
370
|
entry.bytesSent += byteLength;
|
|
162
371
|
entry.uniquePeers.add(toPeer);
|
|
@@ -180,11 +389,16 @@ export class AidStatsCollector {
|
|
|
180
389
|
lastSentAt: entry.lastSentAt,
|
|
181
390
|
lastReceivedText: entry.lastReceivedText,
|
|
182
391
|
lastReceivedFrom: entry.lastReceivedFrom,
|
|
392
|
+
lastReceivedEncrypt: entry.lastReceivedEncrypt,
|
|
393
|
+
lastReceivedChatmode: entry.lastReceivedChatmode,
|
|
183
394
|
lastSentText: entry.lastSentText,
|
|
184
395
|
lastSentTo: entry.lastSentTo,
|
|
396
|
+
lastSentEncrypt: entry.lastSentEncrypt,
|
|
397
|
+
lastSentChatmode: entry.lastSentChatmode,
|
|
185
398
|
uniquePeerCount: entry.uniquePeers.size,
|
|
186
399
|
processing: queueStats.processing,
|
|
187
400
|
queued: queueStats.queued,
|
|
401
|
+
lastTaskEnd: entry.lastTaskEnd,
|
|
188
402
|
});
|
|
189
403
|
}
|
|
190
404
|
return out;
|
|
@@ -1,25 +1,72 @@
|
|
|
1
1
|
# 私聊消息命令
|
|
2
2
|
|
|
3
|
-
<!-- TODO: 填充私聊消息命令详细参考 -->
|
|
4
|
-
|
|
5
3
|
## 发送消息
|
|
6
4
|
|
|
5
|
+
### 以指定 AID 发送(首选)
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# 明文
|
|
9
|
+
ec msg send <from-aid> <to-aid> "<message>"
|
|
10
|
+
|
|
11
|
+
# 密文(E2EE)
|
|
12
|
+
ec msg send <from-aid> <to-aid> "<message>" --encrypt
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### 发送文件
|
|
16
|
+
|
|
7
17
|
```bash
|
|
8
|
-
|
|
18
|
+
ec msg send <from-aid> <to-aid> --file <path>
|
|
19
|
+
ec msg send <from-aid> <to-aid> --file <path> --as image
|
|
20
|
+
ec msg send <from-aid> <to-aid> --file <path> --encrypt
|
|
9
21
|
```
|
|
10
22
|
|
|
23
|
+
`--as` 可选值:`image` | `video` | `voice` | `file`(默认按扩展名推断)
|
|
24
|
+
|
|
11
25
|
## 拉取消息
|
|
12
26
|
|
|
13
27
|
```bash
|
|
14
|
-
|
|
28
|
+
ec msg pull <self-aid> --app <app-name>
|
|
29
|
+
ec msg pull <self-aid> --app <app-name> --after-seq <N> --limit <N>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 确认消息已读
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
ec msg ack <self-aid> <seq> --app <app-name>
|
|
15
36
|
```
|
|
16
37
|
|
|
17
|
-
|
|
38
|
+
`--app` 必须传,否则会污染 daemon 游标。
|
|
39
|
+
|
|
40
|
+
## 撤回消息
|
|
18
41
|
|
|
19
42
|
```bash
|
|
20
|
-
|
|
43
|
+
ec msg recall <self-aid> <message-id>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 查询在线状态
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
ec msg online <self-aid> <target-aid>
|
|
21
50
|
```
|
|
22
51
|
|
|
23
52
|
## 自主回复策略
|
|
24
53
|
|
|
25
54
|
收到消息 ≠ 必须回复。是否回复、怎么回复、何时回复由 agent 自主决定。
|
|
55
|
+
|
|
56
|
+
加密策略:
|
|
57
|
+
- 对端发来密文消息时,回复也应使用 `--encrypt`(保持对话加密一致性)
|
|
58
|
+
- 对端发来明文消息时,默认明文回复
|
|
59
|
+
|
|
60
|
+
## 在当前会话中快速回复(备选)
|
|
61
|
+
|
|
62
|
+
仅当无法使用 `ec msg send` 时(如不知道自己的 AID),可用 `ec ctl send`:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# 明文
|
|
66
|
+
ec ctl send "<text>"
|
|
67
|
+
|
|
68
|
+
# 密文
|
|
69
|
+
ec ctl send --encrypt "<text>"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`ec ctl send` 自动继承当前会话的 AID 和对端,无需指定。
|
package/kits/rules/06-channel.md
CHANGED
|
@@ -13,6 +13,36 @@
|
|
|
13
13
|
|
|
14
14
|
与其他主体通信时,**必须调用 CLI 命令**发消息,不要把输出当成发送给对方的内容。
|
|
15
15
|
|
|
16
|
+
### 必须使用 `ec msg send`(首选)
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# 明文
|
|
20
|
+
ec msg send <self-aid> <to-aid> "<text>"
|
|
21
|
+
|
|
22
|
+
# 密文
|
|
23
|
+
ec msg send <self-aid> <to-aid> "<text>" --encrypt
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`<self-aid>` 是注入上下文里的 selfAid,`<to-aid>` 是 peerKey 解析出的对端 AID。
|
|
27
|
+
|
|
28
|
+
### 仅在无法获取 selfAid 时才用 `ec ctl send`
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
ec ctl send "<text>"
|
|
32
|
+
ec ctl send --encrypt "<text>"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 加密策略
|
|
36
|
+
|
|
37
|
+
对端发来密文消息时回复必须加密;明文消息默认明文回复。
|
|
38
|
+
|
|
39
|
+
### 命令返回值
|
|
40
|
+
|
|
41
|
+
- 成功:`ok` 或包含 `✓ 已发送 ...` 的输出(exit 0)
|
|
42
|
+
- 失败:`✗ ...` 错误信息(exit 非零)
|
|
43
|
+
|
|
44
|
+
发送成功后**继续后续处理**。一次任务可能发 0 到多条消息,不要因为看到"已发送"就反复发送同一条消息。
|
|
45
|
+
|
|
16
46
|
不同渠道有不同的命令行工具,使用方式参见各渠道文档。
|
|
17
47
|
|
|
18
48
|
## Agent 管理命令
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "evolclaw",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"description": "Lightweight AI Agent gateway connecting Claude Agent SDK to messaging channels (Feishu, ACP) with multi-project session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"prepublishOnly": "npm run build && npm test"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@agentunion/fastaun": "^0.3.
|
|
29
|
+
"@agentunion/fastaun": "^0.3.2",
|
|
30
30
|
"@anthropic-ai/claude-agent-sdk": "^0.2.100",
|
|
31
31
|
"cron-parser": "^5.5.0",
|
|
32
32
|
"image-type": "^6.0.0",
|