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.
Files changed (38) hide show
  1. package/CHANGELOG.md +17 -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/kit-renderer.js +35 -21
  6. package/dist/aun/aid/agentmd.js +25 -54
  7. package/dist/aun/aid/client.js +22 -7
  8. package/dist/aun/aid/identity.js +314 -28
  9. package/dist/aun/aid/index.js +1 -1
  10. package/dist/aun/rpc/connection.js +8 -13
  11. package/dist/channels/aun.js +31 -72
  12. package/dist/cli/agent.js +15 -22
  13. package/dist/cli/bench.js +8 -14
  14. package/dist/cli/help.js +23 -0
  15. package/dist/cli/index.js +371 -36
  16. package/dist/cli/init-channel.js +2 -3
  17. package/dist/cli/link-rules.js +2 -1
  18. package/dist/cli/net-check.js +10 -11
  19. package/dist/core/command-handler.js +6 -7
  20. package/dist/core/message/message-processor.js +19 -18
  21. package/dist/core/relation/peer-identity.js +64 -21
  22. package/dist/core/session/session-manager.js +6 -2
  23. package/dist/core/trigger/manager.js +37 -0
  24. package/dist/index.js +4 -1
  25. package/dist/paths.js +87 -16
  26. package/dist/utils/npm-ops.js +18 -11
  27. package/kits/eck_manifest.json +8 -8
  28. package/kits/rules/05-venue.md +2 -2
  29. package/kits/templates/system-fragments/baseagent.md +7 -1
  30. package/kits/templates/system-fragments/channel.md +4 -1
  31. package/kits/templates/system-fragments/identity.md +4 -4
  32. package/kits/templates/system-fragments/relation.md +8 -5
  33. package/kits/templates/system-fragments/session.md +20 -0
  34. package/kits/templates/system-fragments/venue.md +4 -1
  35. package/package.json +4 -2
  36. package/dist/net-check.js +0 -640
  37. package/dist/watch-msg.js +0 -544
  38. package/kits/templates/system-fragments/eckruntime.md +0 -14
@@ -1,56 +1,193 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import os from 'os';
4
3
  import crypto from 'crypto';
5
- import { getAunClient, downloadCaRoot } from './client.js';
6
- import { resolvePaths, aidsDir as evolclawAidsDir, agentMdPath } from '../../paths.js';
4
+ import { getAunClient, createAunClient, downloadCaRoot } from './client.js';
5
+ import { resolvePaths, agentMdPath, aunPath as defaultAunPath } from '../../paths.js';
7
6
  // ==================== Validation ====================
8
7
  export function isValidAid(name) {
9
8
  const labels = name.split('.');
10
9
  return labels.length >= 3 && labels.every(l => /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/.test(l));
11
10
  }
11
+ /**
12
+ * 根据扫描得到的状态推断 AID 分类。
13
+ * - hasPrivateKey + signVerified=true → mine
14
+ * - hasPrivateKey + (signVerified=false 或未实测) → broken
15
+ * - !hasPrivateKey + hasCert → peer-cert
16
+ * - !hasPrivateKey + !hasCert → no-cert
17
+ */
18
+ function categorizeAid(info) {
19
+ if (info.hasPrivateKey) {
20
+ if (info.signVerified === true)
21
+ return 'mine';
22
+ return 'broken';
23
+ }
24
+ return info.hasCert ? 'peer-cert' : 'no-cert';
25
+ }
12
26
  // ==================== AID Operations ====================
