evolclaw 3.1.0 → 3.1.2
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/CHANGELOG.md +407 -0
- package/README.md +1 -1
- package/SKILLS.md +311 -0
- package/dist/agents/claude-runner.js +40 -3
- package/dist/aun/aid/agentmd.js +7 -6
- package/dist/aun/aid/client.js +5 -11
- package/dist/aun/aid/identity.js +32 -13
- package/dist/aun/msg/group.js +1 -0
- package/dist/aun/msg/p2p.js +51 -0
- package/dist/aun/msg/upload.js +57 -18
- package/dist/channels/aun.js +124 -50
- package/dist/channels/dingtalk.js +2 -0
- package/dist/channels/feishu.js +15 -6
- package/dist/channels/qqbot.js +2 -0
- package/dist/channels/wechat.js +2 -0
- package/dist/channels/wecom.js +2 -0
- package/dist/cli/agent.js +130 -35
- package/dist/cli/index.js +221 -48
- package/dist/cli/init-channel.js +4 -2
- package/dist/cli/init.js +44 -23
- package/dist/cli/watch-msg.js +109 -30
- package/dist/config-store.js +67 -1
- package/dist/core/channel-loader.js +4 -4
- package/dist/core/command-handler.js +95 -84
- package/dist/core/evolagent-registry.js +45 -9
- package/dist/core/evolagent.js +4 -4
- package/dist/core/message/im-renderer.js +47 -8
- package/dist/core/message/message-bridge.js +30 -1
- package/dist/core/message/message-log.js +6 -1
- package/dist/core/message/message-processor.js +29 -35
- package/dist/core/relation/peer-identity.js +161 -0
- package/dist/core/session/session-fs-store.js +23 -0
- package/dist/core/session/session-manager.js +11 -4
- 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 +64 -20
- package/dist/paths.js +35 -0
- package/dist/utils/cross-platform.js +2 -1
- package/dist/utils/error-utils.js +17 -13
- package/dist/utils/stats.js +216 -2
- package/kits/docs/INDEX.md +6 -0
- package/kits/docs/evolclaw/MSG_PRIVATE.md +53 -6
- package/kits/rules/06-channel.md +30 -0
- package/package.json +6 -3
package/dist/channels/feishu.js
CHANGED
|
@@ -219,7 +219,7 @@ export class FeishuChannel {
|
|
|
219
219
|
const imageData = await this.downloadAndSaveImage(imageKey, msg.chat_id, msg.message_id, projectPath);
|
|
220
220
|
if (imageData) {
|
|
221
221
|
const allImages = [...quotedImages, imageData];
|
|
222
|
-
const prompt = quotedText + '
|
|
222
|
+
const prompt = quotedText + '用户发送了一张图片,请结合上下文理解用户意图并回应。';
|
|
223
223
|
await this.messageHandler({ channelId: msg.chat_id, content: prompt, images: allImages, peerId, peerName, messageId: msg.message_id, threadId, rootId, chatType });
|
|
224
224
|
}
|
|
225
225
|
else {
|
|
@@ -329,12 +329,11 @@ export class FeishuChannel {
|
|
|
329
329
|
}
|
|
330
330
|
logger.info(`[Feishu] CommandCard trigger: command=${value._command}, operator=${operatorId}`);
|
|
331
331
|
if (this.messageHandler) {
|
|
332
|
-
//
|
|
333
|
-
|
|
332
|
+
// 卡片回调不传 chatType——oc_ 前缀不区分群聊/单聊,
|
|
333
|
+
// 由 ensureSession 从已有 session 中继承正确的 chatType
|
|
334
334
|
await this.messageHandler({
|
|
335
335
|
channelId: chatId,
|
|
336
336
|
content: value._command,
|
|
337
|
-
chatType,
|
|
338
337
|
peerId: operatorId,
|
|
339
338
|
messageId: `card-trigger-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
340
339
|
source: 'card-trigger',
|
|
@@ -573,7 +572,14 @@ export class FeishuChannel {
|
|
|
573
572
|
const truncated = content.slice(0, 28000) + '\n\n⚠️ 消息过长,已截断';
|
|
574
573
|
return this.sendMessage(chatId, truncated, options);
|
|
575
574
|
}
|
|
576
|
-
|
|
575
|
+
const respData = error?.response?.data;
|
|
576
|
+
const code = respData?.code;
|
|
577
|
+
logger.error('[Feishu] Failed to send message:', respData ? JSON.stringify(respData) : error?.message ?? error);
|
|
578
|
+
// post 格式相关错误(400/230001):降级为纯文本重试
|
|
579
|
+
if (!options?.forceText && (error?.response?.status === 400 || code === 230001)) {
|
|
580
|
+
logger.warn('[Feishu] Retrying as plain text (forceText)');
|
|
581
|
+
return this.sendMessage(chatId, content, { ...options, forceText: true });
|
|
582
|
+
}
|
|
577
583
|
throw error;
|
|
578
584
|
}
|
|
579
585
|
}
|
|
@@ -1320,6 +1326,7 @@ export class FeishuChannelPlugin {
|
|
|
1320
1326
|
});
|
|
1321
1327
|
const adapter = {
|
|
1322
1328
|
channelName: inst.name,
|
|
1329
|
+
channelKey: inst.name,
|
|
1323
1330
|
capabilities: { file: true, image: true, interaction: true, markdown: true, thought: false, status: true },
|
|
1324
1331
|
send: async (envelope, payload) => {
|
|
1325
1332
|
const ctx = envelope.replyContext;
|
|
@@ -1357,6 +1364,7 @@ export class FeishuChannelPlugin {
|
|
|
1357
1364
|
case 'status.interrupted':
|
|
1358
1365
|
case 'status.error':
|
|
1359
1366
|
case 'status.timeout':
|
|
1367
|
+
case 'status.progress':
|
|
1360
1368
|
// Feishu 通过 acknowledge (✓ 表情) 表达状态,由 channel 自行处理
|
|
1361
1369
|
return;
|
|
1362
1370
|
case 'interaction':
|
|
@@ -1416,7 +1424,8 @@ export class FeishuChannelPlugin {
|
|
|
1416
1424
|
registerBridge(bridge, channelType) {
|
|
1417
1425
|
bridge.register(adapter.channelName, (handler) => channel.onMessage(async ({ channelId: chatId, content, images, peerId, peerName, messageId, mentions, threadId, rootId, chatType, source }) => {
|
|
1418
1426
|
await handler({
|
|
1419
|
-
channel: adapter.channelName, channelType, channelId: chatId, content, images,
|
|
1427
|
+
channel: adapter.channelName, channelType, channelId: chatId, content, images,
|
|
1428
|
+
chatType: chatType || 'private',
|
|
1420
1429
|
peerId: peerId || '', peerName, messageId, mentions, threadId,
|
|
1421
1430
|
replyContext: threadId ? { replyToMessageId: rootId ?? threadId, replyInThread: true } : undefined,
|
|
1422
1431
|
source,
|
package/dist/channels/qqbot.js
CHANGED
|
@@ -334,6 +334,7 @@ export class QQBotChannelPlugin {
|
|
|
334
334
|
});
|
|
335
335
|
const adapter = {
|
|
336
336
|
channelName: inst.name,
|
|
337
|
+
channelKey: inst.name,
|
|
337
338
|
capabilities: { file: true, image: true, interaction: false, markdown: true, thought: false, status: false },
|
|
338
339
|
send: async (envelope, payload) => {
|
|
339
340
|
const ctx = envelope.replyContext;
|
|
@@ -369,6 +370,7 @@ export class QQBotChannelPlugin {
|
|
|
369
370
|
case 'status.interrupted':
|
|
370
371
|
case 'status.error':
|
|
371
372
|
case 'status.timeout':
|
|
373
|
+
case 'status.progress':
|
|
372
374
|
case 'custom':
|
|
373
375
|
return;
|
|
374
376
|
default:
|
package/dist/channels/wechat.js
CHANGED
|
@@ -730,6 +730,7 @@ export class WechatChannelPlugin {
|
|
|
730
730
|
});
|
|
731
731
|
const adapter = {
|
|
732
732
|
channelName: inst.name,
|
|
733
|
+
channelKey: inst.name,
|
|
733
734
|
capabilities: { file: false, image: false, interaction: false, markdown: false, thought: false, status: true },
|
|
734
735
|
send: async (envelope, payload) => {
|
|
735
736
|
const channelId = envelope.channelId;
|
|
@@ -766,6 +767,7 @@ export class WechatChannelPlugin {
|
|
|
766
767
|
case 'status.interrupted':
|
|
767
768
|
case 'status.error':
|
|
768
769
|
case 'status.timeout':
|
|
770
|
+
case 'status.progress':
|
|
769
771
|
case 'custom':
|
|
770
772
|
return;
|
|
771
773
|
default:
|
package/dist/channels/wecom.js
CHANGED
|
@@ -490,6 +490,7 @@ export class WecomChannelPlugin {
|
|
|
490
490
|
});
|
|
491
491
|
const adapter = {
|
|
492
492
|
channelName: inst.name,
|
|
493
|
+
channelKey: inst.name,
|
|
493
494
|
capabilities: { file: true, image: true, interaction: false, markdown: true, thought: false, status: false },
|
|
494
495
|
send: async (envelope, payload) => {
|
|
495
496
|
const ctx = envelope.replyContext;
|
|
@@ -525,6 +526,7 @@ export class WecomChannelPlugin {
|
|
|
525
526
|
case 'status.interrupted':
|
|
526
527
|
case 'status.error':
|
|
527
528
|
case 'status.timeout':
|
|
529
|
+
case 'status.progress':
|
|
528
530
|
case 'custom':
|
|
529
531
|
return;
|
|
530
532
|
default:
|
package/dist/cli/agent.js
CHANGED
|
@@ -239,22 +239,30 @@ export async function agentShow(aid) {
|
|
|
239
239
|
}
|
|
240
240
|
export async function agentCreateInteractive(opts = {}) {
|
|
241
241
|
const p = resolvePaths();
|
|
242
|
-
const
|
|
242
|
+
const ownRl = !opts.rl;
|
|
243
|
+
const rl = opts.rl ?? readline.createInterface({
|
|
243
244
|
input: opts.stdin || process.stdin,
|
|
244
245
|
output: opts.stdout || process.stdout,
|
|
245
246
|
});
|
|
246
247
|
const ask = (q) => new Promise(r => rl.question(q, r));
|
|
247
248
|
try {
|
|
249
|
+
const { isValidAid, aidCreate } = await import('../aun/aid/index.js');
|
|
248
250
|
const aidPrompt = opts.suggestedName
|
|
249
251
|
? `AID [${opts.suggestedName}]: `
|
|
250
252
|
: 'AID (e.g. mybot.agentid.pub): ';
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
253
|
+
let aid = '';
|
|
254
|
+
while (!aid) {
|
|
255
|
+
const aidInput = (await ask(aidPrompt)).trim();
|
|
256
|
+
const candidate = aidInput || opts.suggestedName;
|
|
257
|
+
if (!candidate) {
|
|
258
|
+
console.log(' ⚠ AID is required.');
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (!isValidAid(candidate)) {
|
|
262
|
+
console.log(` ⚠ Invalid AID "${candidate}": must be a valid multi-level domain (e.g. mybot.agentid.pub)`);
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
aid = candidate;
|
|
258
266
|
}
|
|
259
267
|
const agentDirPath = path.join(p.agentsDir, aid);
|
|
260
268
|
const configExists = fs.existsSync(path.join(agentDirPath, 'config.json'));
|
|
@@ -311,26 +319,48 @@ export async function agentCreateInteractive(opts = {}) {
|
|
|
311
319
|
return { ok: false, error: `No baseagent CLI detected on PATH. Install one of: ${BASEAGENT_CANDIDATES.join('/')}` };
|
|
312
320
|
}
|
|
313
321
|
const defaultBa = pickDefaultBaseagent(available);
|
|
314
|
-
let baseagent
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
322
|
+
let baseagent;
|
|
323
|
+
if (available.length === 1) {
|
|
324
|
+
console.log(` Baseagent: ${defaultBa}`);
|
|
325
|
+
baseagent = defaultBa;
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
let chosen = null;
|
|
329
|
+
while (chosen === null) {
|
|
330
|
+
const input = (await ask(`Baseagent (${available.join('/')}) [${defaultBa}]: `)).trim() || defaultBa;
|
|
331
|
+
if (!BASEAGENT_CANDIDATES.includes(input)) {
|
|
332
|
+
console.log(` Invalid choice. Options: ${BASEAGENT_CANDIDATES.join('/')}`);
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
if (!available.includes(input)) {
|
|
336
|
+
console.log(` ${input} not detected on PATH. Available: ${available.join('/')}`);
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
chosen = input;
|
|
340
|
+
}
|
|
341
|
+
baseagent = chosen;
|
|
342
|
+
}
|
|
343
|
+
// Owner
|
|
344
|
+
let owner;
|
|
345
|
+
while (true) {
|
|
346
|
+
const ownerInput = (await ask('Owner AID (leave empty for auto-bind on first message): ')).trim();
|
|
347
|
+
if (!ownerInput) {
|
|
348
|
+
owner = undefined;
|
|
349
|
+
break;
|
|
320
350
|
}
|
|
321
|
-
if (!
|
|
322
|
-
console.log(` ${
|
|
351
|
+
if (!isValidAid(ownerInput)) {
|
|
352
|
+
console.log(` ⚠ Invalid Owner AID "${ownerInput}": must be a valid multi-level domain (e.g. alice.agentid.pub)`);
|
|
323
353
|
continue;
|
|
324
354
|
}
|
|
325
|
-
|
|
355
|
+
owner = ownerInput;
|
|
356
|
+
break;
|
|
326
357
|
}
|
|
327
|
-
// Owner
|
|
328
|
-
const owner = (await ask('Owner AID (leave empty for auto-bind on first message): ')).trim() || undefined;
|
|
329
358
|
// Name + description for agent.md
|
|
330
359
|
const defaultName = aid.split('.')[0];
|
|
331
360
|
const agentName = (await ask(`Display name [${defaultName}]: `)).trim() || defaultName;
|
|
332
361
|
const agentDescription = (await ask('Description (optional): ')).trim() || '';
|
|
333
|
-
|
|
362
|
+
if (ownRl)
|
|
363
|
+
rl.close();
|
|
334
364
|
const agentConfig = {
|
|
335
365
|
$schema_version: CONFIG_SCHEMA_VERSION,
|
|
336
366
|
aid,
|
|
@@ -359,31 +389,64 @@ export async function agentCreateInteractive(opts = {}) {
|
|
|
359
389
|
const agentMdPath = path.join(aunPath, 'AIDs', aid, 'agent.md');
|
|
360
390
|
fs.mkdirSync(path.dirname(agentMdPath), { recursive: true });
|
|
361
391
|
fs.writeFileSync(agentMdPath, content, 'utf-8');
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
392
|
+
// Upload with retry (3 attempts, 2s delay between retries)
|
|
393
|
+
const MAX_ATTEMPTS = 3;
|
|
394
|
+
const RETRY_DELAY_MS = 2000;
|
|
395
|
+
let lastError;
|
|
396
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
397
|
+
try {
|
|
398
|
+
if (attempt > 1) {
|
|
399
|
+
process.stdout.write(` ↻ agent.md 上传重试 (${attempt}/${MAX_ATTEMPTS})...\n`);
|
|
400
|
+
await new Promise(r => setTimeout(r, RETRY_DELAY_MS));
|
|
401
|
+
}
|
|
402
|
+
await agentmdPut(content, { aid, aunPath });
|
|
403
|
+
agentmdUploaded = true;
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
catch (e) {
|
|
407
|
+
lastError = e;
|
|
408
|
+
}
|
|
365
409
|
}
|
|
366
|
-
|
|
367
|
-
console.warn(` ⚠ agent.md upload failed: ${
|
|
410
|
+
if (!agentmdUploaded) {
|
|
411
|
+
console.warn(` ⚠ agent.md upload failed: ${lastError?.message || lastError}`);
|
|
368
412
|
console.warn(` → Retry later with: evolclaw aid agentmd put ${aid}`);
|
|
369
413
|
}
|
|
414
|
+
// Yield to allow the SDK WebSocket to fully close before IPC
|
|
415
|
+
await new Promise(r => setTimeout(r, 0));
|
|
370
416
|
}
|
|
371
417
|
catch (e) {
|
|
372
418
|
console.warn(` ⚠ agent.md generation failed: ${e?.message || e}`);
|
|
373
419
|
}
|
|
420
|
+
// Attempt hot-load via IPC (if daemon is running)
|
|
421
|
+
let hotLoaded = false;
|
|
422
|
+
let hotLoadError;
|
|
423
|
+
try {
|
|
424
|
+
const ipcResult = await ipcQuery(p.socket, { type: 'evolagent.load', aid });
|
|
425
|
+
if (ipcResult?.ok) {
|
|
426
|
+
hotLoaded = true;
|
|
427
|
+
}
|
|
428
|
+
else if (ipcResult) {
|
|
429
|
+
hotLoadError = ipcResult.error;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
catch { /* daemon not running */ }
|
|
374
433
|
return {
|
|
375
434
|
ok: true,
|
|
376
435
|
aid,
|
|
377
436
|
configPath: toPosix(path.join(agentDirPath, 'config.json')),
|
|
378
437
|
aidCreated,
|
|
379
438
|
agentmdUploaded,
|
|
439
|
+
hotLoaded,
|
|
440
|
+
hotLoadError,
|
|
380
441
|
};
|
|
381
442
|
}
|
|
382
443
|
finally {
|
|
383
|
-
|
|
384
|
-
|
|
444
|
+
if (ownRl) {
|
|
445
|
+
try {
|
|
446
|
+
rl.close();
|
|
447
|
+
}
|
|
448
|
+
catch { /* ignore */ }
|
|
385
449
|
}
|
|
386
|
-
catch { /* ignore */ }
|
|
387
450
|
}
|
|
388
451
|
}
|
|
389
452
|
export async function agentCreateNonInteractive(opts) {
|
|
@@ -482,24 +545,51 @@ export async function agentCreateNonInteractive(opts) {
|
|
|
482
545
|
const agentMdPath = path.join(aunPath, 'AIDs', opts.aid, 'agent.md');
|
|
483
546
|
fs.mkdirSync(path.dirname(agentMdPath), { recursive: true });
|
|
484
547
|
fs.writeFileSync(agentMdPath, content, 'utf-8');
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
548
|
+
const MAX_ATTEMPTS = 3;
|
|
549
|
+
const RETRY_DELAY_MS = 2000;
|
|
550
|
+
let lastError;
|
|
551
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
552
|
+
try {
|
|
553
|
+
if (attempt > 1)
|
|
554
|
+
await new Promise(r => setTimeout(r, RETRY_DELAY_MS));
|
|
555
|
+
await agentmdPut(content, { aid: opts.aid, aunPath });
|
|
556
|
+
agentmdUploaded = true;
|
|
557
|
+
break;
|
|
558
|
+
}
|
|
559
|
+
catch (e) {
|
|
560
|
+
lastError = e;
|
|
561
|
+
}
|
|
488
562
|
}
|
|
489
|
-
|
|
490
|
-
console.warn(`⚠ agent.md upload failed: ${
|
|
563
|
+
if (!agentmdUploaded) {
|
|
564
|
+
console.warn(`⚠ agent.md upload failed: ${lastError?.message || lastError}`);
|
|
491
565
|
console.warn(` Retry later with: evolclaw aid agentmd put ${opts.aid}`);
|
|
492
566
|
}
|
|
567
|
+
await new Promise(r => setTimeout(r, 0));
|
|
493
568
|
}
|
|
494
569
|
catch (e) {
|
|
495
570
|
console.warn(`⚠ agent.md generation failed: ${e?.message || e}`);
|
|
496
571
|
}
|
|
572
|
+
// Attempt hot-load via IPC (if daemon is running)
|
|
573
|
+
let hotLoaded = false;
|
|
574
|
+
let hotLoadError;
|
|
575
|
+
try {
|
|
576
|
+
const ipcResult = await ipcQuery(p.socket, { type: 'evolagent.load', aid: opts.aid });
|
|
577
|
+
if (ipcResult?.ok) {
|
|
578
|
+
hotLoaded = true;
|
|
579
|
+
}
|
|
580
|
+
else if (ipcResult) {
|
|
581
|
+
hotLoadError = ipcResult.error;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
catch { /* daemon not running */ }
|
|
497
585
|
return {
|
|
498
586
|
ok: true,
|
|
499
587
|
aid: opts.aid,
|
|
500
588
|
configPath: toPosix(path.join(agentDirPath, 'config.json')),
|
|
501
589
|
aidCreated,
|
|
502
590
|
agentmdUploaded,
|
|
591
|
+
hotLoaded,
|
|
592
|
+
hotLoadError,
|
|
503
593
|
};
|
|
504
594
|
}
|
|
505
595
|
// ==================== agentSyncAids ====================
|
|
@@ -665,7 +755,12 @@ export async function agentSet(aid, key, rawValue) {
|
|
|
665
755
|
}
|
|
666
756
|
const value = parseJsonValue(rawValue);
|
|
667
757
|
setNestedValue(config, key, value);
|
|
668
|
-
|
|
758
|
+
try {
|
|
759
|
+
saveAgent(config);
|
|
760
|
+
}
|
|
761
|
+
catch (e) {
|
|
762
|
+
return { ok: false, error: e?.message || String(e) };
|
|
763
|
+
}
|
|
669
764
|
// Try hot-reload
|
|
670
765
|
let reloaded = false;
|
|
671
766
|
try {
|
|
@@ -733,7 +828,7 @@ export async function agentChannelUpsert(opts) {
|
|
|
733
828
|
return {
|
|
734
829
|
ok: true,
|
|
735
830
|
aid: opts.aid,
|
|
736
|
-
channelKey: `${opts.
|
|
831
|
+
channelKey: `${opts.channel.type}#${encodeURIComponent(opts.aid)}#${opts.channel.name}`,
|
|
737
832
|
reloaded,
|
|
738
833
|
};
|
|
739
834
|
}
|