evolclaw 3.1.3 → 3.1.4
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 +17 -0
- package/assets/.env.template +4 -0
- package/assets/config.json.template +6 -0
- package/assets/wechat-group-qr.jpeg +0 -0
- package/dist/agents/kit-renderer.js +35 -21
- package/dist/aun/aid/agentmd.js +25 -54
- package/dist/aun/aid/client.js +22 -7
- package/dist/aun/aid/identity.js +314 -28
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/rpc/connection.js +8 -13
- package/dist/channels/aun.js +31 -72
- package/dist/cli/agent.js +15 -22
- package/dist/cli/bench.js +8 -14
- package/dist/cli/help.js +23 -0
- package/dist/cli/index.js +371 -36
- package/dist/cli/init-channel.js +2 -3
- package/dist/cli/link-rules.js +2 -1
- package/dist/cli/net-check.js +10 -11
- package/dist/core/command-handler.js +6 -7
- package/dist/core/message/message-processor.js +19 -18
- package/dist/core/relation/peer-identity.js +64 -21
- package/dist/core/session/session-manager.js +6 -2
- package/dist/core/trigger/manager.js +37 -0
- package/dist/index.js +4 -1
- package/dist/paths.js +87 -16
- package/dist/utils/npm-ops.js +18 -11
- package/kits/eck_manifest.json +8 -8
- package/kits/rules/05-venue.md +2 -2
- package/kits/templates/system-fragments/baseagent.md +7 -1
- package/kits/templates/system-fragments/channel.md +4 -1
- package/kits/templates/system-fragments/identity.md +4 -4
- package/kits/templates/system-fragments/relation.md +8 -5
- package/kits/templates/system-fragments/session.md +20 -0
- package/kits/templates/system-fragments/venue.md +4 -1
- package/package.json +4 -2
- package/dist/net-check.js +0 -640
- package/dist/watch-msg.js +0 -544
- package/kits/templates/system-fragments/eckruntime.md +0 -14
package/dist/cli/net-check.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import os from 'os';
|
|
4
3
|
import net from 'net';
|
|
5
4
|
import tls from 'tls';
|
|
6
5
|
import dns from 'dns/promises';
|
|
7
6
|
import https from 'https';
|
|
8
7
|
// @ts-ignore
|
|
9
8
|
import { WebSocket } from 'ws';
|
|
9
|
+
import { aunPath as defaultAunPath } from '../paths.js';
|
|
10
|
+
import { createAunClient } from '../aun/aid/client.js';
|
|
11
|
+
import { isHelpFlag } from './help.js';
|
|
10
12
|
const GREEN = '\x1b[32m';
|
|
11
13
|
const RED = '\x1b[31m';
|
|
12
14
|
const YELLOW = '\x1b[33m';
|
|
@@ -217,10 +219,9 @@ async function runCheck(aid, formatJson) {
|
|
|
217
219
|
let accessToken;
|
|
218
220
|
try {
|
|
219
221
|
const start = Date.now();
|
|
220
|
-
const aunPath = process.env.AUN_HOME ||
|
|
221
|
-
const { AUNClient } = await import('@agentunion/fastaun');
|
|
222
|
+
const aunPath = process.env.AUN_HOME || defaultAunPath();
|
|
222
223
|
const result = await suppressSdkOutput(async () => {
|
|
223
|
-
const client =
|
|
224
|
+
const client = await createAunClient({ aunPath });
|
|
224
225
|
await client.auth.createAid({ aid });
|
|
225
226
|
const authResult = await client.auth.authenticate({ aid });
|
|
226
227
|
await client.close().catch(() => { });
|
|
@@ -268,10 +269,9 @@ async function runCheck(aid, formatJson) {
|
|
|
268
269
|
// ── Step 9: RPC 调用 (meta.ping) ──
|
|
269
270
|
try {
|
|
270
271
|
const start = Date.now();
|
|
271
|
-
const aunPath = process.env.AUN_HOME ||
|
|
272
|
-
const { AUNClient } = await import('@agentunion/fastaun');
|
|
272
|
+
const aunPath = process.env.AUN_HOME || defaultAunPath();
|
|
273
273
|
const sendResult = await suppressSdkOutput(async () => {
|
|
274
|
-
const client =
|
|
274
|
+
const client = await createAunClient({ aunPath });
|
|
275
275
|
await client.auth.createAid({ aid });
|
|
276
276
|
const authResult = await client.auth.authenticate({ aid });
|
|
277
277
|
const at = authResult?.access_token || client._access_token;
|
|
@@ -291,8 +291,7 @@ async function runCheck(aid, formatJson) {
|
|
|
291
291
|
// CLI 模拟 app 发送 echo[nc],向多个目标测试链路
|
|
292
292
|
try {
|
|
293
293
|
const echoStart = Date.now();
|
|
294
|
-
const aunPath = process.env.AUN_HOME ||
|
|
295
|
-
const { AUNClient } = await import('@agentunion/fastaun');
|
|
294
|
+
const aunPath = process.env.AUN_HOME || defaultAunPath();
|
|
296
295
|
// 选择 6 个测试目标,按消息数量、域、有无证书均衡选择
|
|
297
296
|
const allAids = await getAidList();
|
|
298
297
|
const myDomain = aid.split('.').slice(1).join('.');
|
|
@@ -421,7 +420,7 @@ async function runCheck(aid, formatJson) {
|
|
|
421
420
|
const label = targetLabel(target);
|
|
422
421
|
try {
|
|
423
422
|
const replyText = await suppressSdkOutput(async () => {
|
|
424
|
-
const client =
|
|
423
|
+
const client = await createAunClient({ aunPath });
|
|
425
424
|
await client.auth.createAid({ aid });
|
|
426
425
|
const authResult = await client.auth.authenticate({ aid });
|
|
427
426
|
const at = authResult?.access_token || client._access_token;
|
|
@@ -564,7 +563,7 @@ function shuffle(arr) {
|
|
|
564
563
|
export async function cmdNet(args) {
|
|
565
564
|
const sub = args[0];
|
|
566
565
|
const formatJson = args.includes('--format') && args.includes('json');
|
|
567
|
-
if (sub
|
|
566
|
+
if (isHelpFlag(sub)) {
|
|
568
567
|
console.log(`用法: evolclaw net check [<aid>] [--format json]
|
|
569
568
|
|
|
570
569
|
检查 AUN 网络链路连通性(10 步逐层诊断)。
|
|
@@ -410,8 +410,9 @@ export class CommandHandler {
|
|
|
410
410
|
return { session };
|
|
411
411
|
}
|
|
412
412
|
const ct = chatType === 'group' ? 'group' : chatType === 'private' ? 'private' : undefined;
|
|
413
|
+
const channelType = this.resolveChannelType(channel);
|
|
413
414
|
const session = await this.sessionManager.getActiveSession(channel, channelId)
|
|
414
|
-
?? await this.sessionManager.getOrCreateSession(channel, channelId, this.getEffectiveDefaultPath(channel), undefined, undefined, undefined, undefined, ct);
|
|
415
|
+
?? await this.sessionManager.getOrCreateSession(channel, channelId, this.getEffectiveDefaultPath(channel), undefined, undefined, undefined, undefined, ct, undefined, undefined, channelType);
|
|
415
416
|
// 如果 session 已存在但 chatType 跟传入的不一致,更新
|
|
416
417
|
if (ct && session.chatType !== ct) {
|
|
417
418
|
await this.sessionManager.updateSession(session.id, { chatType: ct });
|
|
@@ -2185,16 +2186,14 @@ export class CommandHandler {
|
|
|
2185
2186
|
// 尝试获取活跃会话(话题时直接查找话题 session)
|
|
2186
2187
|
let session;
|
|
2187
2188
|
if (threadId) {
|
|
2188
|
-
session = await this.sessionManager.getOrCreateSession(channel, channelId, this.getEffectiveDefaultPath(channel), threadId);
|
|
2189
|
+
session = await this.sessionManager.getOrCreateSession(channel, channelId, this.getEffectiveDefaultPath(channel), threadId, undefined, undefined, undefined, chatType, undefined, undefined, this.resolveChannelType(channel));
|
|
2189
2190
|
}
|
|
2190
2191
|
else {
|
|
2191
2192
|
session = await this.sessionManager.getActiveSession(channel, channelId);
|
|
2192
2193
|
}
|
|
2193
|
-
//
|
|
2194
|
-
if (!session
|
|
2195
|
-
|
|
2196
|
-
normalizedContent === '/status')) {
|
|
2197
|
-
session = await this.sessionManager.getOrCreateSession(channel, channelId, this.getEffectiveDefaultPath(channel));
|
|
2194
|
+
// 如果没有会话,自动创建(所有后续命令都需要 session)
|
|
2195
|
+
if (!session) {
|
|
2196
|
+
session = await this.sessionManager.getOrCreateSession(channel, channelId, this.getEffectiveDefaultPath(channel), undefined, undefined, undefined, undefined, chatType, undefined, undefined, this.resolveChannelType(channel));
|
|
2198
2197
|
}
|
|
2199
2198
|
// /status 命令:显示会话状态
|
|
2200
2199
|
if (normalizedContent === '/status') {
|
|
@@ -95,10 +95,11 @@ export class MessageProcessor {
|
|
|
95
95
|
return [...this.agentMap.keys()];
|
|
96
96
|
}
|
|
97
97
|
/** 判断是否为后台会话(仅主会话参与判断,话题会话独立) */
|
|
98
|
-
|
|
98
|
+
isBackgroundSession(session, _channel, _channelId) {
|
|
99
99
|
if (session.threadId)
|
|
100
100
|
return false;
|
|
101
|
-
|
|
101
|
+
// 使用 session 自身的 channelType 精确定位 active.json,避免扫描误匹配
|
|
102
|
+
const active = this.sessionManager.getActiveSessionSync(session.channel, session.channelId, session.channelType, session.selfId);
|
|
102
103
|
return active ? session.id !== active.id : false;
|
|
103
104
|
}
|
|
104
105
|
constructor(agentRunnerOrMap, sessionManager, globalSettings, messageCache, eventBus, commandHandler, primaryRunnerKey) {
|
|
@@ -245,7 +246,7 @@ export class MessageProcessor {
|
|
|
245
246
|
monitor?.recordEvent(eventType || 'unknown', toolName);
|
|
246
247
|
};
|
|
247
248
|
// Cache background status to avoid async call inside setInterval
|
|
248
|
-
const isBackground =
|
|
249
|
+
const isBackground = this.isBackgroundSession(session, message.channel, message.channelId);
|
|
249
250
|
const timeoutPromise = new Promise((_, reject) => {
|
|
250
251
|
rejectFn = reject;
|
|
251
252
|
if (!monitorEnabled)
|
|
@@ -382,7 +383,7 @@ export class MessageProcessor {
|
|
|
382
383
|
replyContext: taskReplyContext(),
|
|
383
384
|
});
|
|
384
385
|
try {
|
|
385
|
-
const isBackground =
|
|
386
|
+
const isBackground = this.isBackgroundSession(session, message.channel, message.channelId);
|
|
386
387
|
// 记录收到消息
|
|
387
388
|
logger.message({
|
|
388
389
|
msgId: messageId,
|
|
@@ -439,7 +440,7 @@ export class MessageProcessor {
|
|
|
439
440
|
// (不支持 thought 的 channel 静默丢弃,避免降级为普通消息)
|
|
440
441
|
if (isProactive && payload.kind === 'activity.batch' && !adapter.capabilities?.thought)
|
|
441
442
|
return;
|
|
442
|
-
const isCurrentlyBackground =
|
|
443
|
+
const isCurrentlyBackground = this.isBackgroundSession(session, message.channel, message.channelId);
|
|
443
444
|
if (isCurrentlyBackground)
|
|
444
445
|
return;
|
|
445
446
|
const opts = {};
|
|
@@ -516,18 +517,13 @@ export class MessageProcessor {
|
|
|
516
517
|
const selfAid = typeof adapterAny._selfAid === 'function' ? adapterAny._selfAid() : undefined;
|
|
517
518
|
const selfName = typeof adapterAny._selfName === 'function' ? adapterAny._selfName() : undefined;
|
|
518
519
|
const peerName = message.peerName || session.metadata?.peerName;
|
|
519
|
-
// 文件发送能力
|
|
520
|
-
let currentCanSend = false;
|
|
521
|
-
if (!isProactive) {
|
|
522
|
-
currentCanSend = !!(channelInfo.adapter.capabilities?.file);
|
|
523
|
-
}
|
|
524
520
|
// 通道能力
|
|
525
521
|
const capParts = [];
|
|
526
522
|
if (options?.supportsImages)
|
|
527
523
|
capParts.push('图片输入');
|
|
528
524
|
if (channelInfo.adapter.capabilities?.image)
|
|
529
525
|
capParts.push('图片输出');
|
|
530
|
-
if (channelInfo.adapter.capabilities?.file)
|
|
526
|
+
if (!isProactive && channelInfo.adapter.capabilities?.file)
|
|
531
527
|
capParts.push('文件发送');
|
|
532
528
|
// Personal layer
|
|
533
529
|
const owningAgent = this.agentRegistry?.resolveByChannel(channelKey);
|
|
@@ -543,6 +539,7 @@ export class MessageProcessor {
|
|
|
543
539
|
? `${currentChannelType}#${encodeURIComponent(peerIdRaw)}`
|
|
544
540
|
: undefined;
|
|
545
541
|
const normalizedBaseagent = normalizeBaseagent(agent.name);
|
|
542
|
+
const agentModel = (typeof agent.getModel === 'function') ? agent.getModel() : undefined;
|
|
546
543
|
// Kit renderer: 组装上下文
|
|
547
544
|
const kitCtx = {
|
|
548
545
|
vars: {
|
|
@@ -557,19 +554,23 @@ export class MessageProcessor {
|
|
|
557
554
|
peerKey,
|
|
558
555
|
peerName: peerName || undefined,
|
|
559
556
|
peerRole: session.identity?.role || 'unknown',
|
|
557
|
+
peerType: message.peerType || undefined,
|
|
560
558
|
groupId: session.metadata?.groupId || undefined,
|
|
561
|
-
scene: session.chatType ? (session.chatType === 'group' ? 'group' : 'private') : 'coding',
|
|
562
559
|
chatType: session.chatType || null,
|
|
563
560
|
channel: currentChannelType || null,
|
|
564
561
|
venueUid: undefined,
|
|
562
|
+
capabilities: capParts.length > 0 ? capParts.join('、') : undefined,
|
|
565
563
|
project: path.basename(absoluteProjectPath),
|
|
564
|
+
sessionId: session.id,
|
|
566
565
|
sessionName: session.name || undefined,
|
|
567
|
-
|
|
566
|
+
sessionCreatedAt: session.createdAt ? new Date(session.createdAt).toISOString() : undefined,
|
|
567
|
+
threadId: session.threadId || undefined,
|
|
568
|
+
chatMode: isProactive ? 'proactive' : 'interactive',
|
|
568
569
|
readonly: session.metadata?.permissionMode === 'readonly',
|
|
569
|
-
canSendFile: !isProactive && currentCanSend,
|
|
570
|
-
capabilities: capParts.length > 0 ? capParts.join('、') : undefined,
|
|
571
570
|
baseAgent: normalizedBaseagent.canonical,
|
|
572
571
|
baseAgentName: normalizedBaseagent.displayName,
|
|
572
|
+
baseAgentModel: agentModel || undefined,
|
|
573
|
+
agentSessionId: session.agentSessionId || undefined,
|
|
573
574
|
},
|
|
574
575
|
sessionId: session.id,
|
|
575
576
|
};
|
|
@@ -752,7 +753,7 @@ export class MessageProcessor {
|
|
|
752
753
|
if (finalReplyText) {
|
|
753
754
|
if (isProactive && !streamResult.hasReceivedText && /^Unknown skill:\s+\S+/i.test(finalReplyText.trim())) {
|
|
754
755
|
// Proactive 模式 + SDK 本地兜底:直接发送绕过 silent renderer
|
|
755
|
-
const isCurrentlyBackground =
|
|
756
|
+
const isCurrentlyBackground = this.isBackgroundSession(session, message.channel, message.channelId);
|
|
756
757
|
if (!isCurrentlyBackground) {
|
|
757
758
|
await adapter.send({ ...envelope, replyContext: capturedReplyContext }, { kind: 'result.text', text: finalReplyText, isFinal: true });
|
|
758
759
|
logger.info(`[MessageProcessor] proactive SDK fallback replied task=${taskId} text="${finalReplyText.slice(0, 60)}"`);
|
|
@@ -868,7 +869,7 @@ export class MessageProcessor {
|
|
|
868
869
|
// 写入消息记录(出方向)已下沉到 aun.ts:deliverTextEntry,
|
|
869
870
|
// 所有 message.send 成功后统一写入 messages.jsonl,此处不再重复写入。
|
|
870
871
|
}
|
|
871
|
-
const isFinallyBackground =
|
|
872
|
+
const isFinallyBackground = this.isBackgroundSession(session, message.channel, message.channelId);
|
|
872
873
|
if (isFinallyBackground && session.sessionMode !== 'autonomous') {
|
|
873
874
|
const projectName = path.basename(session.projectPath);
|
|
874
875
|
const count = this.messageCache.getCount(session.id);
|
|
@@ -1083,7 +1084,7 @@ export class MessageProcessor {
|
|
|
1083
1084
|
});
|
|
1084
1085
|
continue;
|
|
1085
1086
|
}
|
|
1086
|
-
const isCurrentlyBackground =
|
|
1087
|
+
const isCurrentlyBackground = this.isBackgroundSession(session, session.channel, session.channelId);
|
|
1087
1088
|
// === 前台任务:正常处理所有事件 ===
|
|
1088
1089
|
if (!isCurrentlyBackground) {
|
|
1089
1090
|
// 流式文本
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* PeerIdentityCache - 对端身份缓存管理
|
|
3
3
|
*
|
|
4
4
|
* 职责:
|
|
5
|
-
* 1.
|
|
6
|
-
* 2.
|
|
5
|
+
* 1. 通过 agentmdSync 标准流程获取对端 agent.md(check → fetch if changed)
|
|
6
|
+
* 2. 仅在 agent.md 内容变化时重写 peer-identity.json
|
|
7
7
|
* 3. 支持入站和出站消息的身份查询
|
|
8
8
|
*
|
|
9
|
-
* 信源:对端的 agent.md(通过 AUN SDK
|
|
9
|
+
* 信源:对端的 agent.md(通过 AUN SDK checkAgentMd + fetchAgentMd)
|
|
10
10
|
* 判定规则:type !== 'human' → agent
|
|
11
11
|
* 缓存位置:$AGENT_DIR/relations/<channel>#<urlEncode(peerId)>/peer-identity.json
|
|
12
12
|
*/
|
|
@@ -14,6 +14,7 @@ import * as fs from 'fs';
|
|
|
14
14
|
import * as path from 'path';
|
|
15
15
|
import * as crypto from 'crypto';
|
|
16
16
|
import { logger } from '../../utils/logger.js';
|
|
17
|
+
import { agentMdPath } from '../../paths.js';
|
|
17
18
|
/**
|
|
18
19
|
* 对端身份缓存管理器
|
|
19
20
|
*/
|
|
@@ -54,29 +55,26 @@ export class PeerIdentityCache {
|
|
|
54
55
|
}
|
|
55
56
|
/**
|
|
56
57
|
* 从 agent.md 更新身份信息
|
|
57
|
-
* @param agentMd 已验签的 agent.md 内容
|
|
58
58
|
*/
|
|
59
59
|
static updateFromAgentMd(channel, peerId, agentDir, agentMd, verifiedAt) {
|
|
60
|
-
// 解析 type 和 name
|
|
61
60
|
const typeMatch = agentMd.match(/^type:\s*["']?(\w+)["']?/m);
|
|
62
61
|
const nameMatch = agentMd.match(/^name:\s*["']?(.+?)["']?\s*$/m);
|
|
63
62
|
const type = typeMatch?.[1] || 'unknown';
|
|
64
63
|
const isAgent = type !== 'human';
|
|
65
64
|
const name = nameMatch?.[1]?.trim();
|
|
66
|
-
// 计算 hash
|
|
67
65
|
const agentMdHash = 'sha256:' + crypto.createHash('sha256').update(agentMd, 'utf-8').digest('hex');
|
|
68
|
-
|
|
66
|
+
const now = Date.now();
|
|
69
67
|
const identity = {
|
|
70
68
|
aid: peerId,
|
|
71
69
|
type,
|
|
72
70
|
isAgent,
|
|
73
71
|
name,
|
|
74
72
|
agentMdHash,
|
|
73
|
+
agentMdUpdatedAt: now,
|
|
75
74
|
verifiedAt,
|
|
76
|
-
lastCheckedAt:
|
|
75
|
+
lastCheckedAt: now,
|
|
77
76
|
source: 'agentmd',
|
|
78
77
|
};
|
|
79
|
-
// 写入文件
|
|
80
78
|
const filePath = this.getFilePath(channel, peerId, agentDir);
|
|
81
79
|
try {
|
|
82
80
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
@@ -88,6 +86,18 @@ export class PeerIdentityCache {
|
|
|
88
86
|
}
|
|
89
87
|
return identity;
|
|
90
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* 仅更新 lastCheckedAt(内容未变时的轻量操作)
|
|
91
|
+
*/
|
|
92
|
+
static touchLastChecked(channel, peerId, agentDir, cached) {
|
|
93
|
+
const updated = { ...cached, lastCheckedAt: Date.now() };
|
|
94
|
+
const filePath = this.getFilePath(channel, peerId, agentDir);
|
|
95
|
+
try {
|
|
96
|
+
fs.writeFileSync(filePath, JSON.stringify(updated, null, 2), 'utf-8');
|
|
97
|
+
}
|
|
98
|
+
catch { /* ignore */ }
|
|
99
|
+
return updated;
|
|
100
|
+
}
|
|
91
101
|
/**
|
|
92
102
|
* 标记为 unknown(验签失败或无 agent.md)
|
|
93
103
|
*/
|
|
@@ -95,8 +105,9 @@ export class PeerIdentityCache {
|
|
|
95
105
|
const identity = {
|
|
96
106
|
aid: peerId,
|
|
97
107
|
type: 'unknown',
|
|
98
|
-
isAgent: true,
|
|
108
|
+
isAgent: true,
|
|
99
109
|
agentMdHash: '',
|
|
110
|
+
agentMdUpdatedAt: 0,
|
|
100
111
|
verifiedAt: 0,
|
|
101
112
|
lastCheckedAt: Date.now(),
|
|
102
113
|
source: 'unknown',
|
|
@@ -113,17 +124,17 @@ export class PeerIdentityCache {
|
|
|
113
124
|
return identity;
|
|
114
125
|
}
|
|
115
126
|
/**
|
|
116
|
-
*
|
|
127
|
+
* 完整流程:缓存检查 → agentmdSync(check+fetch)→ 按 changed 决定是否重写
|
|
117
128
|
*
|
|
118
129
|
* @param channel 渠道类型(如 'aun')
|
|
119
130
|
* @param peerId 对端 ID(AUN 是 AID)
|
|
120
131
|
* @param agentDir agent 数据根目录
|
|
121
|
-
* @param aunClient AUN SDK client(需要有 fetchAgentMd 方法)
|
|
132
|
+
* @param aunClient AUN SDK client(需要有 checkAgentMd / fetchAgentMd 方法)
|
|
122
133
|
* @param forceRefresh 强制刷新(忽略缓存时效)
|
|
123
134
|
* @returns PeerIdentity
|
|
124
135
|
*/
|
|
125
136
|
static async resolve(channel, peerId, agentDir, aunClient, forceRefresh = false) {
|
|
126
|
-
// 1.
|
|
137
|
+
// 1. 缓存检查
|
|
127
138
|
if (!forceRefresh && !this.needsRefresh(channel, peerId, agentDir)) {
|
|
128
139
|
const cached = this.get(channel, peerId, agentDir);
|
|
129
140
|
if (cached) {
|
|
@@ -131,17 +142,49 @@ export class PeerIdentityCache {
|
|
|
131
142
|
return cached;
|
|
132
143
|
}
|
|
133
144
|
}
|
|
134
|
-
// 2.
|
|
145
|
+
// 2. 标准流程:checkAgentMd → fetchAgentMd(如果有变化)
|
|
135
146
|
try {
|
|
136
|
-
logger.debug(`[PeerIdentityCache]
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
147
|
+
logger.debug(`[PeerIdentityCache] Syncing agent.md: ${channel}#${peerId}`);
|
|
148
|
+
const state = await aunClient.checkAgentMd(peerId, 30);
|
|
149
|
+
let content;
|
|
150
|
+
if (state.in_sync && state.local_found) {
|
|
151
|
+
// 本地已是最新,读本地文件
|
|
152
|
+
const localPath = agentMdPath(peerId);
|
|
153
|
+
try {
|
|
154
|
+
content = fs.readFileSync(localPath, 'utf-8');
|
|
155
|
+
}
|
|
156
|
+
catch { /* ignore */ }
|
|
157
|
+
}
|
|
158
|
+
if (!content) {
|
|
159
|
+
// 需要下载(不同步或本地不存在)
|
|
160
|
+
const info = await aunClient.fetchAgentMd(peerId);
|
|
161
|
+
content = info.content;
|
|
162
|
+
}
|
|
163
|
+
// 3. 比较 hash,仅在变化时重写 peer-identity.json
|
|
164
|
+
const newHash = 'sha256:' + crypto.createHash('sha256').update(content, 'utf-8').digest('hex');
|
|
165
|
+
const cached = this.get(channel, peerId, agentDir);
|
|
166
|
+
if (cached && cached.agentMdHash === newHash && cached.source === 'agentmd') {
|
|
167
|
+
return this.touchLastChecked(channel, peerId, agentDir, cached);
|
|
168
|
+
}
|
|
169
|
+
return this.updateFromAgentMd(channel, peerId, agentDir, content, Date.now());
|
|
141
170
|
}
|
|
142
171
|
catch (err) {
|
|
143
|
-
//
|
|
144
|
-
|
|
172
|
+
// 4. 网络失败,fallback 本地文件
|
|
173
|
+
const localPath = agentMdPath(peerId);
|
|
174
|
+
try {
|
|
175
|
+
if (fs.existsSync(localPath)) {
|
|
176
|
+
const localContent = fs.readFileSync(localPath, 'utf-8');
|
|
177
|
+
logger.info(`[PeerIdentityCache] Network failed, using local agent.md for ${peerId}`);
|
|
178
|
+
const localHash = 'sha256:' + crypto.createHash('sha256').update(localContent, 'utf-8').digest('hex');
|
|
179
|
+
const cached = this.get(channel, peerId, agentDir);
|
|
180
|
+
if (cached && cached.agentMdHash === localHash && cached.source === 'agentmd') {
|
|
181
|
+
return this.touchLastChecked(channel, peerId, agentDir, cached);
|
|
182
|
+
}
|
|
183
|
+
return this.updateFromAgentMd(channel, peerId, agentDir, localContent, cached?.verifiedAt ?? 0);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch { /* ignore fs errors */ }
|
|
187
|
+
logger.warn(`[PeerIdentityCache] Failed to resolve: ${channel}#${peerId} err=${err instanceof Error ? err.message : String(err)}`);
|
|
145
188
|
return this.markUnknown(channel, peerId, agentDir);
|
|
146
189
|
}
|
|
147
190
|
}
|
|
@@ -36,8 +36,8 @@ export class SessionManager {
|
|
|
36
36
|
// 来源2:群聊强制 proactive
|
|
37
37
|
if (ct === 'group')
|
|
38
38
|
return 'proactive';
|
|
39
|
-
// 来源3:非 human
|
|
40
|
-
if (peerType && peerType !== 'human'
|
|
39
|
+
// 来源3:非 human 对端强制 proactive,无视 agent 的默认 chatmode 配置
|
|
40
|
+
if (peerType && peerType !== 'human')
|
|
41
41
|
return 'proactive';
|
|
42
42
|
// 来源1:agent 配置默认值
|
|
43
43
|
const resolved = this.sessionModeResolver?.(channel, ct, peerType);
|
|
@@ -850,6 +850,10 @@ export class SessionManager {
|
|
|
850
850
|
async getActiveSession(channel, channelId) {
|
|
851
851
|
return this.readActive(channel, channelId);
|
|
852
852
|
}
|
|
853
|
+
/** 同步版 getActiveSession,支持精确路径定位(避免扫描) */
|
|
854
|
+
getActiveSessionSync(channel, channelId, channelType, selfId) {
|
|
855
|
+
return this.readActive(channel, channelId, channelType, selfId);
|
|
856
|
+
}
|
|
853
857
|
async getThreadSession(channel, channelId, threadId) {
|
|
854
858
|
let chatDir;
|
|
855
859
|
try {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { atomicWriteJson, appendJsonl } from '../session/session-fs-store.js';
|
|
4
|
+
import { formatChannelKey } from '../channel-loader.js';
|
|
4
5
|
import { logger } from '../../utils/logger.js';
|
|
5
6
|
export class TriggerManager {
|
|
6
7
|
aid;
|
|
@@ -22,6 +23,26 @@ export class TriggerManager {
|
|
|
22
23
|
const raw = fs.readFileSync(this.triggersPath, 'utf8');
|
|
23
24
|
const data = JSON.parse(raw);
|
|
24
25
|
this.triggers = new Map(Object.entries(data.triggers ?? {}));
|
|
26
|
+
// Migrate old channel key format: "selfPeerId#type#name" → "type#selfPeerId#name"
|
|
27
|
+
let migrated = false;
|
|
28
|
+
for (const trigger of this.triggers.values()) {
|
|
29
|
+
const fixed = this.migrateChannelKey(trigger.targetChannel);
|
|
30
|
+
if (fixed !== trigger.targetChannel) {
|
|
31
|
+
trigger.targetChannel = fixed;
|
|
32
|
+
migrated = true;
|
|
33
|
+
}
|
|
34
|
+
if (trigger.createdByChannel) {
|
|
35
|
+
const fixedBy = this.migrateChannelKey(trigger.createdByChannel);
|
|
36
|
+
if (fixedBy !== trigger.createdByChannel) {
|
|
37
|
+
trigger.createdByChannel = fixedBy;
|
|
38
|
+
migrated = true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (migrated) {
|
|
43
|
+
logger.info(`[TriggerManager] Migrated old channel key format`);
|
|
44
|
+
this.save();
|
|
45
|
+
}
|
|
25
46
|
return [...this.triggers.values()];
|
|
26
47
|
}
|
|
27
48
|
catch (e) {
|
|
@@ -30,6 +51,22 @@ export class TriggerManager {
|
|
|
30
51
|
return [];
|
|
31
52
|
}
|
|
32
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Detect and fix old format "selfPeerId#type#name" → current "type#selfPeerId#name".
|
|
56
|
+
* Old format: first segment contains '.' (AID like "evolai.agentid.pub").
|
|
57
|
+
*/
|
|
58
|
+
migrateChannelKey(key) {
|
|
59
|
+
if (!key || !key.includes('#'))
|
|
60
|
+
return key;
|
|
61
|
+
const parts = key.split('#');
|
|
62
|
+
if (parts.length !== 3)
|
|
63
|
+
return key;
|
|
64
|
+
const [first, second, third] = parts;
|
|
65
|
+
if (first.includes('.') && !second.includes('.')) {
|
|
66
|
+
return formatChannelKey({ type: second, selfPeerId: first, name: third });
|
|
67
|
+
}
|
|
68
|
+
return key;
|
|
69
|
+
}
|
|
33
70
|
save() {
|
|
34
71
|
const data = {
|
|
35
72
|
version: 1,
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { ensureDataDirs, resolvePaths, getPackageRoot, agentMdPath } from './pat
|
|
|
5
5
|
import { resolveAnthropicConfig } from './agents/resolve.js';
|
|
6
6
|
import { loadDefaults, autoMigrateIfNeeded, migrateIdentitiesIfNeeded } from './config-store.js';
|
|
7
7
|
import { CONFIG_SCHEMA_VERSION } from './types.js';
|
|
8
|
+
import dotenv from 'dotenv';
|
|
8
9
|
import { SessionManager } from './core/session/session-manager.js';
|
|
9
10
|
import { ClaudeAgentPlugin } from './agents/claude-runner.js';
|
|
10
11
|
import { CodexAgentPlugin } from './agents/codex-runner.js';
|
|
@@ -183,6 +184,8 @@ async function main() {
|
|
|
183
184
|
logger.info(`EvolClaw v${readEvolclawVersion()} starting... (fastaun v${readFastaunVersion()})`);
|
|
184
185
|
// 确保数据目录存在
|
|
185
186
|
ensureDataDirs();
|
|
187
|
+
// 加载 $EVOLCLAW_HOME/.env 到 process.env(不覆盖已存在的变量)
|
|
188
|
+
dotenv.config({ path: path.join(resolvePaths().root, '.env') });
|
|
186
189
|
// ── 单实例保护(pre-check + post-write self-check)──
|
|
187
190
|
// pre-check:发现已有活 main 直接退出,避免起任何副作用
|
|
188
191
|
{
|
|
@@ -313,7 +316,7 @@ async function main() {
|
|
|
313
316
|
// 优先级:群聊 > nothuman > private
|
|
314
317
|
if (chatType === 'group')
|
|
315
318
|
return cm.group;
|
|
316
|
-
if (peerType && peerType !== 'human'
|
|
319
|
+
if (peerType && peerType !== 'human')
|
|
317
320
|
return cm.nothuman;
|
|
318
321
|
return cm.private;
|
|
319
322
|
});
|
package/dist/paths.js
CHANGED
|
@@ -46,6 +46,14 @@ export function resolvePaths() {
|
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
48
|
// ── AID 路径(agent.md 存放在 $EVOLCLAW_HOME/AIDs/<aid>/)──
|
|
49
|
+
/**
|
|
50
|
+
* AUN SDK 数据根路径(密钥/证书/AgentMDs 等)。
|
|
51
|
+
* 与 evolclaw 数据共用 $EVOLCLAW_HOME 根,避免散落到 ~/.aun。
|
|
52
|
+
* 启动时 SDK 通过 aun_path 选项指向这里;agent.md 子目录通过 setAgentMdPath(aidsDir()) 单独设置。
|
|
53
|
+
*/
|
|
54
|
+
export function aunPath() {
|
|
55
|
+
return resolveRoot();
|
|
56
|
+
}
|
|
49
57
|
export function aidsDir() {
|
|
50
58
|
return path.join(resolveRoot(), 'AIDs');
|
|
51
59
|
}
|
|
@@ -109,30 +117,93 @@ export function ensureDataDirs() {
|
|
|
109
117
|
fs.mkdirSync(p.eckDir, { recursive: true });
|
|
110
118
|
fs.mkdirSync(eckDebugDir(), { recursive: true });
|
|
111
119
|
fs.mkdirSync(aidsDir(), { recursive: true });
|
|
112
|
-
|
|
120
|
+
seedConfigTemplates();
|
|
121
|
+
migrateFromAun();
|
|
113
122
|
}
|
|
123
|
+
const MIGRATION_MARKER = '.migrated-from-aun';
|
|
114
124
|
/**
|
|
115
|
-
*
|
|
116
|
-
*
|
|
125
|
+
* 一次性迁移:把 ~/.aun 下的 SDK 数据搬到 $EVOLCLAW_HOME 下。
|
|
126
|
+
* 通过标记文件判断是否已迁移,完成后写入标记,后续版本可删除此函数。
|
|
117
127
|
*/
|
|
118
|
-
function
|
|
119
|
-
const
|
|
120
|
-
const
|
|
121
|
-
if (
|
|
128
|
+
function migrateFromAun() {
|
|
129
|
+
const newRoot = resolveRoot();
|
|
130
|
+
const markerPath = path.join(newRoot, MIGRATION_MARKER);
|
|
131
|
+
if (fs.existsSync(markerPath))
|
|
132
|
+
return;
|
|
133
|
+
const oldRoot = path.join(os.homedir(), '.aun');
|
|
134
|
+
if (!fs.existsSync(oldRoot) || oldRoot === newRoot) {
|
|
135
|
+
try {
|
|
136
|
+
fs.writeFileSync(markerPath, new Date().toISOString(), 'utf-8');
|
|
137
|
+
}
|
|
138
|
+
catch { }
|
|
122
139
|
return;
|
|
140
|
+
}
|
|
141
|
+
const copyFileIfMissing = (src, dst) => {
|
|
142
|
+
if (!fs.existsSync(src) || fs.existsSync(dst))
|
|
143
|
+
return;
|
|
144
|
+
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
145
|
+
fs.copyFileSync(src, dst);
|
|
146
|
+
};
|
|
147
|
+
const copyDirIfMissing = (src, dst) => {
|
|
148
|
+
if (!fs.existsSync(src))
|
|
149
|
+
return;
|
|
150
|
+
if (!fs.statSync(src).isDirectory())
|
|
151
|
+
return;
|
|
152
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
153
|
+
const s = path.join(src, entry.name);
|
|
154
|
+
const d = path.join(dst, entry.name);
|
|
155
|
+
if (entry.isDirectory()) {
|
|
156
|
+
copyDirIfMissing(s, d);
|
|
157
|
+
}
|
|
158
|
+
else if (entry.isFile()) {
|
|
159
|
+
copyFileIfMissing(s, d);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
};
|
|
123
163
|
try {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
fs.copyFileSync(oldMd, newMd);
|
|
164
|
+
const oldAids = path.join(oldRoot, 'AIDs');
|
|
165
|
+
const newAids = aidsDir();
|
|
166
|
+
if (fs.existsSync(oldAids)) {
|
|
167
|
+
for (const entry of fs.readdirSync(oldAids, { withFileTypes: true })) {
|
|
168
|
+
if (!entry.isDirectory())
|
|
169
|
+
continue;
|
|
170
|
+
copyDirIfMissing(path.join(oldAids, entry.name), path.join(newAids, entry.name));
|
|
132
171
|
}
|
|
133
172
|
}
|
|
173
|
+
copyDirIfMissing(path.join(oldRoot, 'CA'), path.join(newRoot, 'CA'));
|
|
174
|
+
for (const f of ['.seed', '.device_id']) {
|
|
175
|
+
copyFileIfMissing(path.join(oldRoot, f), path.join(newRoot, f));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch { /* best-effort */ }
|
|
179
|
+
try {
|
|
180
|
+
fs.writeFileSync(markerPath, new Date().toISOString(), 'utf-8');
|
|
181
|
+
}
|
|
182
|
+
catch { }
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* 首次运行时从 assets/ 模板拷贝 config.json 和 .env 到 $EVOLCLAW_HOME。
|
|
186
|
+
* 已存在则跳过,不覆盖用户修改。
|
|
187
|
+
*/
|
|
188
|
+
function seedConfigTemplates() {
|
|
189
|
+
const root = resolveRoot();
|
|
190
|
+
const assetsDir = path.join(getPackageRoot(), 'assets');
|
|
191
|
+
const templates = [
|
|
192
|
+
{ src: 'config.json.template', dst: 'config.json' },
|
|
193
|
+
{ src: '.env.template', dst: '.env' },
|
|
194
|
+
];
|
|
195
|
+
for (const { src, dst } of templates) {
|
|
196
|
+
const dstPath = path.join(root, dst);
|
|
197
|
+
if (fs.existsSync(dstPath))
|
|
198
|
+
continue;
|
|
199
|
+
const srcPath = path.join(assetsDir, src);
|
|
200
|
+
if (!fs.existsSync(srcPath))
|
|
201
|
+
continue;
|
|
202
|
+
try {
|
|
203
|
+
fs.copyFileSync(srcPath, dstPath);
|
|
204
|
+
}
|
|
205
|
+
catch { /* best-effort */ }
|
|
134
206
|
}
|
|
135
|
-
catch { /* best-effort migration */ }
|
|
136
207
|
}
|
|
137
208
|
// ── kits 路径(始终从包内读取,不复制到 EVOLCLAW_HOME)──
|
|
138
209
|
export function kitsDir() {
|
package/dist/utils/npm-ops.js
CHANGED
|
@@ -84,19 +84,26 @@ export function getLocalVersion() {
|
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
86
86
|
* 查询 npm registry 上指定包的最新版本。
|
|
87
|
-
*
|
|
87
|
+
* 使用 HTTP fetch 直接查 registry API,不依赖 npm CLI。
|
|
88
|
+
* 超时 10 秒,失败返回 null。
|
|
88
89
|
*/
|
|
89
|
-
export function checkLatestVersion(pkg = 'evolclaw') {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
const ver = stdout.trim();
|
|
97
|
-
resolve(ver || null);
|
|
90
|
+
export async function checkLatestVersion(pkg = 'evolclaw') {
|
|
91
|
+
try {
|
|
92
|
+
const controller = new AbortController();
|
|
93
|
+
const timer = setTimeout(() => controller.abort(), 10000);
|
|
94
|
+
const res = await fetch(`https://registry.npmjs.org/${pkg}/latest`, {
|
|
95
|
+
signal: controller.signal,
|
|
96
|
+
headers: { 'Accept': 'application/json' },
|
|
98
97
|
});
|
|
99
|
-
|
|
98
|
+
clearTimeout(timer);
|
|
99
|
+
if (!res.ok)
|
|
100
|
+
return null;
|
|
101
|
+
const data = await res.json();
|
|
102
|
+
return data.version || null;
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
100
107
|
}
|
|
101
108
|
/**
|
|
102
109
|
* 完整升级流程:检查 → 比较 → 安装(失败重试一次)
|