evolclaw 3.1.3 → 3.1.5

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.
Files changed (100) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/assets/.env.template +4 -0
  3. package/assets/config.json.template +6 -0
  4. package/assets/wechat-group-qr.jpeg +0 -0
  5. package/dist/agents/claude-runner.js +348 -156
  6. package/dist/agents/kit-renderer.js +211 -42
  7. package/dist/aun/aid/agentmd.js +75 -139
  8. package/dist/aun/aid/client.js +1 -14
  9. package/dist/aun/aid/identity.js +381 -54
  10. package/dist/aun/aid/index.js +3 -2
  11. package/dist/aun/aid/store.js +74 -0
  12. package/dist/aun/msg/p2p.js +26 -2
  13. package/dist/aun/rpc/connection.js +23 -35
  14. package/dist/channels/aun.js +92 -144
  15. package/dist/channels/dingtalk.js +1 -0
  16. package/dist/channels/feishu.js +270 -190
  17. package/dist/channels/qqbot.js +1 -0
  18. package/dist/channels/wechat.js +1 -0
  19. package/dist/channels/wecom.js +1 -0
  20. package/dist/cli/agent.js +26 -27
  21. package/dist/cli/bench.js +45 -34
  22. package/dist/cli/help.js +23 -0
  23. package/dist/cli/index.js +538 -77
  24. package/dist/cli/init-channel.js +7 -4
  25. package/dist/cli/link-rules.js +2 -1
  26. package/dist/cli/model.js +324 -0
  27. package/dist/cli/net-check.js +138 -56
  28. package/dist/cli/watch-msg.js +7 -7
  29. package/dist/cli/watch-web/debug-log.js +18 -0
  30. package/dist/cli/watch-web/server.js +306 -0
  31. package/dist/cli/watch-web/sources/aid.js +63 -0
  32. package/dist/cli/watch-web/sources/msg.js +70 -0
  33. package/dist/cli/watch-web/sources/session.js +638 -0
  34. package/dist/cli/watch-web/sources/types.js +10 -0
  35. package/dist/cli/watch-web/static/app.js +546 -0
  36. package/dist/cli/watch-web/static/index.html +54 -0
  37. package/dist/cli/watch-web/static/style.css +247 -0
  38. package/dist/core/channel-loader.js +7 -4
  39. package/dist/core/command-handler.js +87 -93
  40. package/dist/core/evolagent-registry.js +1 -1
  41. package/dist/core/evolagent.js +4 -4
  42. package/dist/core/interaction-router.js +59 -0
  43. package/dist/core/message/message-bridge.js +6 -6
  44. package/dist/core/message/message-log.js +2 -2
  45. package/dist/core/message/message-processor.js +104 -118
  46. package/dist/core/message/stream-idle-monitor.js +21 -0
  47. package/dist/core/model/model-catalog.js +215 -0
  48. package/dist/core/model/model-scope.js +250 -0
  49. package/dist/core/relation/peer-identity.js +78 -44
  50. package/dist/core/relation/peer-key.js +16 -0
  51. package/dist/core/session/session-fs-store.js +34 -55
  52. package/dist/core/session/session-key.js +24 -0
  53. package/dist/core/session/session-manager.js +312 -251
  54. package/dist/core/session/session-mapper.js +9 -4
  55. package/dist/core/trigger/manager.js +37 -0
  56. package/dist/core/trigger/scheduler.js +2 -1
  57. package/dist/index.js +10 -3
  58. package/dist/ipc.js +22 -0
  59. package/dist/paths.js +87 -16
  60. package/dist/utils/npm-ops.js +18 -11
  61. package/kits/docs/GUIDE.md +2 -2
  62. package/kits/docs/INDEX.md +11 -7
  63. package/kits/docs/channels/aun.md +56 -17
  64. package/kits/docs/channels/feishu.md +41 -12
  65. package/kits/docs/context-assembly.md +181 -0
  66. package/kits/docs/evolclaw/agent.md +49 -0
  67. package/kits/docs/evolclaw/aid.md +49 -0
  68. package/kits/docs/evolclaw/ctl.md +46 -0
  69. package/kits/docs/evolclaw/group.md +82 -0
  70. package/kits/docs/evolclaw/msg.md +86 -0
  71. package/kits/docs/evolclaw/rpc.md +35 -0
  72. package/kits/docs/evolclaw/storage.md +49 -0
  73. package/kits/docs/venues/aun-group.md +10 -0
  74. package/kits/docs/venues/aun-private.md +10 -0
  75. package/kits/docs/venues/client-desktop.md +10 -0
  76. package/kits/docs/venues/client-mobile.md +10 -0
  77. package/kits/docs/venues/feishu-group.md +13 -0
  78. package/kits/docs/venues/feishu-private.md +9 -0
  79. package/kits/docs/venues/group.md +11 -0
  80. package/kits/docs/venues/private.md +10 -0
  81. package/kits/eck_manifest.json +75 -39
  82. package/kits/rules/01-overview.md +20 -10
  83. package/kits/rules/05-venue.md +2 -2
  84. package/kits/rules/06-channel.md +30 -27
  85. package/kits/templates/system-fragments/baseagent.md +7 -1
  86. package/kits/templates/system-fragments/channel.md +4 -1
  87. package/kits/templates/system-fragments/identity.md +4 -4
  88. package/kits/templates/system-fragments/relation.md +8 -5
  89. package/kits/templates/system-fragments/session.md +27 -0
  90. package/kits/templates/system-fragments/venue.md +13 -1
  91. package/package.json +13 -6
  92. package/dist/aun/aid/lifecycle-log.js +0 -33
  93. package/dist/net-check.js +0 -640
  94. package/dist/utils/aid-lifecycle-log.js +0 -33
  95. package/dist/watch-msg.js +0 -544
  96. package/kits/docs/evolclaw/AGENT_CMD.md +0 -31
  97. package/kits/docs/evolclaw/MSG_GROUP.md +0 -30
  98. package/kits/docs/evolclaw/MSG_PRIVATE.md +0 -72
  99. package/kits/docs/evolclaw/tools.md +0 -25
  100. package/kits/templates/system-fragments/eckruntime.md +0 -14
