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/aun/aid/identity.js
CHANGED
|
@@ -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,
|
|
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
|
|
15
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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 ??
|
|
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 {
|
|
53
|
-
let client =
|
|
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 =
|
|
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 ??
|
|
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
|
-
|
|
100
|
-
const x509 = new crypto.X509Certificate(
|
|
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
|
-
|
|
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 ??
|
|
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 = '';
|
package/dist/aun/aid/index.js
CHANGED
|
@@ -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
|
|
2
|
-
import
|
|
3
|
-
import
|
|
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 ??
|
|
5
|
+
const aunPath = opts?.aunPath ?? defaultAunPath();
|
|
6
6
|
const slotId = opts?.slotId ?? '';
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
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;
|
package/dist/channels/aun.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
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 =
|
|
591
|
-
|
|
592
|
-
|
|
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
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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
|
-
|
|
583
|
+
client._gatewayUrl = gateway;
|
|
622
584
|
// Register event handlers before connecting
|
|
623
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
604
|
+
client.on('gateway.disconnect', (data) => {
|
|
643
605
|
this.trace('IN', 'gateway.disconnect', data);
|
|
644
606
|
this.handleGatewayDisconnect(data);
|
|
645
607
|
});
|
|
646
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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:
|
|
720
|
-
await
|
|
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:
|
|
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
|
|
2422
|
-
const
|
|
2423
|
-
const
|
|
2424
|
-
const
|
|
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);
|