13
27
  export function aidList(aunPath) {
14
- const aunAidsDir = path.join(aunPath ?? path.join(os.homedir(), '.aun'), 'AIDs');
15
- const ecAidsDir = evolclawAidsDir();
28
+ const root = aunPath ?? defaultAunPath();
29
+ const aunAidsDir = path.join(root, 'AIDs');
16
30
  const seen = new Map();
17
- // Scan ~/.aun/AIDs (private keys live here)
18
31
  if (fs.existsSync(aunAidsDir)) {
19
32
  for (const e of fs.readdirSync(aunAidsDir, { withFileTypes: true })) {
20
33
  if (!e.isDirectory())
21
34
  continue;
35
+ const aidDir = path.join(aunAidsDir, e.name);
36
+ const keyJsonPath = path.join(aidDir, 'private', 'key.json');
37
+ const hasPrivateKey = fs.existsSync(path.join(aidDir, 'private'));
38
+ const certPath = path.join(aidDir, 'public', 'cert.pem');
39
+ let hasCert = false;
40
+ let certExpired = false;
41
+ let keyMatchesCert = null;
42
+ if (fs.existsSync(certPath)) {
43
+ hasCert = true;
44
+ try {
45
+ const certPem = fs.readFileSync(certPath, 'utf-8');
46
+ const x509 = new crypto.X509Certificate(certPem);
47
+ certExpired = new Date(x509.validTo) < new Date();
48
+ if (hasPrivateKey && fs.existsSync(keyJsonPath)) {
49
+ try {
50
+ const kp = JSON.parse(fs.readFileSync(keyJsonPath, 'utf-8'));
51
+ const localPubB64 = typeof kp?.public_key_der_b64 === 'string' ? kp.public_key_der_b64 : '';
52
+ if (localPubB64) {
53
+ const certPubDer = x509.publicKey.export({ type: 'spki', format: 'der' });
54
+ const localPubDer = Buffer.from(localPubB64, 'base64');
55
+ keyMatchesCert = certPubDer.equals(localPubDer);
56
+ }
57
+ }
58
+ catch { /* key.json 不可解析视作不匹配 */
59
+ keyMatchesCert = false;
60
+ }
61
+ }
62
+ }
63
+ catch {
64
+ // 证书无法解析视为过期 / 不可用
65
+ certExpired = true;
66
+ }
67
+ }
22
68
  seen.set(e.name, {
23
69
  aid: e.name,
24
- hasPrivateKey: fs.existsSync(path.join(aunAidsDir, e.name, 'private')),
70
+ category: categorizeAid({
71
+ hasPrivateKey,
72
+ hasCert,
73
+ // 静态扫描没跑实测,用 canSign 作为 mine/broken 的近似判定
74
+ signVerified: hasPrivateKey ? (hasCert && !certExpired && keyMatchesCert === true) : null,
75
+ canSign: hasPrivateKey && hasCert && !certExpired && keyMatchesCert === true,
76
+ }),
77
+ hasPrivateKey,
25
78
  hasAgentMd: fs.existsSync(agentMdPath(e.name)),
79
+ hasCert,
80
+ certExpired,
81
+ keyMatchesCert,
82
+ canSign: hasPrivateKey && hasCert && !certExpired && keyMatchesCert === true,
83
+ signVerified: null,
26
84
  });
27
85
  }
28
86
  }
29
- // Scan $EVOLCLAW_HOME/AIDs (agent.md lives here)
30
- if (fs.existsSync(ecAidsDir) && ecAidsDir !== aunAidsDir) {
31
- for (const e of fs.readdirSync(ecAidsDir, { withFileTypes: true })) {
32
- if (!e.isDirectory())
33
- continue;
34
- if (seen.has(e.name))
87
+ return [...seen.values()];
88
+ }
89
+ // ==================== Sign Self-Test ====================
90
+ /**
91
+ * 实跑一次本地 sign + verify,验证 AID 是否真能签名/验签。
92
+ * 全本地(不联网):用 SDK 解密私钥 → ECDSA 签 payload → 用本地 cert 公钥验。
93
+ * 任一环节失败都视为不可签——包括私钥 passphrase 解不开、cert 被 SDK 主动 discard 等。
94
+ */
95
+ export async function verifySignAbility(aid, opts) {
96
+ const aunPath = opts?.aunPath ?? defaultAunPath();
97
+ let ownClient = null;
98
+ try {
99
+ let client = opts?.client;
100
+ if (!client) {
101
+ client = await createAunClient({ aunPath });
102
+ ownClient = client;
103
+ }
104
+ const probe = `# probe\naid: "${aid}"\n`;
105
+ let signed;
106
+ try {
107
+ signed = await client.auth.signAgentMd(probe, { aid });
108
+ }
109
+ catch (e) {
110
+ return { ok: false, reason: `sign failed: ${String(e?.message || e).slice(0, 120)}` };
111
+ }
112
+ const certPath = path.join(aunPath, 'AIDs', aid, 'public', 'cert.pem');
113
+ const certPem = fs.existsSync(certPath) ? fs.readFileSync(certPath, 'utf-8') : '';
114
+ if (!certPem)
115
+ return { ok: false, reason: 'cert.pem missing' };
116
+ let result;
117
+ try {
118
+ result = await client.auth.verifyAgentMd(signed, { aid, certPem });
119
+ }
120
+ catch (e) {
121
+ return { ok: false, reason: `verify threw: ${String(e?.message || e).slice(0, 120)}` };
122
+ }
123
+ const verified = result?.status === 'verified' || result?.verified === true;
124
+ if (verified)
125
+ return { ok: true };
126
+ return { ok: false, reason: `verify failed: ${result?.status ?? 'unknown'}${result?.reason ? ' — ' + result.reason : ''}` };
127
+ }
128
+ finally {
129
+ if (ownClient) {
130
+ try {
131
+ await ownClient.close();
132
+ }
133
+ catch { /* ignore */ }
134
+ }
135
+ }
136
+ }
137
+ /**
138
+ * aidList 的"实测版":先做静态扫描,再对每个 AID 跑一次本地 sign+verify。
139
+ * 共用同一个 AUNClient 实例,避免重复初始化 secret-store / sqlite。
140
+ */
141
+ export async function aidListVerified(aunPath) {
142
+ const list = aidList(aunPath);
143
+ const root = aunPath ?? defaultAunPath();
144
+ const client = await createAunClient({ aunPath: root });
145
+ try {
146
+ for (const a of list) {
147
+ // canSign=false 的 AID 不必跑实测,结论已经明确
148
+ if (!a.canSign) {
149
+ a.signVerified = false;
150
+ if (!a.hasPrivateKey)
151
+ a.signError = 'no private key';
152
+ else if (!a.hasCert)
153
+ a.signError = 'no cert';
154
+ else if (a.certExpired)
155
+ a.signError = 'cert expired';
156
+ else if (a.keyMatchesCert === false)
157
+ a.signError = 'key/cert public-key mismatch';
158
+ a.category = categorizeAid({
159
+ hasPrivateKey: a.hasPrivateKey, hasCert: a.hasCert,
160
+ signVerified: a.signVerified, canSign: a.canSign,
161
+ });
35
162
  continue;
36
- seen.set(e.name, {
37
- aid: e.name,
38
- hasPrivateKey: fs.existsSync(path.join(aunAidsDir, e.name, 'private')),
39
- hasAgentMd: fs.existsSync(agentMdPath(e.name)),
163
+ }
164
+ const r = await verifySignAbility(a.aid, { aunPath: root, client });
165
+ a.signVerified = r.ok;
166
+ if (!r.ok)
167
+ a.signError = r.reason;
168
+ a.category = categorizeAid({
169
+ hasPrivateKey: a.hasPrivateKey, hasCert: a.hasCert,
170
+ signVerified: a.signVerified, canSign: a.canSign,
40
171
  });
41
172
  }
42
173
  }
43
- return [...seen.values()];
174
+ finally {
175
+ try {
176
+ await client.close();
177
+ }
178
+ catch { /* ignore */ }
179
+ }
180
+ return list;
44
181
  }
45
182
  export async function aidCreate(aid, opts) {
46
- const aunPath = opts?.aunPath ?? path.join(os.homedir(), '.aun');
183
+ const aunPath = opts?.aunPath ?? defaultAunPath();
47
184
  const aidDir = path.join(aunPath, 'AIDs', aid);
48
185
  if (fs.existsSync(aidDir) && fs.existsSync(path.join(aidDir, 'private'))) {
49
186
  const client = await getAunClient(aid, { aunPath });
50
187
  return { aid, alreadyExisted: true, gateway: '', client };
51
188
  }
52
- const { AUNClient, GatewayDiscovery } = await import('@agentunion/fastaun');
53
- let client = new AUNClient({ aun_path: aunPath });
189
+ const { GatewayDiscovery } = await import('@agentunion/fastaun');
190
+ let client = await createAunClient({ aunPath });
54
191
  try {
55
192
  const result = await client.auth.createAid({ aid });
56
193
  const gateway = result.gateway || '';
@@ -61,7 +198,7 @@ export async function aidCreate(aid, opts) {
61
198
  await client.close();
62
199
  }
63
200
  catch { /* ignore */ }
64
- client = new AUNClient({ aun_path: aunPath, root_ca_path: caCertPath });
201
+ client = await createAunClient({ aunPath });
65
202
  await client.auth.createAid({ aid });
66
203
  }
67
204
  let gatewayUrl = gateway;
@@ -86,34 +223,183 @@ export async function aidCreate(aid, opts) {
86
223
  }
87
224
  }
88
225
  // ==================== Show ====================
89
- export function aidShow(aid, opts) {
90
- const aunPath = opts?.aunPath ?? path.join(os.homedir(), '.aun');
226
+ export async function aidShow(aid, opts) {
227
+ const aunPath = opts?.aunPath ?? defaultAunPath();
91
228
  const aidDir = path.join(aunPath, 'AIDs', aid);
92
229
  const hasPrivateKey = fs.existsSync(path.join(aidDir, 'private'));
93
230
  const hasAgentMd = fs.existsSync(agentMdPath(aid));
94
231
  let certExpiresAt = null;
95
232
  let certSubject = null;
233
+ let certExpired = false;
234
+ let certPem = null;
235
+ let keyMatchesCert = null;
96
236
  const certPath = path.join(aidDir, 'public', 'cert.pem');
97
237
  if (fs.existsSync(certPath)) {
98
238
  try {
99
- const pem = fs.readFileSync(certPath, 'utf-8');
100
- const x509 = new crypto.X509Certificate(pem);
239
+ certPem = fs.readFileSync(certPath, 'utf-8');
240
+ const x509 = new crypto.X509Certificate(certPem);
101
241
  certExpiresAt = x509.validTo;
102
242
  certSubject = x509.subject;
243
+ certExpired = new Date(x509.validTo) < new Date();
244
+ const keyJsonPath = path.join(aidDir, 'private', 'key.json');
245
+ if (hasPrivateKey && fs.existsSync(keyJsonPath)) {
246
+ try {
247
+ const kp = JSON.parse(fs.readFileSync(keyJsonPath, 'utf-8'));
248
+ const localPubB64 = typeof kp?.public_key_der_b64 === 'string' ? kp.public_key_der_b64 : '';
249
+ if (localPubB64) {
250
+ const certPubDer = x509.publicKey.export({ type: 'spki', format: 'der' });
251
+ const localPubDer = Buffer.from(localPubB64, 'base64');
252
+ keyMatchesCert = certPubDer.equals(localPubDer);
253
+ }
254
+ }
255
+ catch {
256
+ keyMatchesCert = false;
257
+ }
258
+ }
103
259
  }
104
260
  catch { /* ignore parse errors */ }
105
261
  }
106
- return { aid, hasPrivateKey, hasAgentMd, certExpiresAt, certSubject };
262
+ let agentMdSignature = 'unknown';
263
+ let agentMdSignatureReason;
264
+ let signVerified = null;
265
+ let signError;
266
+ // 先做一次签名自检(共享 client,避免重复起 SDK)
267
+ const client = await createAunClient({ aunPath });
268
+ try {
269
+ if (hasPrivateKey && certPem && !certExpired && keyMatchesCert !== false) {
270
+ const r = await verifySignAbility(aid, { aunPath, client });
271
+ signVerified = r.ok;
272
+ if (!r.ok)
273
+ signError = r.reason;
274
+ }
275
+ else {
276
+ signVerified = false;
277
+ if (!hasPrivateKey)
278
+ signError = 'no private key';
279
+ else if (!certPem)
280
+ signError = 'no cert';
281
+ else if (certExpired)
282
+ signError = 'cert expired';
283
+ else if (keyMatchesCert === false)
284
+ signError = 'key/cert public-key mismatch';
285
+ }
286
+ if (hasAgentMd) {
287
+ try {
288
+ const content = fs.readFileSync(agentMdPath(aid), 'utf-8');
289
+ if (!content.includes('AUN-SIGNATURE')) {
290
+ agentMdSignature = 'unsigned';
291
+ }
292
+ else {
293
+ const result = await client.auth.verifyAgentMd(content, { aid, ...(certPem ? { certPem } : {}) });
294
+ if (result.status === 'verified' || result.verified) {
295
+ agentMdSignature = 'verified';
296
+ }
297
+ else if (result.status === 'unsigned') {
298
+ agentMdSignature = 'unsigned';
299
+ }
300
+ else {
301
+ agentMdSignature = 'invalid';
302
+ agentMdSignatureReason = result.reason;
303
+ }
304
+ }
305
+ }
306
+ catch (e) {
307
+ agentMdSignature = 'unknown';
308
+ agentMdSignatureReason = String(e?.message || e).slice(0, 100);
309
+ }
310
+ }
311
+ }
312
+ finally {
313
+ try {
314
+ await client.close();
315
+ }
316
+ catch { }
317
+ }
318
+ return { aid, hasPrivateKey, hasAgentMd, certExpiresAt, certSubject, certExpired, keyMatchesCert, signVerified, signError, agentMdSignature, agentMdSignatureReason };
107
319
  }
108
320
  // ==================== Delete ====================
109
321
  export function aidDelete(aid, opts) {
110
- const aunPath = opts?.aunPath ?? path.join(os.homedir(), '.aun');
322
+ const aunPath = opts?.aunPath ?? defaultAunPath();
111
323
  const aidDir = path.join(aunPath, 'AIDs', aid);
112
324
  if (!fs.existsSync(aidDir))
113
325
  return false;
114
326
  fs.rmSync(aidDir, { recursive: true, force: true });
115
327
  return true;
116
328
  }
329
+ export async function probePkiRecoverability(aid, opts) {
330
+ const aunPath = opts?.aunPath ?? defaultAunPath();
331
+ const timeoutMs = opts?.timeoutMs ?? 8000;
332
+ const keyJsonPath = path.join(aunPath, 'AIDs', aid, 'private', 'key.json');
333
+ if (!fs.existsSync(keyJsonPath))
334
+ return { kind: 'no-key' };
335
+ let localPubB64 = '';
336
+ try {
337
+ const kp = JSON.parse(fs.readFileSync(keyJsonPath, 'utf-8'));
338
+ if (typeof kp?.public_key_der_b64 !== 'string' || !kp.public_key_der_b64) {
339
+ return { kind: 'unknown', reason: 'key.json missing public_key_der_b64' };
340
+ }
341
+ localPubB64 = kp.public_key_der_b64;
342
+ }
343
+ catch (e) {
344
+ return { kind: 'unknown', reason: `key.json parse failed: ${String(e?.message || e).slice(0, 80)}` };
345
+ }
346
+ // 1. 发现网关
347
+ let gateway = '';
348
+ try {
349
+ const ctl = AbortSignal.timeout(timeoutMs);
350
+ const gwResp = await fetch(`https://${aid}/.well-known/aun-gateway`, { redirect: 'follow', signal: ctl });
351
+ if (gwResp.ok) {
352
+ const text = (await gwResp.text()).trim();
353
+ try {
354
+ const parsed = JSON.parse(text);
355
+ gateway = parsed?.gateways?.[0]?.url || text;
356
+ }
357
+ catch {
358
+ gateway = text;
359
+ }
360
+ }
361
+ }
362
+ catch (e) {
363
+ return { kind: 'no-server-record', reason: `gateway discovery failed: ${String(e?.message || e).slice(0, 80)}` };
364
+ }
365
+ if (!gateway)
366
+ return { kind: 'no-server-record', reason: 'no gateway for AID' };
367
+ // 2. 拉云端 cert
368
+ let certPem = '';
369
+ try {
370
+ const parsed = new URL(gateway);
371
+ const scheme = parsed.protocol === 'wss:' ? 'https:' : 'http:';
372
+ const certUrl = `${scheme}//${parsed.host}/pki/cert/${encodeURIComponent(aid)}`;
373
+ const ctl = AbortSignal.timeout(timeoutMs);
374
+ const resp = await fetch(certUrl, { redirect: 'follow', signal: ctl });
375
+ if (!resp.ok) {
376
+ return { kind: 'no-server-record', reason: `pki/cert HTTP ${resp.status}` };
377
+ }
378
+ certPem = (await resp.text()).trim();
379
+ if (!certPem.includes('BEGIN CERTIFICATE')) {
380
+ return { kind: 'no-server-record', reason: 'pki/cert returned non-cert content' };
381
+ }
382
+ }
383
+ catch (e) {
384
+ return { kind: 'no-server-record', reason: `pki/cert fetch failed: ${String(e?.message || e).slice(0, 80)}` };
385
+ }
386
+ // 3. 比对公钥
387
+ try {
388
+ const x509 = new crypto.X509Certificate(certPem);
389
+ const certPubDer = x509.publicKey.export({ type: 'spki', format: 'der' });
390
+ const localPubDer = Buffer.from(localPubB64, 'base64');
391
+ if (certPubDer.equals(localPubDer)) {
392
+ return { kind: 'recoverable', serverCertPubB64: certPubDer.toString('base64') };
393
+ }
394
+ return {
395
+ kind: 'unrecoverable',
396
+ reason: 'server has different public key registered, current local private key cannot be used',
397
+ };
398
+ }
399
+ catch (e) {
400
+ return { kind: 'unknown', reason: `cert parse failed: ${String(e?.message || e).slice(0, 80)}` };
401
+ }
402
+ }
117
403
  // ==================== Lookup ====================
118
404
  export async function aidLookup(aid) {
119
405
  let gateway = '';
@@ -1,3 +1,3 @@
1
- export { isValidAid, aidList, aidCreate, aidShow, aidDelete, aidLookup, appendAidLifecycle, readAidLifecycle } from './identity.js';
1
+ export { isValidAid, aidList, aidListVerified, aidCreate, aidShow, aidDelete, aidLookup, verifySignAbility, probePkiRecoverability, appendAidLifecycle, readAidLifecycle } from './identity.js';
2
2
  export { buildInitialAgentMd, agentmdGet, agentmdPut, agentmdSync } from './agentmd.js';
3
3
  export { MIN_AUN_CORE_SDK, AUN_CORE_SDK_PKG, isAunSdkVersionOk, resolveAunCoreSdkPkg, ensureAunSdk, isAunSdkReady, downloadCaRoot, getAunClient, suppressSdkLogs, } from './client.js';
@@ -1,18 +1,13 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import os from 'os';
1
+ import { aunPath as defaultAunPath } from '../../paths.js';
2
+ import { createAunClient } from '../aid/client.js';
3
+ import { loadProcessConfig } from '../../config-store.js';
4
4
  export async function createShortConnection(aid, opts) {
5
- const aunPath = opts?.aunPath ?? path.join(os.homedir(), '.aun');
5
+ const aunPath = opts?.aunPath ?? defaultAunPath();
6
6
  const slotId = opts?.slotId ?? '';
7
- const caCertPath = path.join(aunPath, 'CA', 'root', 'root.crt');
8
- const { AUNClient } = await import('@agentunion/fastaun');
9
- const encryptionSeed = process.env.AUN_ENCRYPTION_SEED || undefined;
10
- const clientOpts = { aun_path: aunPath, debug: false };
11
- if (fs.existsSync(caCertPath))
12
- clientOpts.root_ca_path = caCertPath;
13
- if (encryptionSeed)
14
- clientOpts.encryption_seed = encryptionSeed;
15
- const client = new AUNClient(clientOpts);
7
+ const encryptionSeed = loadProcessConfig().aun?.encryptionSeed
8
+ || process.env.AUN_ENCRYPTION_SEED
9
+ || 'evol';
10
+ const client = await createAunClient({ aunPath, encryptionSeed });
16
11
  await client.auth.createAid({ aid });
17
12
  const authResult = await client.auth.authenticate({ aid });
18
13
  const accessToken = authResult?.access_token ?? client._access_token;
@@ -1,4 +1,4 @@
1
- import { AUNClient, GatewayDiscovery, E2EEError } from '@agentunion/fastaun';
1
+ import { GatewayDiscovery, E2EEError } from '@agentunion/fastaun';
2
2
  import crypto from 'crypto';
3
3
  import fs from 'fs';
4
4
  import path from 'path';
@@ -12,7 +12,8 @@ import { appendAidEvent } from '../utils/instance-registry.js';
12
12
  import { appendMessageLog, buildOutboundEntry } from '../core/message/message-log.js';
13
13
  import { chatDirPath } from '../core/session/session-fs-store.js';
14
14
  import { appendAidLifecycle } from '../aun/aid/identity.js';
15
- import { loadAgent, saveAgent } from '../config-store.js';
15
+ import { createAunClient } from '../aun/aid/client.js';
16
+ import { loadAgent, saveAgent, loadProcessConfig } from '../config-store.js';
16
17
  import { getProcessStartTime } from '../utils/process-introspect.js';
17
18
  import * as outbox from '../aun/outbox.js';
18
19
  import { guessMime, formatSize } from '../utils/media-cache.js';
@@ -57,46 +58,6 @@ function getEvolclawVersion() {
57
58
  }
58
59
  return _cachedVersion;
59
60
  }
60
- function migrateAunData(targetPath) {
61
- const legacyPath = path.join(os.homedir(), '.aun');
62
- if (legacyPath === targetPath)
63
- return;
64
- // AIDs 迁移:逐个检查每个 AID,缺少 private 的就从 legacy 复制
65
- const srcAIDs = path.join(legacyPath, 'AIDs');
66
- const dstAIDs = path.join(targetPath, 'AIDs');
67
- if (fs.existsSync(srcAIDs)) {
68
- fs.mkdirSync(dstAIDs, { recursive: true });
69
- try {
70
- for (const entry of fs.readdirSync(srcAIDs, { withFileTypes: true })) {
71
- if (!entry.isDirectory())
72
- continue;
73
- const aidName = entry.name;
74
- const srcAidDir = path.join(srcAIDs, aidName);
75
- const dstAidDir = path.join(dstAIDs, aidName);
76
- const srcPrivate = path.join(srcAidDir, 'private');
77
- const dstPrivate = path.join(dstAidDir, 'private');
78
- // 如果目标 AID 目录不存在或缺少 private,从源复制整个 AID 目录
79
- if (fs.existsSync(srcPrivate) && !fs.existsSync(dstPrivate)) {
80
- fs.cpSync(srcAidDir, dstAidDir, { recursive: true });
81
- }
82
- }
83
- }
84
- catch { }
85
- }
86
- // CA 迁移
87
- const srcCA = path.join(legacyPath, 'CA');
88
- const dstCA = path.join(targetPath, 'CA');
89
- if (fs.existsSync(srcCA) && !fs.existsSync(dstCA)) {
90
- fs.cpSync(srcCA, dstCA, { recursive: true });
91
- }
92
- for (const file of ['.seed', '.device_id']) {
93
- const src = path.join(legacyPath, file);
94
- const dst = path.join(targetPath, file);
95
- if (fs.existsSync(src) && !fs.existsSync(dst)) {
96
- fs.copyFileSync(src, dst);
97
- }
98
- }
99
- }
100
61
  export class AUNChannel {
101
62
  config;
102
63
  client = null;
@@ -587,9 +548,10 @@ export class AUNChannel {
587
548
  this.connected = false;
588
549
  const aunPath = this.config.keystorePath || resolveRoot();
589
550
  const aidName = this.config.aid;
590
- const encryptionSeed = this.config.encryptionSeed || process.env.AUN_ENCRYPTION_SEED || undefined;
591
- // Migrate legacy ~/.aun data to EVOLCLAW_HOME on first run
592
- migrateAunData(aunPath);
551
+ const encryptionSeed = loadProcessConfig().aun?.encryptionSeed
552
+ || process.env.AUN_ENCRYPTION_SEED
553
+ || 'evol';
554
+ // Migration from ~/.aun is handled by ensureDataDirs() at startup with a marker file.
593
555
  // Gateway URL 解析:优先用配置的 gatewayUrl,否则通过 well-known 自动发现
594
556
  let gateway = this.config.gatewayUrl || '';
595
557
  if (!gateway) {
@@ -611,39 +573,39 @@ export class AUNChannel {
611
573
  logger.info(`${this.logPrefix()} Initializing: aid=${aidName}, gateway=${gateway}, aun_path=${aunPath}`);
612
574
  // Create client with FileSecretStore (AES-256-GCM)
613
575
  // 不传 encryption_seed 时,SDK 自动从 {aun_path}/.seed 文件派生密钥(与 aun_cli.py 对齐)
614
- const rootCaPath = path.join(aunPath, 'CA', 'root', 'root.crt');
615
- this.client = new AUNClient({
616
- aun_path: aunPath,
617
- root_ca_path: rootCaPath,
618
- ...(encryptionSeed && { encryption_seed: encryptionSeed }),
619
- }, this.config.aunSdkLog ?? true);
576
+ const client = await createAunClient({
577
+ aunPath,
578
+ encryptionSeed,
579
+ aunSdkLog: this.config.aunSdkLog ?? true,
580
+ });
581
+ this.client = client;
620
582
  // Set gateway URL (internal property, same as Python SDK)
621
- this.client._gatewayUrl = gateway;
583
+ client._gatewayUrl = gateway;
622
584
  // Register event handlers before connecting
623
- this.client.on('message.received', (data) => {
585
+ client.on('message.received', (data) => {
624
586
  this.trace('IN', 'message.received', data);
625
587
  const kind = (data && typeof data === 'object') ? data.kind ?? '' : '';
626
588
  const keys = (data && typeof data === 'object') ? Object.keys(data).join(',') : typeof data;
627
589
  logger.debug(`${this.logPrefix()}[DIAG] message.received: kind=${kind} keys=${keys}`);
628
590
  this.handleIncomingPrivateMessage(data);
629
591
  });
630
- this.client.on('group.message_created', (data) => {
592
+ client.on('group.message_created', (data) => {
631
593
  this.trace('IN', 'group.message_created', data);
632
594
  const gid = (data && typeof data === 'object') ? data.group_id ?? '' : '';
633
595
  const sender = (data && typeof data === 'object') ? data.sender_aid ?? '' : '';
634
596
  logger.debug(`${this.logPrefix()}[DIAG] group.message_created: group_id=${gid} sender=${sender}`);
635
597
  this.handleIncomingGroupMessage(data);
636
598
  });
637
- this.client.on('connection.state', (data) => {
599
+ client.on('connection.state', (data) => {
638
600
  // trace is handled inside handleConnectionState with throttling
639
601
  this.handleConnectionState(data);
640
602
  });
641
603
  // gateway 被踢/服务端主动断开(含同槽位互踢的 self/new extra_info)
642
- this.client.on('gateway.disconnect', (data) => {
604
+ client.on('gateway.disconnect', (data) => {
643
605
  this.trace('IN', 'gateway.disconnect', data);
644
606
  this.handleGatewayDisconnect(data);
645
607
  });
646
- this.client.on('message.recalled', (data) => {
608
+ client.on('message.recalled', (data) => {
647
609
  this.trace('IN', 'message.recalled', data);
648
610
  if (data && typeof data === 'object') {
649
611
  const ids = data.message_ids;
@@ -657,12 +619,12 @@ export class AUNChannel {
657
619
  }
658
620
  }
659
621
  });
660
- this.client.on('message.undecryptable', (data) => {
622
+ client.on('message.undecryptable', (data) => {
661
623
  this.trace('IN', 'message.undecryptable', data);
662
624
  const d = data;
663
625
  logger.warn(`${this.logPrefix()} Message undecryptable: from=${d.from} mid=${d.message_id} err=${d._decrypt_error}`);
664
626
  });
665
- this.client.on('group.message_undecryptable', (data) => {
627
+ client.on('group.message_undecryptable', (data) => {
666
628
  this.trace('IN', 'group.message_undecryptable', data);
667
629
  const d = data;
668
630
  logger.warn(`${this.logPrefix()} Group message undecryptable: group=${d.group_id} from=${d.from} mid=${d.message_id} err=${d._decrypt_error}`);
@@ -670,7 +632,7 @@ export class AUNChannel {
670
632
  // Authenticate
671
633
  // Workaround: SDK 0.3.x _loadIdentityOrRaise doesn't set identity.aid from requested aid,
672
634
  // causing gateway "missing aid" error. Patch to backfill aid on loaded identity.
673
- const authFlow = this.client._auth;
635
+ const authFlow = client._auth;
674
636
  if (authFlow && typeof authFlow._loadIdentityOrRaise === 'function') {
675
637
  const origLoad = authFlow._loadIdentityOrRaise.bind(authFlow);
676
638
  authFlow._loadIdentityOrRaise = (aid) => {
@@ -684,12 +646,12 @@ export class AUNChannel {
684
646
  try {
685
647
  logger.info(`${this.logPrefix()} Authenticating as ${aidName}...`);
686
648
  this.trace('OUT', 'auth.authenticate', { aid: aidName });
687
- const auth = await this.client.auth.authenticate(aidName ? { aid: aidName } : undefined);
649
+ const auth = await client.auth.authenticate(aidName ? { aid: aidName } : undefined);
688
650
  this.trace('OUT', 'auth.authenticate.ok', { aid: auth.aid, gateway: auth.gateway, hasToken: !!auth.access_token });
689
651
  this.trace('IN', 'auth.result', { aid: auth.aid, gateway: auth.gateway, hasToken: !!auth.access_token });
690
652
  accessToken = auth.access_token;
691
653
  const resolvedGateway = auth.gateway || gateway;
692
- this.client._gatewayUrl = resolvedGateway;
654
+ client._gatewayUrl = resolvedGateway;
693
655
  logger.info(`${this.logPrefix()} Authenticated as ${auth.aid ?? '?'}, gateway=${resolvedGateway}`);
694
656
  }
695
657
  catch (e) {
@@ -716,12 +678,12 @@ export class AUNChannel {
716
678
  agentName: this.config.agentName,
717
679
  channelName: this.config.channelName,
718
680
  });
719
- this.trace('OUT', 'client.connect', { gateway: this.client._gatewayUrl, extra_info: extraInfo });
720
- await this.client.connect({ access_token: accessToken, gateway: this.client._gatewayUrl, extra_info: extraInfo },
681
+ this.trace('OUT', 'client.connect', { gateway: client._gatewayUrl, extra_info: extraInfo });
682
+ await client.connect({ access_token: accessToken, gateway: client._gatewayUrl, extra_info: extraInfo },
721
683
  // max_attempts=0 = 无限重试(与 Go/Python 对齐),交由 SDK 自己跑指数退避
722
684
  // initial_delay=1s,max_delay=300s(5min 封顶)
723
685
  { auto_reconnect: true, retry: { max_attempts: 0, initial_delay: 1.0, max_delay: 300.0 } });
724
- this.trace('OUT', 'client.connect.ok', { aid: this.client.aid });
686
+ this.trace('OUT', 'client.connect.ok', { aid: client.aid });
725
687
  this._aid = this.client.aid ?? undefined;
726
688
  const deviceId = this.client._device_id ?? '';
727
689
  this._chatId = this._aid ? `${this._aid}:${deviceId}:` : '';
@@ -2418,13 +2380,10 @@ EvolClaw AI Agent 网关,支持多项目会话管理和多 AI 后端切换。
2418
2380
  if (!this.client)
2419
2381
  return { type: null };
2420
2382
  try {
2421
- const { agentmdSync } = await import('../aun/aid/agentmd.js');
2422
- const result = await agentmdSync(aid, { client: this.client });
2423
- const md = result.content ?? '';
2424
- const typeMatch = md.match(/^type:\s*["']?(\w+)["']?/m);
2425
- const nameMatch = md.match(/^name:\s*["']?(.+?)["']?\s*$/m);
2426
- const type = typeMatch?.[1] === 'human' ? 'human' : 'ai';
2427
- const name = nameMatch?.[1]?.trim() || undefined;
2383
+ const selfAgentDir = path.join(resolvePaths().agentsDir, this.config.aid);
2384
+ const identity = await PeerIdentityCache.resolve('aun', aid, selfAgentDir, this.client, false);
2385
+ const type = identity.type === 'human' ? 'human' : 'ai';
2386
+ const name = identity.name || undefined;
2428
2387
  const info = { type, name };
2429
2388
  this.peerInfoCache.set(aid, info);
2430
2389
  setTimeout(() => this.peerInfoCache.delete(aid), 30 * 60 * 1000);