package/dist/net-check.js DELETED
@@ -1,640 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import os from 'os';
4
- import net from 'net';
5
- import tls from 'tls';
6
- import dns from 'dns/promises';
7
- import https from 'https';
8
- // @ts-ignore
9
- import { WebSocket } from 'ws';
10
- const GREEN = '\x1b[32m';
11
- const RED = '\x1b[31m';
12
- const YELLOW = '\x1b[33m';
13
- const CYAN = '\x1b[36m';
14
- const DIM = '\x1b[2m';
15
- const BOLD = '\x1b[1m';
16
- const ORANGE = '\x1b[38;5;208m';
17
- const RST = '\x1b[0m';
18
- function ok(msg) { return ` ${GREEN}✓${RST} ${msg}`; }
19
- function fail(msg) { return ` ${RED}✗${RST} ${msg}`; }
20
- function skip(msg) { return ` ${YELLOW}○${RST} ${DIM}${msg}${RST}`; }
21
- function ms(n) { return `${DIM}${n}ms${RST}`; }
22
- function step(n, label) { return `${DIM}[${n}/10]${RST} ${CYAN}${label}${RST}`; }
23
- const isZh = (process.env.LANG || process.env.LC_ALL || process.env.LANGUAGE || Intl.DateTimeFormat().resolvedOptions().locale || '').toLowerCase().startsWith('zh');
24
- const i18n = {
25
- resolve: isZh ? '解析' : 'resolve',
26
- failed: isZh ? '失败' : 'failed',
27
- gateway: isZh ? '网关' : 'gateway',
28
- connect: isZh ? '连接' : 'connect',
29
- tlsFailed: isZh ? 'TLS握手失败' : 'TLS handshake failed',
30
- wsOpen: isZh ? 'WebSocket 连接成功' : 'WebSocket connected',
31
- wsFailed: isZh ? 'WebSocket 升级失败' : 'WebSocket upgrade failed',
32
- authOk: isZh ? '认证成功' : 'auth ok',
33
- authFailed: isZh ? '认证失败' : 'auth failed',
34
- noToken: isZh ? '未返回 token' : 'no token returned',
35
- sessionReady: isZh ? '会话就绪' : 'session ready',
36
- pingOk: isZh ? '响应正常' : 'response ok',
37
- pingFailed: isZh ? '失败' : 'failed',
38
- msgOk: isZh ? '自发自收成功' : 'self-send ok',
39
- msgFailed: isZh ? '消息发送失败' : 'message send failed',
40
- noGateway: isZh ? '响应中无网关地址' : 'no gateway in response',
41
- fetchGwFailed: isZh ? '获取网关失败' : 'fetch gateway failed',
42
- allPassed: isZh ? '全部通过' : 'All checks passed',
43
- checksFailed: isZh ? '项检查失败' : 'check(s) failed',
44
- passed: isZh ? '项通过' : 'passed',
45
- };
46
- // ==================== Network helpers ====================
47
- function httpGet(url, timeoutMs = 8000) {
48
- return new Promise((resolve, reject) => {
49
- const start = Date.now();
50
- const req = https.get(url, { timeout: timeoutMs }, (res) => {
51
- if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
52
- resolve({ status: res.statusCode, body: '', ms: Date.now() - start, redirectUrl: res.headers.location });
53
- return;
54
- }
55
- let body = '';
56
- res.on('data', (chunk) => { body += chunk; });
57
- res.on('end', () => resolve({ status: res.statusCode || 0, body, ms: Date.now() - start }));
58
- });
59
- req.on('timeout', () => { req.destroy(); reject(new Error(`timeout (${timeoutMs}ms)`)); });
60
- req.on('error', (e) => reject(e));
61
- });
62
- }
63
- function dnsResolve(hostname) {
64
- const start = Date.now();
65
- return dns.resolve4(hostname).then(addrs => ({ addrs, ms: Date.now() - start }));
66
- }
67
- function tcpConnect(host, port, timeoutMs = 5000) {
68
- return new Promise((resolve, reject) => {
69
- const start = Date.now();
70
- const sock = net.connect({ host, port, timeout: timeoutMs }, () => {
71
- sock.destroy();
72
- resolve(Date.now() - start);
73
- });
74
- sock.on('timeout', () => { sock.destroy(); reject(new Error(`timeout (${timeoutMs}ms)`)); });
75
- sock.on('error', (e) => { sock.destroy(); reject(e); });
76
- });
77
- }
78
- function tlsConnect(host, port, timeoutMs = 5000) {
79
- return new Promise((resolve, reject) => {
80
- const start = Date.now();
81
- const sock = tls.connect({ host, port, timeout: timeoutMs, servername: host }, () => {
82
- const elapsed = Date.now() - start;
83
- const cipher = sock.getCipher();
84
- const cert = sock.getPeerCertificate();
85
- sock.destroy();
86
- resolve({
87
- ms: elapsed,
88
- protocol: sock.getProtocol() || '?',
89
- cipher: cipher?.name || '?',
90
- certCN: String(cert?.subject?.CN || '?'),
91
- });
92
- });
93
- sock.on('timeout', () => { sock.destroy(); reject(new Error(`timeout (${timeoutMs}ms)`)); });
94
- sock.on('error', (e) => { sock.destroy(); reject(e); });
95
- });
96
- }
97
- function wssConnect(url, timeoutMs = 8000) {
98
- return new Promise((resolve, reject) => {
99
- const start = Date.now();
100
- const ws = new WebSocket(url, { handshakeTimeout: timeoutMs });
101
- const timer = setTimeout(() => { ws.terminate(); reject(new Error(`timeout (${timeoutMs}ms)`)); }, timeoutMs);
102
- ws.on('open', () => { clearTimeout(timer); resolve({ ms: Date.now() - start, ws }); });
103
- ws.on('error', (e) => { clearTimeout(timer); reject(e); });
104
- });
105
- }
106
- function suppressSdkOutput(fn) {
107
- const origWrite = process.stdout.write.bind(process.stdout);
108
- const origErrWrite = process.stderr.write.bind(process.stderr);
109
- process.stdout.write = ((chunk, ...a) => {
110
- if (typeof chunk === 'string' && chunk.includes('[aun_core'))
111
- return true;
112
- return origWrite(chunk, ...a);
113
- });
114
- process.stderr.write = ((chunk, ...a) => {
115
- if (typeof chunk === 'string' && chunk.includes('[aun_core'))
116
- return true;
117
- return origErrWrite(chunk, ...a);
118
- });
119
- return fn().finally(() => {
120
- process.stdout.write = origWrite;
121
- process.stderr.write = origErrWrite;
122
- });
123
- }
124
- // ==================== Check pipeline ====================
125
- async function runCheck(aid, formatJson) {
126
- const results = [];
127
- const log = (r) => {
128
- results.push(r);
129
- if (formatJson)
130
- return;
131
- const prefix = step(r.index, r.step);
132
- if (r.skipped) {
133
- console.log(skip(`${r.step}: ${r.detail}`));
134
- return;
135
- }
136
- console.log(r.ok ? ok(`${prefix} ${r.detail} ${r.ms !== undefined ? ms(r.ms) : ''}`) : fail(`${prefix} ${r.detail}`));
137
- };
138
- // ── Step 1: 解析 AID 域名 ──
139
- const aidDomain = aid;
140
- try {
141
- const d = await dnsResolve(aidDomain);
142
- log({ step: 'DNS (AID)', index: 1, ok: true, detail: `${i18n.resolve} ${aidDomain} → ${d.addrs.join(', ')}`, ms: d.ms });
143
- }
144
- catch (e) {
145
- log({ step: 'DNS (AID)', index: 1, ok: false, detail: `${i18n.resolve} ${aidDomain} ${i18n.failed}: ${e.message}` });
146
- return results;
147
- }
148
- // ── Step 2: 获取网关地址 ──
149
- const wkUrl = `https://${aid}/.well-known/aun-gateway`;
150
- let gatewayUrl;
151
- try {
152
- const resp = await httpGet(wkUrl);
153
- if (resp.status !== 200) {
154
- log({ step: 'Discovery', index: 2, ok: false, detail: `GET ${wkUrl} → HTTP ${resp.status}`, ms: resp.ms });
155
- return results;
156
- }
157
- const data = JSON.parse(resp.body);
158
- gatewayUrl = data?.gateways?.[0]?.url;
159
- if (!gatewayUrl) {
160
- log({ step: 'Discovery', index: 2, ok: false, detail: `GET ${wkUrl} → ${i18n.noGateway}`, ms: resp.ms });
161
- return results;
162
- }
163
- log({ step: 'Discovery', index: 2, ok: true, detail: `GET ${wkUrl} → ${gatewayUrl}`, ms: resp.ms });
164
- }
165
- catch (e) {
166
- log({ step: 'Discovery', index: 2, ok: false, detail: `GET ${wkUrl} → ${e.message}` });
167
- return results;
168
- }
169
- // ── Step 3: 解析网关域名 ──
170
- const gwParsed = new URL(gatewayUrl);
171
- const gwHost = gwParsed.hostname;
172
- const gwPort = parseInt(gwParsed.port || '443', 10);
173
- try {
174
- const d = await dnsResolve(gwHost);
175
- log({ step: 'DNS (GW)', index: 3, ok: true, detail: `${i18n.resolve} ${gwHost} → ${d.addrs.join(', ')}`, ms: d.ms });
176
- }
177
- catch (e) {
178
- log({ step: 'DNS (GW)', index: 3, ok: false, detail: `${i18n.resolve} ${gwHost} ${i18n.failed}: ${e.message}` });
179
- return results;
180
- }
181
- // ── Step 4: TCP 连接网关端口 ──
182
- try {
183
- const elapsed = await tcpConnect(gwHost, gwPort);
184
- log({ step: 'TCP', index: 4, ok: true, detail: `${i18n.connect} ${gwHost}:${gwPort}`, ms: elapsed });
185
- }
186
- catch (e) {
187
- log({ step: 'TCP', index: 4, ok: false, detail: `${i18n.connect} ${gwHost}:${gwPort} ${i18n.failed}: ${e.message}` });
188
- return results;
189
- }
190
- // ── Step 5: TLS 握手 ──
191
- try {
192
- const t = await tlsConnect(gwHost, gwPort);
193
- log({ step: 'TLS', index: 5, ok: true, detail: `${gwHost}:${gwPort} ${t.protocol} ${t.cipher} CN=${t.certCN}`, ms: t.ms });
194
- }
195
- catch (e) {
196
- log({ step: 'TLS', index: 5, ok: false, detail: `${gwHost}:${gwPort} ${i18n.tlsFailed}: ${e.message}` });
197
- return results;
198
- }
199
- // ── Step 6: WebSocket 升级 ──
200
- let ws;
201
- try {
202
- const r = await wssConnect(gatewayUrl);
203
- ws = r.ws;
204
- log({ step: 'WSS', index: 6, ok: true, detail: `${i18n.wsOpen} (${gatewayUrl})`, ms: r.ms });
205
- }
206
- catch (e) {
207
- log({ step: 'WSS', index: 6, ok: false, detail: `${i18n.wsFailed}: ${e.message}` });
208
- return results;
209
- }
210
- finally {
211
- if (ws) {
212
- ws.close();
213
- ws = undefined;
214
- }
215
- }
216
- // ── Step 7: AID 认证 ──
217
- let accessToken;
218
- try {
219
- const start = Date.now();
220
- const aunPath = process.env.AUN_HOME || path.join(os.homedir(), '.aun');
221
- const { AUNClient } = await import('@agentunion/fastaun');
222
- const result = await suppressSdkOutput(async () => {
223
- const client = new AUNClient({ aun_path: aunPath, debug: false });
224
- await client.auth.createAid({ aid });
225
- const authResult = await client.auth.authenticate({ aid });
226
- await client.close().catch(() => { });
227
- return authResult;
228
- });
229
- accessToken = result?.access_token;
230
- const elapsed = Date.now() - start;
231
- if (accessToken) {
232
- log({ step: 'Auth', index: 7, ok: true, detail: `${aid} ${i18n.authOk} (login1→login2→token)`, ms: elapsed });
233
- }
234
- else {
235
- log({ step: 'Auth', index: 7, ok: false, detail: `${aid} ${i18n.authFailed}: ${i18n.noToken}`, ms: elapsed });
236
- return results;
237
- }
238
- }
239
- catch (e) {
240
- log({ step: 'Auth', index: 7, ok: false, detail: `${aid} ${i18n.authFailed}: ${e.message?.slice(0, 120) || String(e)}` });
241
- return results;
242
- }
243
- // ── Step 8: 建立会话 ──
244
- log({ step: 'Session', index: 8, ok: true, detail: i18n.sessionReady });
245
- // ── Step 8.5: agent.md 读取验证 ──
246
- try {
247
- const start = Date.now();
248
- const { agentmdGet } = await import('./aun/aid/index.js');
249
- const result = await suppressSdkOutput(() => agentmdGet(aid, { withVerification: true }));
250
- const elapsed = Date.now() - start;
251
- if (typeof result === 'object' && result.content) {
252
- const fmMatch = result.content.match(/^---\n([\s\S]*?)\n---/);
253
- const nameMatch = fmMatch?.[1]?.match(/^name:\s*["']?(.+?)["']?\s*$/m);
254
- const name = nameMatch?.[1] || aid;
255
- const sigStatus = result.verification?.status || 'unknown';
256
- log({ step: 'AgentMd', index: 8, ok: true, detail: `${name} (sig: ${sigStatus})`, ms: elapsed });
257
- }
258
- else if (typeof result === 'string' && result) {
259
- log({ step: 'AgentMd', index: 8, ok: true, detail: `content loaded (sig: not checked)`, ms: elapsed });
260
- }
261
- else {
262
- log({ step: 'AgentMd', index: 8, ok: false, detail: `agent.md not found`, ms: elapsed });
263
- }
264
- }
265
- catch (e) {
266
- log({ step: 'AgentMd', index: 8, ok: false, detail: `agent.md: ${e.message?.slice(0, 100) || String(e)}` });
267
- }
268
- // ── Step 9: RPC 调用 (meta.ping) ──
269
- try {
270
- const start = Date.now();
271
- const aunPath = process.env.AUN_HOME || path.join(os.homedir(), '.aun');
272
- const { AUNClient } = await import('@agentunion/fastaun');
273
- const sendResult = await suppressSdkOutput(async () => {
274
- const client = new AUNClient({ aun_path: aunPath, debug: false });
275
- await client.auth.createAid({ aid });
276
- const authResult = await client.auth.authenticate({ aid });
277
- const at = authResult?.access_token || client._access_token;
278
- const gw = client._gatewayUrl || authResult?.gateway;
279
- await client.connect({ access_token: at, gateway: gw, connection_kind: 'short' }, { auto_reconnect: false });
280
- const r = await client.call('meta.ping', {});
281
- await client.close().catch(() => { });
282
- return r;
283
- });
284
- const elapsed = Date.now() - start;
285
- log({ step: 'Ping', index: 9, ok: true, detail: `meta.ping ${i18n.pingOk}`, ms: elapsed });
286
- }
287
- catch (e) {
288
- log({ step: 'Ping', index: 9, ok: false, detail: `meta.ping ${i18n.pingFailed}: ${e.message?.slice(0, 100) || String(e)}` });
289
- }
290
- // ── Step 10: Echo 链路追踪 ──
291
- // CLI 模拟 app 发送 echo[nc],向多个目标测试链路
292
- try {
293
- const echoStart = Date.now();
294
- const aunPath = process.env.AUN_HOME || path.join(os.homedir(), '.aun');
295
- const { AUNClient } = await import('@agentunion/fastaun');
296
- // 选择 6 个测试目标,按消息数量、域、有无证书均衡选择
297
- const allAids = await getAidList();
298
- const myDomain = aid.split('.').slice(1).join('.');
299
- const isValidAid = (a) => a.aid.includes('.') && a.aid !== aid;
300
- // 统计每个 AID 的总消息活跃度(遍历所有本地 AID 的 sessions)
301
- const { resolvePaths } = await import('./paths.js');
302
- const p = resolvePaths();
303
- const sessAunDir = path.join(p.sessionsDir, 'aun');
304
- const encode = (s) => s.replace(/[/%\\:*?"<>|]/g, ch => '%' + ch.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0'));
305
- const decode = (s) => s.replace(/%([0-9A-Fa-f]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
306
- function getGlobalMessageCounts() {
307
- const counts = new Map();
308
- try {
309
- const localDirs = fs.readdirSync(sessAunDir, { withFileTypes: true });
310
- for (const localDir of localDirs) {
311
- if (!localDir.isDirectory())
312
- continue;
313
- const localAid = decode(localDir.name);
314
- const peerDirs = fs.readdirSync(path.join(sessAunDir, localDir.name), { withFileTypes: true });
315
- for (const peerDir of peerDirs) {
316
- if (!peerDir.isDirectory())
317
- continue;
318
- const peerId = decode(peerDir.name);
319
- const msgFile = path.join(sessAunDir, localDir.name, peerDir.name, 'messages.jsonl');
320
- try {
321
- const content = fs.readFileSync(msgFile, 'utf-8');
322
- const lineCount = content.split('\n').filter(l => l.trim()).length;
323
- counts.set(peerId, (counts.get(peerId) ?? 0) + lineCount);
324
- counts.set(localAid, (counts.get(localAid) ?? 0) + lineCount);
325
- }
326
- catch { }
327
- }
328
- }
329
- }
330
- catch { }
331
- return counts;
332
- }
333
- const msgCounts = getGlobalMessageCounts();
334
- const sortByActivity = (aids) => {
335
- const sorted = [...aids].sort((a, b) => (msgCounts.get(b.aid) ?? 0) - (msgCounts.get(a.aid) ?? 0));
336
- // Keep top half by activity, shuffle within to add randomness
337
- const topHalf = sorted.slice(0, Math.max(3, Math.ceil(sorted.length / 2)));
338
- shuffle(topHalf);
339
- return [...topHalf, ...sorted.slice(topHalf.length)];
340
- };
341
- const targets = [];
342
- // 分桶
343
- const localSameDomain = allAids.filter(a => a.hasPrivateKey && isValidAid(a) && a.aid.split('.').slice(1).join('.') === myDomain);
344
- const localDiffDomain = allAids.filter(a => a.hasPrivateKey && isValidAid(a) && a.aid.split('.').slice(1).join('.') !== myDomain);
345
- const remoteSameDomain = allAids.filter(a => !a.hasPrivateKey && isValidAid(a) && a.aid.split('.').slice(1).join('.') === myDomain);
346
- const remoteDiffDomain = allAids.filter(a => !a.hasPrivateKey && isValidAid(a) && a.aid.split('.').slice(1).join('.') !== myDomain);
347
- // Round-robin 从各桶取,按消息数优先,直到 6 个
348
- const buckets = [localSameDomain, localDiffDomain, remoteSameDomain, remoteDiffDomain];
349
- let round = 0;
350
- while (targets.length < 6 && round < 6) {
351
- for (const bucket of buckets) {
352
- if (targets.length >= 6)
353
- break;
354
- const sorted = sortByActivity(bucket);
355
- const candidate = sorted.find(a => !targets.includes(a.aid));
356
- if (candidate)
357
- targets.push(candidate.aid);
358
- }
359
- round++;
360
- }
361
- if (targets.length === 0) {
362
- log({ step: 'Echo', index: 10, ok: true, detail: `skipped (no other AID for echo target)`, skipped: true });
363
- return results;
364
- }
365
- if (!formatJson) {
366
- console.log(` ${DIM}[10/10]${RST} ${CYAN}Echo${RST} ${DIM}${targets.length} target(s)${RST}`);
367
- }
368
- const echoResults = [];
369
- // 读取所有目标的 agent.md(含签名验证)获取昵称和类型
370
- const { agentmdGet: agentmdGetFn } = await import('./aun/aid/index.js');
371
- const targetMeta = new Map();
372
- if (!formatJson) {
373
- console.log(` ${DIM}[10/10]${RST} ${CYAN}Echo${RST} ${DIM}reading agent.md for ${targets.length} target(s)...${RST}`);
374
- }
375
- await Promise.all(targets.map(async (t) => {
376
- const start = Date.now();
377
- try {
378
- const result = await suppressSdkOutput(() => agentmdGetFn(t, { withVerification: true }));
379
- const elapsed = Date.now() - start;
380
- if (typeof result === 'object' && result.content) {
381
- const fmMatch = result.content.match(/^---\n([\s\S]*?)\n---/);
382
- const nameMatch = fmMatch?.[1]?.match(/^name:\s*["']?(.+?)["']?\s*$/m);
383
- const typeMatch = fmMatch?.[1]?.match(/^type:\s*["']?(.+?)["']?\s*$/m);
384
- const sigStatus = result.verification?.status || 'unknown';
385
- targetMeta.set(t, {
386
- name: nameMatch?.[1] || shortAid(t),
387
- type: typeMatch?.[1] || 'unknown',
388
- sigStatus,
389
- });
390
- if (!formatJson) {
391
- console.log(` ${GREEN}✓${RST} ${DIM}agentmd${RST} ${shortAid(t)}: ${nameMatch?.[1] || '?'} (${typeMatch?.[1] || '?'}) sig=${sigStatus} ${ms(elapsed)}`);
392
- }
393
- }
394
- else {
395
- targetMeta.set(t, { name: shortAid(t), type: 'unknown', sigStatus: 'none' });
396
- if (!formatJson) {
397
- console.log(` ${YELLOW}○${RST} ${DIM}agentmd${RST} ${shortAid(t)}: no content ${ms(elapsed)}`);
398
- }
399
- }
400
- }
401
- catch (e) {
402
- targetMeta.set(t, { name: shortAid(t), type: 'unknown', sigStatus: 'error' });
403
- if (!formatJson) {
404
- console.log(` ${RED}✗${RST} ${DIM}agentmd${RST} ${shortAid(t)}: ${e.message?.slice(0, 60) || String(e)}`);
405
- }
406
- }
407
- }));
408
- if (!formatJson)
409
- console.log('');
410
- // 构建目标标记
411
- const aidMap = new Map(allAids.map(a => [a.aid, a]));
412
- function targetLabel(t) {
413
- const entry = aidMap.get(t);
414
- const meta = targetMeta.get(t);
415
- const keyIcon = entry?.hasPrivateKey ? '🔑' : '🌐';
416
- const typeIcon = meta.type === 'human' ? '👤' : '🤖';
417
- return `${keyIcon}${typeIcon} ${meta.name}`;
418
- }
419
- for (const target of targets) {
420
- const targetStart = Date.now();
421
- const label = targetLabel(target);
422
- try {
423
- const replyText = await suppressSdkOutput(async () => {
424
- const client = new AUNClient({ aun_path: aunPath, debug: false });
425
- await client.auth.createAid({ aid });
426
- const authResult = await client.auth.authenticate({ aid });
427
- const at = authResult?.access_token || client._access_token;
428
- const gw = client._gatewayUrl || authResult?.gateway;
429
- await client.connect({ access_token: at, gateway: gw, slot_id: 'net-check', connection_kind: 'short' }, { auto_reconnect: false });
430
- // 取基线 seq
431
- const baseline = await client.call('message.pull', { limit: 100 });
432
- const baselineSeq = baseline?.latest_seq ?? 0;
433
- if (baselineSeq > 0) {
434
- await client.call('message.ack', { seq: baselineSeq });
435
- }
436
- await client.call('message.send', {
437
- to: target,
438
- payload: { type: 'text', text: 'echo[nc]' },
439
- encrypt: false,
440
- });
441
- await new Promise(r => setTimeout(r, 1500));
442
- const pullResult = await client.call('message.pull', { after_seq: baselineSeq, limit: 10 });
443
- await client.close().catch(() => { });
444
- const messages = pullResult?.messages || [];
445
- const reply = messages.find((m) => m.from === target && m.payload?.text?.includes('[EvolClaw.'));
446
- return reply?.payload?.text || null;
447
- });
448
- const elapsed = Date.now() - targetStart;
449
- if (replyText) {
450
- echoResults.push({ target, ok: true, detail: `reply received (${elapsed}ms)`, replyText });
451
- if (!formatJson) {
452
- console.log(` ${GREEN}✓${RST} ${label} ${DIM}${target}${RST} ${ms(elapsed)}`);
453
- printTrace(replyText);
454
- }
455
- }
456
- else {
457
- echoResults.push({ target, ok: false, detail: `no reply in ${elapsed}ms` });
458
- if (!formatJson) {
459
- console.log(` ${RED}✗${RST} ${label} ${DIM}${target}${RST} no reply ${ms(elapsed)}`);
460
- }
461
- }
462
- }
463
- catch (e) {
464
- echoResults.push({ target, ok: false, detail: e.message?.slice(0, 100) || String(e) });
465
- if (!formatJson) {
466
- console.log(` ${RED}✗${RST} ${label} ${DIM}${target}${RST} ${e.message?.slice(0, 80)}`);
467
- }
468
- }
469
- }
470
- const elapsed = Date.now() - echoStart;
471
- const okCount = echoResults.filter(r => r.ok).length;
472
- const allOk = echoResults.every(r => r.ok);
473
- const anyOk = echoResults.some(r => r.ok);
474
- results.push({
475
- step: 'Echo',
476
- index: 10,
477
- ok: anyOk,
478
- detail: `${okCount}/${echoResults.length} targets replied`,
479
- ms: elapsed,
480
- });
481
- }
482
- catch (e) {
483
- log({ step: 'Echo', index: 10, ok: false, detail: `echo ${i18n.failed}: ${e.message?.slice(0, 100) || String(e)}` });
484
- }
485
- return results;
486
- }
487
- // 添加这个常量到 ANSI 块(如果还没有)
488
- function printTrace(text) {
489
- const traceLines = text.split('\n').filter((l) => /^\d{2}:\d{2}:\d{2}\.\d{3}/.test(l));
490
- if (traceLines.length === 0)
491
- return;
492
- const parsed = [];
493
- for (const line of traceLines) {
494
- const match = line.match(/^(\d{2}):(\d{2}):(\d{2})\.(\d{3})\s+\[([^\]]+)\]\s*(.*)/);
495
- if (match) {
496
- const [, hh, mm, ss, mmm, node, info] = match;
497
- const t = (+hh * 3600 + +mm * 60 + +ss) * 1000 + +mmm;
498
- // 提取 from/to/aid 关键字段
499
- const fromMatch = info.match(/from=(\S+?)(?:\s|$|,)/);
500
- const toMatch = info.match(/to=(\S+?)(?:\s|$|,)/);
501
- const aidMatch = info.match(/(?:^|\s)aid=(\S+?)(?:\s|$|,)/);
502
- const selfMatch = info.match(/self=(\S+?)(?:\s|$|,)/);
503
- let summary = '';
504
- if (fromMatch && toMatch)
505
- summary = `${shortAid(fromMatch[1])}→${shortAid(toMatch[1])}`;
506
- else if (aidMatch)
507
- summary = `aid=${shortAid(aidMatch[1])}`;
508
- else if (selfMatch)
509
- summary = `self=${shortAid(selfMatch[1])}`;
510
- parsed.push({ label: `[${node}]`, ts: t, info: summary });
511
- }
512
- }
513
- if (parsed.length < 2)
514
- return;
515
- console.log(`${DIM} ── trace ──${RST}`);
516
- let totalLocal = 0;
517
- for (let i = 0; i < parsed.length; i++) {
518
- let delta = i > 0 ? parsed[i].ts - parsed[i - 1].ts : 0;
519
- while (delta > 43200000)
520
- delta -= 86400000;
521
- while (delta < -43200000)
522
- delta += 86400000;
523
- const isLocal = Math.abs(delta) <= 60000;
524
- const deltaStr = i > 0 ? (isLocal ? `+${delta}ms` : `~`) : '';
525
- if (i > 0 && isLocal)
526
- totalLocal += Math.max(0, delta);
527
- const infoStr = parsed[i].info ? ` ${DIM}${parsed[i].info}${RST}` : '';
528
- console.log(`${DIM} ${parsed[i].label}${RST}${infoStr} ${DIM}${deltaStr}${RST}`);
529
- }
530
- console.log(`${DIM} ── local-side total: ${totalLocal}ms (~ = cross-tz hop) ──${RST}`);
531
- }
532
- function shortAid(aid) {
533
- return aid.split('.')[0];
534
- }
535
- async function getAidList() {
536
- const { aidList } = await import('./aun/aid/index.js');
537
- return aidList();
538
- }
539
- function pickDefaultAids(aids) {
540
- const withKey = aids.filter(a => a.hasPrivateKey && a.aid.includes('.'));
541
- const withoutKey = aids.filter(a => !a.hasPrivateKey && a.aid.includes('.'));
542
- const picked = [];
543
- // Prefer AIDs with agentMd (actively used), shuffle within group for randomness
544
- const active = withKey.filter(a => a.hasAgentMd);
545
- const inactive = withKey.filter(a => !a.hasAgentMd);
546
- shuffle(active);
547
- shuffle(inactive);
548
- const sorted = [...active, ...inactive];
549
- for (const a of sorted.slice(0, 2))
550
- picked.push(a.aid);
551
- // 1 without private key (random pick)
552
- shuffle(withoutKey);
553
- if (withoutKey.length > 0)
554
- picked.push(withoutKey[0].aid);
555
- return picked;
556
- }
557
- function shuffle(arr) {
558
- for (let i = arr.length - 1; i > 0; i--) {
559
- const j = Math.floor(Math.random() * (i + 1));
560
- [arr[i], arr[j]] = [arr[j], arr[i]];
561
- }
562
- return arr;
563
- }
564
- export async function cmdNet(args) {
565
- const sub = args[0];
566
- const formatJson = args.includes('--format') && args.includes('json');
567
- if (sub === 'help' || sub === '--help' || sub === '-h') {
568
- console.log(`用法: evolclaw net check [<aid>] [--format json]
569
-
570
- 检查 AUN 网络链路连通性(10 步逐层诊断)。
571
-
572
- 步骤:
573
- 1. DNS (AID) AID 域名解析
574
- 2. Discovery .well-known/aun-gateway 获取网关地址
575
- 3. DNS (Gateway) 网关域名解析
576
- 4. TCP 网关端口连接
577
- 5. TLS TLS 握手 + 证书验证
578
- 6. WSS WebSocket 升级握手
579
- 7. Auth AID 认证(login1 + login2 → token)
580
- 8. Session 会话建立
581
- 9. Ping meta.ping RPC
582
- 10. Message self-to-self 消息收发
583
-
584
- 参数:
585
- <aid> 要检查的 AID(可选,默认取前 3 个本地 AID)
586
-
587
- 选项:
588
- --format json JSON 格式输出`);
589
- return;
590
- }
591
- if (sub && sub !== 'check' && !sub.startsWith('-') && !sub.includes('.')) {
592
- console.error(`❌ 未知子命令: net ${sub}`);
593
- console.error('用法: evolclaw net check [<aid>]');
594
- process.exit(1);
595
- }
596
- let targetAids = [];
597
- for (const a of args) {
598
- if (a.includes('.') && !a.startsWith('-')) {
599
- targetAids.push(a);
600
- }
601
- }
602
- if (targetAids.length === 0) {
603
- const aids = await getAidList();
604
- if (aids.length === 0) {
605
- if (formatJson) {
606
- console.log(JSON.stringify({ ok: false, error: 'No local AIDs found' }));
607
- }
608
- else {
609
- console.error('❌ 未找到本地 AID,请先创建: evolclaw aid new');
610
- }
611
- process.exit(1);
612
- }
613
- targetAids = pickDefaultAids(aids);
614
- }
615
- const allResults = [];
616
- for (const targetAid of targetAids) {
617
- if (!formatJson) {
618
- console.log(`\n${BOLD}── ${targetAid} ──${RST}\n`);
619
- }
620
- const results = await runCheck(targetAid, formatJson);
621
- allResults.push({ aid: targetAid, checks: results });
622
- }
623
- if (formatJson) {
624
- const allOk = allResults.every(r => r.checks.every(c => c.ok || c.skipped));
625
- console.log(JSON.stringify({ ok: allOk, results: allResults }));
626
- }
627
- else {
628
- console.log('');
629
- const passed = allResults.reduce((n, r) => n + r.checks.filter(c => c.ok).length, 0);
630
- const failed = allResults.reduce((n, r) => n + r.checks.filter(c => !c.ok && !c.skipped).length, 0);
631
- if (failed === 0) {
632
- console.log(`${GREEN}${BOLD}${i18n.allPassed} (${passed})${RST}`);
633
- }
634
- else {
635
- console.log(`${RED}${BOLD}${failed} ${i18n.checksFailed}${RST}, ${passed} ${i18n.passed}`);
636
- }
637
- }
638
- const exitOk = allResults.every(r => r.checks.every(c => c.ok || c.skipped));
639
- process.exit(exitOk ? 0 : 1);
640
- }
@@ -1,33 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { resolvePaths } from '../paths.js';
4
- function ensureDir(dir) {
5
- fs.mkdirSync(dir, { recursive: true });
6
- }
7
- function logPath(aid) {
8
- const aidName = aid.startsWith('@') ? aid.slice(1) : aid;
9
- return path.join(resolvePaths().aidLogsDir, `${aidName}.jsonl`);
10
- }
11
- export function appendAidLifecycle(event) {
12
- const filePath = logPath(event.aid);
13
- ensureDir(path.dirname(filePath));
14
- fs.appendFileSync(filePath, JSON.stringify(event) + '\n');
15
- }
16
- export function readAidLifecycle(aid, lastN = 50) {
17
- const filePath = logPath(aid);
18
- try {
19
- const content = fs.readFileSync(filePath, 'utf-8');
20
- const lines = content.trim().split('\n').filter(Boolean);
21
- const events = [];
22
- for (const line of lines.slice(-lastN)) {
23
- try {
24
- events.push(JSON.parse(line));
25
- }
26
- catch { }
27
- }
28
- return events;
29
- }
30
- catch {
31
- return [];
32
- }
33
- }