@agentunion/fastaun-browser 0.4.3 → 0.4.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 (71) hide show
  1. package/CHANGELOG.md +203 -178
  2. package/_packed_docs/CHANGELOG.md +203 -178
  3. package/_packed_docs/INDEX.md +17 -17
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +11 -11
  5. package/_packed_docs/agent.md/SCHEMA.md +49 -49
  6. package/_packed_docs/agent.md/examples/signed-openclaw-lobster.md +22 -22
  7. package/_packed_docs/agent.md//350/277/234/347/250/213agent.md/347/274/223/345/255/230/344/270/216etag/351/200/217/344/274/240/346/226/271/346/241/210.md +327 -327
  8. package/_packed_docs/cli/AUN-CLI/350/256/276/350/256/241/346/226/207/346/241/243.md +686 -686
  9. package/_packed_docs/design/2026-05-22-aun-rpc-trace-enhancement.md +542 -542
  10. package/_packed_docs/design/E2EE_V2/347/256/200/345/214/226/344/270/2721DH/345/212/240Per-AID_Wrap/346/226/271/346/241/210.md +124 -124
  11. package/_packed_docs/design//350/267/250/350/257/255/350/250/200/345/256/271/345/231/250E2E/346/265/213/350/257/225/346/226/271/346/241/210.md +665 -665
  12. package/_packed_docs/protocol/01-/350/272/253/344/273/275/344/270/216/345/207/255/350/257/201/345/215/217/350/256/256-auth.md +2 -2
  13. package/_packed_docs/protocol/14-/344/272/244/344/272/222/346/234/272/345/210/266-/345/223/215/345/272/224/346/250/241/345/274/217/344/270/216/350/207/252/344/270/273/346/250/241/345/274/217.md +170 -170
  14. package/_packed_docs/protocol/15-/347/246/273/347/272/277/346/216/250/351/200/201/351/200/232/347/237/245/345/215/217/350/256/256.md +419 -419
  15. package/_packed_docs/protocol/README.md +1 -1
  16. package/_packed_docs/protocol/aun-docs-guide.md +1 -1
  17. package/_packed_docs/protocol//351/231/204/345/275/225A-/346/234/257/350/257/255/350/241/250.md +15 -15
  18. package/_packed_docs/protocol//351/231/204/345/275/225B-/346/211/251/345/261/225/346/200/247/346/214/207/345/215/227.md +4 -4
  19. package/_packed_docs/protocol//351/231/204/345/275/225J-/345/256/242/346/210/267/347/253/257/346/216/245/345/205/245/347/244/272/344/276/213.md +98 -98
  20. package/_packed_docs/protocol//351/231/204/345/275/225M-JWT/350/256/244/350/257/201/345/256/236/347/216/260/346/214/207/345/215/227.md +46 -46
  21. package/_packed_docs/protocol//351/231/204/345/275/225N-/345/210/206/345/270/203/345/274/217Trace/345/215/217/350/256/256.md +257 -257
  22. package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +1 -1
  23. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +1 -1
  24. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +1 -0
  25. package/_packed_docs/sdk/09-payload-reference.md +13 -13
  26. package/_packed_docs/sdk/E2EE_V2/346/266/210/346/201/257/351/200/232/344/277/241/346/227/266/345/272/217/345/233/276.md +171 -171
  27. package/dist/aid-store.d.ts +1 -0
  28. package/dist/aid-store.d.ts.map +1 -1
  29. package/dist/aid-store.js +26 -9
  30. package/dist/aid-store.js.map +1 -1
  31. package/dist/aid.d.ts +2 -1
  32. package/dist/aid.d.ts.map +1 -1
  33. package/dist/aid.js +7 -6
  34. package/dist/aid.js.map +1 -1
  35. package/dist/auth.d.ts +8 -13
  36. package/dist/auth.d.ts.map +1 -1
  37. package/dist/auth.js +38 -127
  38. package/dist/auth.js.map +1 -1
  39. package/dist/bundle.js +872 -350
  40. package/dist/client.d.ts +12 -5
  41. package/dist/client.d.ts.map +1 -1
  42. package/dist/client.js +296 -213
  43. package/dist/client.js.map +1 -1
  44. package/dist/index.d.ts +1 -0
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +1 -0
  47. package/dist/index.js.map +1 -1
  48. package/dist/keystore/index.d.ts +45 -22
  49. package/dist/keystore/index.d.ts.map +1 -1
  50. package/dist/keystore/index.js +6 -1
  51. package/dist/keystore/index.js.map +1 -1
  52. package/dist/keystore/indexeddb.d.ts +11 -1
  53. package/dist/keystore/indexeddb.d.ts.map +1 -1
  54. package/dist/keystore/indexeddb.js +167 -18
  55. package/dist/keystore/indexeddb.js.map +1 -1
  56. package/dist/register-flow.d.ts +34 -0
  57. package/dist/register-flow.d.ts.map +1 -0
  58. package/dist/register-flow.js +355 -0
  59. package/dist/register-flow.js.map +1 -0
  60. package/dist/v2/session/keystore.d.ts +5 -0
  61. package/dist/v2/session/keystore.d.ts.map +1 -1
  62. package/dist/v2/session/keystore.js +29 -0
  63. package/dist/v2/session/keystore.js.map +1 -1
  64. package/dist/version.d.ts +1 -1
  65. package/dist/version.js +1 -1
  66. package/package.json +1 -1
  67. package/_packed_docs/0.4.0_/345/267/256/345/274/202/346/240/270/345/256/236/345/206/263/347/255/226/350/256/260/345/275/225.md +0 -302
  68. package/_packed_docs/AUN_SDK_0.4.0_/350/256/276/350/256/241/345/257/271/346/257/224/345/210/206/346/236/220.md +0 -194
  69. package/_packed_docs/AUN_SDK_/351/207/215/346/236/204/345/256/236/346/226/275/350/256/241/345/210/222.md +0 -596
  70. package/_packed_docs/AUN_SDK_/351/207/215/346/236/204/350/256/276/350/256/241/346/226/271/346/241/210_v3.md +0 -1697
  71. package/_packed_docs/python-sdk-v2-only-changelog.md +0 -189
@@ -1,1697 +0,0 @@
1
- # AUN SDK 重构设计方案 v4.2
2
-
3
- ## 一、设计原则
4
-
5
- ### 1.1 三主体架构
6
-
7
- | 主体 | 职责 | 状态 |
8
- |------|------|------|
9
- | **AIDStore** | keystore 管理器(配置 + 工厂 + 联网管理) | 持有 `aunPath`、`encryptionSeed` |
10
- | **AID** | 单个身份的值对象(证书 + 可选私钥 + 密码学操作) | 不可变 |
11
- | **AUNClient** | 连接 + 会话生命周期 | 有状态机 |
12
-
13
- **职责边界**:
14
- - `AIDStore` 创建/加载/注册/解析 AID,**不持有正在使用的身份**——它是工厂,不是会话
15
- - `AID` 加载完成后即不可变;续签/换钥通过 `AIDStore` 完成,调用方重新 `load()` 取新实例
16
- - `AUNClient` 接收已加载的 `AID`(`isPrivateKeyValid() === true`),管理连接状态
17
-
18
- ### 1.2 核心改进点
19
-
20
- 1. **AIDStore / AID 拆分**:管理器与值对象分离,`new AIDStore(...)` 持有配置,`AIDStore.load(aid)` 等返回 AID 值对象
21
- 2. **结果字典统一**:所有可能失败的方法返回 `{ ok, data?, error? }`,不抛异常(密码学同步操作除外,见 2.7)
22
- 3. **AID 不可变**:构造后所有字段只读;renewCert/rekey 在 store 上完成,调用方重新 `load()` 取新实例
23
- 4. **AUNClient 身份可重载**:构造时可选传入 AID,也可通过 `loadIdentity()` 加载/重载身份(仅 NoIdentity 或 Closed 状态可调)
24
- 5. **判断方法精简**:只保留 2 个核心判断(`isCertValid()` 公钥有效性、`isPrivateKeyValid()` 私钥有效性)
25
- 6. **exists 语义明确**:HEAD PKI 证书端点判断 AID 是否注册;`headAgentMd()` 判断名片是否发布
26
- 7. **状态机闭环**:AUNClient 状态机支持 close 后重载身份重新使用,连接断开后自动退避重连
27
- 8. **实例级 protected_headers**:AUNClient 构造时可设置默认 `protected_headers`,自动附加到所有消息发送和 RPC 调用
28
- 9. **多设备/多实例支持**:AIDStore 构造时传入 `deviceId`/`slotId` 构成消费通道,同一 AID 最多 10 设备 × 10 slot 在线
29
-
30
- ---
31
-
32
- ## 二、AIDStore 与 AID
33
-
34
- ### 2.1 AIDStore 构造
35
-
36
- ```typescript
37
- class AIDStore {
38
- constructor(opts: {
39
- aunPath: string; // 必传:keystore 根目录
40
- encryptionSeed: string; // 必传:加密种子(可为空字符串 '')
41
- deviceId?: string; // 默认 getDeviceId(),同一 AID 最多 10 个设备在线
42
- slotId?: string; // 默认 'default',同设备最多 10 个 slot 在线
43
- verifySsl?: boolean; // 默认 true,测试环境可设为 false
44
- rootCaPath?: string; // 自定义根证书路径,私有部署使用
45
- debug?: boolean; // 开启调试日志,默认 false
46
- });
47
- }
48
- ```
49
-
50
- **说明**:
51
- - `aunPath` 和 `encryptionSeed` 都是必传参数
52
- - `encryptionSeed` 可以是空字符串 `''`,表示不加密
53
- - 应用层统一管理加密种子,SDK 不持久化
54
- - `deviceId` + `slotId` 构成消费通道,影响 V2 session 密钥存储和消息序号命名空间
55
- - `verifySsl: false` 仅用于测试环境,生产环境不应关闭
56
- - `rootCaPath` 用于私有部署时指定自定义根证书
57
- - `debug: true` 开启详细调试日志输出
58
- - 同一进程内可创建多个 AIDStore 实例(指向不同 keystore 或不同 slot)
59
-
60
- **示例**:
61
- ```typescript
62
- // 有加密
63
- const store = new AIDStore({
64
- aunPath: '/home/user/.evolclaw/aun',
65
- encryptionSeed: 'my-secret-seed-from-env',
66
- deviceId: 'desktop-01',
67
- slotId: 'default'
68
- });
69
-
70
- // 无加密,默认 deviceId/slotId
71
- const store = new AIDStore({
72
- aunPath: '/home/user/.evolclaw/aun',
73
- encryptionSeed: ''
74
- });
75
- ```
76
-
77
- ---
78
-
79
- ### 2.2 AIDStore 加载与注册
80
-
81
- #### `load(aid: string): Result<{ aid: AID }>`
82
-
83
- 从本地 keystore 加载 AID(证书 + 私钥若有)。
84
-
85
- > **注意**:JS SDK 因底层使用 IndexedDB(浏览器异步 API),保持 `Promise<Result<{ aid: AID }>>` 为平台例外。
86
-
87
- **流程**:
88
- 1. 从 `{aunPath}/AIDs/{aid}/public/certs/` 读证书
89
- 2. 链验证 + 有效期检查
90
- 3. 尝试从 `{aunPath}/AIDs/{aid}/private/key.pem` 读私钥
91
- 4. 若有私钥,签名自检(签 → 验)
92
-
93
- **返回**:
94
- - 成功(有私钥)→ `{ ok: true, data: { aid: AID } }`(`aid.isPrivateKeyValid() === true`)
95
- - 成功(仅证书)→ `{ ok: true, data: { aid: AID } }`(`aid.isCertValid() === true`,`aid.isPrivateKeyValid() === false`)
96
- - 失败 → `{ ok: false, error: { code, message } }`
97
-
98
- **错误码**:`CERT_NOT_FOUND` | `CERT_EXPIRED` | `CERT_CHAIN_BROKEN` | `KEYPAIR_MISMATCH` | `PRIVATE_KEY_PARSE_ERROR`
99
-
100
- **示例**:
101
- ```typescript
102
- const store = new AIDStore({ aunPath: '...', encryptionSeed: '...' });
103
- const result = store.load('alice.aid.pub');
104
-
105
- if (result.ok) {
106
- const me = result.data.aid;
107
- if (me.isPrivateKeyValid()) {
108
- console.log('本地身份,可签名');
109
- } else {
110
- console.log('对端身份,仅可验签');
111
- }
112
- } else {
113
- console.log('加载失败:', result.error.code, result.error.message);
114
- }
115
- ```
116
-
117
- ---
118
-
119
- #### `register(aid: string): Promise<Result<{ registered: true }>>`
120
-
121
- 注册新 AID。注册成功后密钥材料落盘,但**不返回 AID 实例**——需后续 `load()` 加载。
122
-
123
- **流程**:
124
- 1. 生成 keypair
125
- 2. 向服务端注册(POST `/auth/register`)
126
- 3. 拿到证书
127
- 4. 原子落盘(cert + 私钥)
128
-
129
- **返回**:
130
- - 成功 → `{ ok: true, data: { registered: true } }`
131
- - 失败 → `{ ok: false, error: { code, message } }`
132
-
133
- **错误码**:`IDENTITY_CONFLICT` | `INVALID_AID_FORMAT` | `NETWORK_ERROR` | `SERVER_ERROR`
134
-
135
- **示例**:
136
- ```typescript
137
- const store = new AIDStore({ aunPath: '...', encryptionSeed: '...' });
138
- const result = await store.register('alice.aid.pub');
139
-
140
- if (result.ok) {
141
- // 注册成功,加载身份
142
- const loadResult = store.load('alice.aid.pub');
143
- const me = loadResult.data!.aid;
144
- } else {
145
- console.log('注册失败:', result.error.code);
146
- }
147
- ```
148
-
149
- ---
150
-
151
- #### `list(): Result<{ identities: AIDInfo[] }>`
152
-
153
- 列出本地所有有私钥的 AID 元信息。
154
-
155
- **流程**:
156
- 1. 扫描 `{aunPath}/AIDs/` 目录
157
- 2. 对每个 AID 读取证书元数据
158
- 3. 过滤出有私钥的
159
-
160
- **返回**:
161
- ```typescript
162
- type AIDInfo = {
163
- aid: string;
164
- certNotAfter: Date;
165
- certIssuer: string;
166
- certFingerprint: string;
167
- };
168
-
169
- // 成功
170
- { ok: true, data: { identities: AIDInfo[] } }
171
- ```
172
-
173
- **示例**:
174
- ```typescript
175
- const store = new AIDStore({ aunPath: '...', encryptionSeed: '...' });
176
- const result = store.list();
177
-
178
- if (result.ok) {
179
- console.log('本地身份:', result.data.identities.map(i => i.aid));
180
- }
181
- ```
182
-
183
- ---
184
-
185
- #### `exists(aid: string): Promise<Result<{ exists: boolean }>>`
186
-
187
- 检查 AID 是否已在网络上注册(PKI 是否签发过证书)。
188
-
189
- **流程**:
190
- 1. HEAD PKI 证书端点(如 `https://pki.{issuer}/certs/{aid}`)
191
- 2. 根据状态码判断
192
-
193
- **返回**:
194
- ```typescript
195
- // AID 已注册
196
- { ok: true, data: { exists: true } }
197
- // AID 未注册
198
- { ok: true, data: { exists: false } }
199
- // 无法判断
200
- { ok: false, error: { code: 'NETWORK_ERROR', message: '...' } }
201
- ```
202
-
203
- **特点**:零 body 传输,最快;明确区分"不存在"和"网络故障"
204
-
205
- **示例**:
206
- ```typescript
207
- const store = new AIDStore({ aunPath: '...', encryptionSeed: '...' });
208
- const result = await store.exists('alice.aid.pub');
209
-
210
- if (result.ok) {
211
- if (result.data.exists) {
212
- console.log('名字已被占用');
213
- } else {
214
- // 可以注册
215
- await store.register('alice.aid.pub');
216
- }
217
- } else {
218
- console.log('网络故障,无法确定');
219
- }
220
- ```
221
-
222
- ---
223
-
224
- ### 2.3 AIDStore 解析对端
225
-
226
- #### `resolve(aid: string, opts?: ResolveOpts): Promise<Result<ResolveResult>>`
227
-
228
- **一站式解析对端 AID**:下载证书 → 验签证书 → 缓存到本地 → 下载 agent.md → 验签 agent.md。
229
-
230
- **参数**:
231
- ```typescript
232
- type ResolveOpts = {
233
- forceRefresh?: boolean; // 强制忽略本地缓存
234
- timeout?: number; // 整体超时(ms),默认 10000
235
- skipAgentMd?: boolean; // 只解析证书,不下载 agent.md
236
- };
237
- ```
238
-
239
- **流程**:
240
- 1. **检查本地证书缓存** — 缓存存在且未过期 → 跳到 step 4
241
- 2. **下载证书** — GET PKI 证书端点
242
- 3. **验证证书 + 落盘缓存** — 链验证 + 有效期检查 + 写入 `{aunPath}/AIDs/{aid}/public/certs/`
243
- 4. **下载 agent.md** — GET `https://{aid}/agent.md`
244
- 5. **验证 agent.md 签名** — 从签名块提取 fingerprint,比对证书 fingerprint,验签
245
-
246
- **返回**:
247
- ```typescript
248
- type ResolveResult = {
249
- aid: AID; // PeerOnly AID 对象
250
- agent_md?: {
251
- content: string;
252
- verification: {
253
- status: string;
254
- reason?: string;
255
- };
256
- cert_pem: string;
257
- };
258
- source: {
259
- cert_from_cache: boolean; // 证书来自本地缓存
260
- agent_md_fetched: boolean; // agent.md 已下载
261
- };
262
- };
263
-
264
- // 成功
265
- { ok: true, data: ResolveResult }
266
- // 失败
267
- { ok: false, error: { code, message } }
268
- ```
269
-
270
- **错误码**:
271
-
272
- | 阶段 | 错误码 | 说明 |
273
- |------|--------|------|
274
- | 证书下载失败(网络) | `NETWORK_ERROR` | 应用层重试 |
275
- | 证书不存在(404) | `CERT_NOT_FOUND` | AID 未注册 |
276
- | 证书链验证失败 | `CERT_CHAIN_BROKEN` | 证书不可信 |
277
- | 证书过期 | `CERT_EXPIRED` | 证书已失效 |
278
- | agent.md 不存在(404) | `AGENTMD_NOT_FOUND` | 对端未发布名片 |
279
-
280
- **关键设计原则**:
281
- - 网络/资源不存在 → 返回 `error`(应用层无法继续)
282
- - 内容验证失败(签名 invalid / unsigned)→ 仍 `ok: true`,通过 `data.agent_md.verification.status` 标记,让应用层决定
283
-
284
- **示例**:
285
- ```typescript
286
- const store = new AIDStore({ aunPath: '...', encryptionSeed: '' });
287
- const result = await store.resolve('bob.aid.pub');
288
-
289
- if (result.ok) {
290
- const { aid: peer, agent_md, source } = result.data;
291
- console.log('证书来自:', source.cert_from_cache ? '本地缓存' : '网络下载');
292
-
293
- if (agent_md?.verification.status === 'verified') {
294
- console.log('名片有效:', agent_md.content);
295
- } else if (agent_md?.verification.status === 'invalid') {
296
- console.log('警告:名片签名无效,原因:', agent_md.verification.reason);
297
- } else {
298
- console.log('名片未签名');
299
- }
300
- } else {
301
- switch (result.error.code) {
302
- case 'CERT_NOT_FOUND': console.log('AID 未在 PKI 注册'); break;
303
- case 'AGENTMD_NOT_FOUND': console.log('AID 未发布 agent.md'); break;
304
- case 'NETWORK_ERROR': console.log('网络故障'); break;
305
- }
306
- }
307
- ```
308
-
309
- **网络开销优化**:
310
- - 本地证书缓存命中:1 次 GET agent.md
311
- - 本地无缓存:1 次 GET 证书 + 1 次 GET agent.md(可并行)
312
- - `skipAgentMd: true`:只解析证书,0~1 次网络请求
313
-
314
- ---
315
-
316
- #### `fetchAgentMd(aid: string): Promise<Result<FetchAgentMdResult>>`
317
-
318
- 下载 agent.md + 自动拉证书 + 验签。比 `resolve` 更轻量,适用于"只想拿名片"的场景。
319
-
320
- **流程**:
321
- 1. GET `https://{aid}/agent.md`
322
- 2. 从签名块提取 fingerprint
323
- 3. 查证书(本地缓存优先,无则从 PKI 拉)
324
- 4. 验签
325
-
326
- **返回**:
327
- ```typescript
328
- type FetchAgentMdResult = {
329
- aid: string;
330
- content: string;
331
- verification: {
332
- status: string;
333
- reason?: string;
334
- };
335
- cert_pem: string;
336
- etag: string;
337
- last_modified: string;
338
- };
339
- ```
340
-
341
- **错误码**:`AGENTMD_NOT_FOUND` | `NETWORK_ERROR`
342
-
343
- ---
344
-
345
- #### `checkAgentMd(aid: string, ttlDays?: number): Promise<Result<CheckAgentMdResult>>`
346
-
347
- 比对本地缓存与远端 etag,决定是否需要重新拉取。
348
-
349
- **返回**:
350
- ```typescript
351
- type CheckAgentMdResult = {
352
- aid: string;
353
- local_found: boolean;
354
- remote_found: boolean;
355
- local_etag: string;
356
- remote_etag: string;
357
- needs_update: boolean;
358
- ttl_days: number;
359
- };
360
- ```
361
-
362
- ---
363
-
364
- #### `headAgentMd(aid: string): Promise<Result<HeadAgentMdResult>>`
365
-
366
- HEAD 请求拿 agent.md 元数据,**判断对端是否发布了名片**。
367
-
368
- **返回**:
369
- ```typescript
370
- type HeadAgentMdResult = {
371
- aid: string;
372
- found: boolean;
373
- etag: string;
374
- last_modified: string;
375
- content_length: number;
376
- };
377
- ```
378
-
379
- **错误码**:`AGENTMD_NOT_FOUND`(对端未发布名片)| `NETWORK_ERROR`
380
-
381
- ---
382
-
383
- ### 2.4 AIDStore 证书运维
384
-
385
- | 方法 | 联网 | 说明 |
386
- |------|:----:|------|
387
- | `renewCert(aid)` | 是 | 续签证书并落盘,调用方需重新 `load()` 取新实例 |
388
- | `rekey(aid)` | 是 | 密钥轮换:生成新 keypair → 服务端换证书 → 落盘,调用方需重新 `load()` |
389
- | `changeSeed(oldSeed, newSeed)` | 否 | 更换加密种子:用旧种子解密所有私钥 → 用新种子重新加密 → 落盘 |
390
- | `diagnose(aid)` | 是 | 本地状态 + 远端注册状态对比 |
391
-
392
- `renewCert` / `rekey` / `diagnose` 返回 `Promise<Result<T>>`,`changeSeed` 为同步方法返回 `Result<T>`:
393
-
394
- ```typescript
395
- // changeSeed 签名(同步)
396
- changeSeed(oldSeed: string, newSeed: string): Result<{ changed: true; count: number }>
397
-
398
- // renewCert 成功
399
- { ok: true, data: { renewed: true, new_cert_not_after: Date, new_fingerprint: string } }
400
-
401
- // rekey 成功
402
- { ok: true, data: { rekeyed: true, new_cert_not_after: Date, new_fingerprint: string } }
403
-
404
- // changeSeed 成功
405
- { ok: true, data: { changed: true, count: number } } // 重新加密的私钥数量
406
-
407
- // diagnose 成功
408
- {
409
- ok: true,
410
- data: {
411
- aid: string;
412
- status: string;
413
- local_valid: boolean;
414
- remote_registered: boolean;
415
- suggestions: string[];
416
- local: Record<string, unknown>;
417
- remote: Record<string, unknown>;
418
- }
419
- }
420
- ```
421
-
422
- **返回类型定义**:
423
- ```typescript
424
- type RenewCertResult = {
425
- renewed: true;
426
- new_cert_not_after: Date;
427
- new_fingerprint: string;
428
- };
429
-
430
- type RekeyResult = {
431
- rekeyed: true;
432
- new_cert_not_after: Date;
433
- new_fingerprint: string;
434
- };
435
-
436
- type DiagnoseResult = {
437
- aid: string;
438
- status: string;
439
- local_valid: boolean;
440
- remote_registered: boolean;
441
- suggestions: string[];
442
- local: Record<string, unknown>;
443
- remote: Record<string, unknown>;
444
- };
445
- ```
446
-
447
- **错误码**:
448
- - `renewCert`: `CERT_RENEWAL_FAILED` | `PRIVATE_KEY_REQUIRED` | `NETWORK_ERROR`
449
- - `rekey`: `REKEY_FAILED` | `PRIVATE_KEY_REQUIRED` | `NETWORK_ERROR`
450
- - `changeSeed`: `PRIVATE_KEY_PARSE_ERROR`(旧种子错误)
451
-
452
- **示例**:
453
- ```typescript
454
- const store = new AIDStore({ aunPath, encryptionSeed: 'old-seed' });
455
-
456
- // 续签
457
- const renewResult = await store.renewCert('alice.aid.pub');
458
- if (renewResult.ok) {
459
- // 重新加载拿新证书
460
- const me = store.load('alice.aid.pub').data!.aid;
461
- console.log('新证书有效期至:', me.certNotAfter);
462
- }
463
-
464
- // 换加密种子(同步)
465
- const seedResult = store.changeSeed('old-seed', 'new-seed');
466
- if (seedResult.ok) {
467
- console.log(`已重新加密 ${seedResult.data.count} 个私钥`);
468
- }
469
- ```
470
-
471
- ---
472
-
473
- ### 2.5 AID 类(值对象)
474
-
475
- AID 是不可变的身份值对象,由 AIDStore 创建,外部不直接 `new AID()`。
476
-
477
- #### 只读属性
478
-
479
- | 属性 | 类型 | 说明 |
480
- |------|------|------|
481
- | `aid` | string | AID 标识符(如 `'alice.aid.pub'`) |
482
- | `aunPath` | string | keystore 根目录(来自创建它的 store) |
483
- | `deviceId` | string | 来自创建它的 AIDStore 的 deviceId |
484
- | `slotId` | string | 来自创建它的 AIDStore 的 slotId |
485
- | `verifySsl` | boolean | 来自创建它的 AIDStore 的 verifySsl |
486
- | `rootCaPath` | string \| null | 来自创建它的 AIDStore 的 rootCaPath |
487
- | `debug` | boolean | 来自创建它的 AIDStore 的 debug |
488
- | `certPem` | string | PEM 格式证书 |
489
- | `publicKey` | string | DER base64 公钥 |
490
- | `certSubject` | string | 证书 subject |
491
- | `certNotBefore` | Date | 证书生效时间 |
492
- | `certNotAfter` | Date | 证书过期时间 |
493
- | `certIssuer` | string | 证书颁发者 |
494
- | `certFingerprint` | string | sha256 指纹 |
495
-
496
- ---
497
-
498
- ### 2.6 AID 状态判断
499
-
500
- | 方法 | 返回 | 说明 |
501
- |------|:----:|------|
502
- | `isCertValid()` | boolean | 公钥有效性:链验证通过 + 在有效期内 |
503
- | `isPrivateKeyValid()` | boolean | 私钥有效性:有私钥 + 与公钥配对(蕴含公钥有效) |
504
-
505
- **派生语义**(直接读这两个判断即可):
506
-
507
- | 派生判断 | 等价表达式 | 说明 |
508
- |---------|-----------|------|
509
- | 能否验签 | `isCertValid()` | 公钥有效即可验签 |
510
- | 能否签名 | `isPrivateKeyValid()` | 私钥有效即可签名 |
511
- | 是否本地身份 | `isPrivateKeyValid()` | 私钥有效一定意味着公私钥都有效 |
512
- | 是否对端身份 | `isCertValid() && !isPrivateKeyValid()` | 仅有公钥 |
513
-
514
- ---
515
-
516
- ### 2.7 AID 密码学操作
517
-
518
- 所有方法**同步**返回结果字典:
519
-
520
- | 方法 | 前置条件 | 返回 |
521
- |------|---------|------|
522
- | `verify(payload, signature)` | `isCertValid()` | `Result<{ valid: boolean }>` |
523
- | `verifyAgentMd(content)` | `isCertValid()` | `Result<VerifyResult>` |
524
- | `sign(payload)` | `isPrivateKeyValid()` | `Result<{ signature: string }>` |
525
- | `signAgentMd(content)` | `isPrivateKeyValid()` | `Result<{ signed: string }>` |
526
-
527
- **类型定义**:
528
- ```typescript
529
- type VerifyResult = {
530
- status: 'verified' | 'invalid' | 'unsigned';
531
- payload?: string; // 去除签名块后的原始内容
532
- reason?: string;
533
- };
534
- ```
535
-
536
- **错误码**:
537
- - `verify` / `verifyAgentMd`:`CERT_NOT_VALID`(前置条件不满足)| `VERIFICATION_OPERATION_ERROR`
538
- - `sign` / `signAgentMd`:`PRIVATE_KEY_NOT_VALID`(前置条件不满足)| `SIGNATURE_OPERATION_ERROR`
539
-
540
- **示例**:
541
- ```typescript
542
- // 签名
543
- const me = store.load('alice.aid.pub').data!.aid;
544
- const signResult = me.signAgentMd(content);
545
- if (signResult.ok) {
546
- const signed = signResult.data.signed;
547
- }
548
-
549
- // 验签
550
- const peer = (await store.resolve('bob.aid.pub')).data!.aid;
551
- const verifyResult = peer.verifyAgentMd(signed);
552
- if (verifyResult.ok && verifyResult.data.status === 'verified') {
553
- console.log('验签通过:', verifyResult.data.payload);
554
- }
555
- ```
556
-
557
- ---
558
-
559
- ### 2.8 结果字典统一格式
560
-
561
- 所有可能失败的方法(包括 AIDStore 联网方法和 AID 同步密码学方法)返回统一格式:
562
-
563
- ```typescript
564
- type Result<T> =
565
- | { ok: true; data: T }
566
- | { ok: false; error: { code: string; message: string; cause?: unknown } };
567
- ```
568
-
569
- **约定**:
570
- - 永远返回 Result,不抛异常(除非是真正的程序错误,如类型不匹配)
571
- - `error.code` 是字符串错误码(见 2.10 错误码汇总)
572
- - `error.message` 是人类可读消息
573
- - `error.cause` 可选,包装底层异常
574
-
575
- **TypeScript 使用模式**:
576
- ```typescript
577
- const result = store.load('alice.aid.pub');
578
- if (!result.ok) {
579
- // 处理错误
580
- console.log(result.error.code, result.error.message);
581
- return;
582
- }
583
- // 之后 result.data 类型已收窄为 { aid: AID }
584
- const me = result.data.aid;
585
- ```
586
-
587
- ---
588
-
589
- ### 2.9 使用场景对照表
590
-
591
- | 场景 | 推荐方法 | 一行代码示例 |
592
- |------|---------|--------------|
593
- | 检查 AID 名字是否可注册 | `store.exists(aid)` | `(await store.exists('alice.aid.pub')).data?.exists === false` |
594
- | 注册新身份 | `store.register` + `store.load` | `await store.register('alice.aid.pub'); store.load('alice.aid.pub')` |
595
- | 加载本地身份 | `store.load(aid)` | `store.load('alice.aid.pub').data!.aid` |
596
- | 列出本地所有身份 | `store.list()` | `store.list().data!.identities` |
597
- | **一站式解析对端**(推荐)| `store.resolve(aid)` | `const { aid: peer, agent_md } = (await store.resolve('bob')).data!` |
598
- | 只想拿对端 agent.md | `store.fetchAgentMd(aid)` | `(await store.fetchAgentMd('bob.aid.pub')).data?.content` |
599
- | 离线签名 agent.md | `load` → `signAgentMd` | `me.signAgentMd(content).data?.signed` |
600
- | 离线验签 agent.md | `load`/`resolve` → `verifyAgentMd` | `peer.verifyAgentMd(signed).data?.status` |
601
- | 检查证书是否即将过期 | `load` + `certNotAfter` | `me.certNotAfter` |
602
- | 证书即将过期,续签 | `store.renewCert(aid)` | `await store.renewCert('alice.aid.pub')` |
603
- | 密钥泄漏,换密钥 | `store.rekey(aid)` | `await store.rekey('alice.aid.pub')` |
604
- | 检查本地+远端一致性 | `store.diagnose(aid)` | `(await store.diagnose('alice.aid.pub')).data` |
605
- | 验证某段 payload 的签名 | `peer.verify(payload, sig)` | `peer.verify(payload, signature).data?.valid` |
606
- | 用本地私钥签 payload | `me.sign(payload)` | `me.sign(payload).data?.signature` |
607
-
608
- #### 关键场景详解
609
-
610
- **场景 A:未知对端,建立信任**
611
-
612
- ```typescript
613
- const store = new AIDStore({ aunPath, encryptionSeed: '' });
614
- const result = await store.resolve('bob.aid.pub');
615
-
616
- if (result.ok && result.data.agent_md?.verification.status === 'verified') {
617
- console.log('对端可信:', result.data.agent_md.content);
618
- }
619
- ```
620
-
621
- **场景 B:已知对端(证书已缓存),快速验签**
622
-
623
- ```typescript
624
- const store = new AIDStore({ aunPath, encryptionSeed: '' });
625
- const peer = store.load('bob.aid.pub').data?.aid;
626
-
627
- if (peer?.isCertValid()) {
628
- const r = peer.verifyAgentMd(content);
629
- if (r.ok) console.log(r.data.status);
630
- }
631
- ```
632
-
633
- **场景 C:注册新身份流程(健壮)**
634
-
635
- ```typescript
636
- const store = new AIDStore({ aunPath, encryptionSeed: process.env.SEED || '' });
637
-
638
- // Step 1: 检查名字是否可用
639
- const check = await store.exists('alice.aid.pub');
640
- if (!check.ok) {
641
- throw new Error('网络故障,无法确定');
642
- }
643
- if (check.data.exists) {
644
- throw new Error('名字已被占用');
645
- }
646
-
647
- // Step 2: 注册(仅落盘,不加载)
648
- const reg = await store.register('alice.aid.pub');
649
- if (!reg.ok) {
650
- throw new Error(`注册失败: ${reg.error.code}`);
651
- }
652
-
653
- // Step 3: 加载身份用于后续操作
654
- const load = store.load('alice.aid.pub');
655
- const me = load.data!.aid;
656
- ```
657
-
658
- **场景 D:批量加载本地身份(并发安全)**
659
-
660
- ```typescript
661
- const store = new AIDStore({ aunPath, encryptionSeed: '' });
662
- const list = store.list();
663
- if (!list.ok) return;
664
-
665
- // 并发加载多个 AID 实例(load 为同步,直接 map)
666
- const aids = list.data.identities.map(i => store.load(i.aid).data!.aid);
667
-
668
- // 并发签名
669
- const signatures = aids.map(me => me.signAgentMd(content));
670
- ```
671
-
672
- ---
673
-
674
- ### 2.10 错误码汇总
675
-
676
- 所有错误以 `error.code` 形式返回,不再以 Error 类抛出。
677
-
678
- #### 2.10.1 加载阶段(store.load)
679
-
680
- | 错误码 | 触发条件 | 恢复建议 |
681
- |--------|---------|---------|
682
- | `CERT_NOT_FOUND` | 证书文件不存在 | 1. 检查 aid 拼写<br>2. `store.register()` 注册<br>3. `store.resolve()` 拉对端证书 |
683
- | `CERT_PARSE_ERROR` | 证书 PEM 格式损坏 | 删除损坏文件,重新拉取 |
684
- | `CERT_EXPIRED` | 证书已过期 | `store.renewCert(aid)` |
685
- | `CERT_NOT_YET_VALID` | 证书未生效 | 检查系统时间 |
686
- | `CERT_CHAIN_BROKEN` | 证书链验证失败 | 1. 更新根证书缓存<br>2. 检查 PKI 配置 |
687
- | `KEYPAIR_MISMATCH` | 私钥与公钥不配对(自检失败) | 1. 私钥损坏,删除重新生成<br>2. 重新拉取证书 |
688
- | `PRIVATE_KEY_PARSE_ERROR` | 私钥 PEM 格式损坏或解密失败 | 1. 检查 `encryptionSeed` 是否正确<br>2. 删除重新生成 |
689
-
690
- #### 2.10.2 注册阶段(store.register)
691
-
692
- | 错误码 | 触发条件 | 恢复建议 |
693
- |--------|---------|---------|
694
- | `IDENTITY_CONFLICT` | AID 已被占用(409) | 换名字 |
695
- | `INVALID_AID_FORMAT` | 不符合 `{name}.{issuer}` 格式 | 检查 AID 格式 |
696
- | `NETWORK_ERROR` | 无法连接服务端 | 检查网络 |
697
- | `SERVER_ERROR` | 服务端 5xx | 稍后重试 |
698
-
699
- #### 2.10.3 agent.md / 证书下载阶段(store.fetchAgentMd / resolve / headAgentMd)
700
-
701
- | 错误码 | 触发条件 | 恢复建议 |
702
- |--------|---------|---------|
703
- | `AGENTMD_NOT_FOUND` | agent.md 不存在(404) | 该 AID 未发布名片 |
704
- | `AGENTMD_PARSE_ERROR` | YAML frontmatter 解析失败 | 联系 AID 所有者修复 |
705
- | `SIGNATURE_NOT_FOUND` | agent.md 未签名 | 该名片不可信(也可能 `verification.status='unsigned'` 形式返回) |
706
- | `SIGNATURE_INVALID` | 签名验证失败 | 该名片已被篡改(也可能 `verification.status='invalid'` 形式返回) |
707
- | `CERT_FINGERPRINT_MISMATCH` | 签名块中 fingerprint 与证书不符 | 证书与签名不对应 |
708
- | `NETWORK_ERROR` | 无法连接 | 检查网络 |
709
-
710
- #### 2.10.4 证书运维阶段(store.renewCert / rekey)
711
-
712
- | 错误码 | 触发条件 | 恢复建议 |
713
- |--------|---------|---------|
714
- | `CERT_RENEWAL_FAILED` | 服务端拒绝续签 | 检查 AID 状态,可能需要 rekey |
715
- | `REKEY_FAILED` | 服务端拒绝换证书 | 联系服务端管理员 |
716
- | `PRIVATE_KEY_REQUIRED` | 没有本地私钥,无法执行 | 该操作需要本地身份 |
717
-
718
- #### 2.10.5 密码学操作(aid.sign / verify / signAgentMd / verifyAgentMd)
719
-
720
- | 错误码 | 触发条件 | 恢复建议 |
721
- |--------|---------|---------|
722
- | `SIGNATURE_OPERATION_ERROR` | 签名操作失败 | 检查私钥完整性 |
723
- | `VERIFICATION_OPERATION_ERROR` | 验签操作失败 | 检查证书完整性 |
724
- | `CERT_NOT_VALID` | `verify()` 但 `isCertValid() === false` | 先检查 `isCertValid()` |
725
- | `PRIVATE_KEY_NOT_VALID` | `sign()` 但 `isPrivateKeyValid() === false` | 先检查 `isPrivateKeyValid()` |
726
-
727
- ---
728
-
729
- ### 2.11 加载诊断流程
730
-
731
- ```
732
- store.load(aid)
733
-
734
- ├─ 证书存在?
735
- │ ├─ 否 → { ok: false, error: { code: 'CERT_NOT_FOUND' } }
736
- │ └─ 是 ↓
737
-
738
- ├─ 证书可解析?
739
- │ ├─ 否 → { ok: false, error: { code: 'CERT_PARSE_ERROR' } }
740
- │ └─ 是 ↓
741
-
742
- ├─ 证书在有效期内?
743
- │ ├─ 否 → { ok: false, error: { code: 'CERT_EXPIRED' / 'CERT_NOT_YET_VALID' } }
744
- │ └─ 是 ↓
745
-
746
- ├─ 证书链验证通过?
747
- │ ├─ 否 → { ok: false, error: { code: 'CERT_CHAIN_BROKEN' } }
748
- │ └─ 是 ↓
749
-
750
- ├─ → AID 实例 isCertValid() = true
751
-
752
- ├─ 私钥存在?
753
- │ ├─ 否 → { ok: true, data: { aid } }(PeerOnly,仅能验签)
754
- │ └─ 是 ↓
755
-
756
- ├─ 私钥可解析?
757
- │ ├─ 否 → { ok: false, error: { code: 'PRIVATE_KEY_PARSE_ERROR' } }
758
- │ └─ 是 ↓
759
-
760
- ├─ 私钥与公钥配对?(签名自检)
761
- │ ├─ 否 → { ok: false, error: { code: 'KEYPAIR_MISMATCH' } }
762
- │ └─ 是 ↓
763
-
764
- └─ → AID 实例 isPrivateKeyValid() = true
765
- { ok: true, data: { aid } }(Local,能签能验)
766
- ```
767
-
768
- ---
769
-
770
- ## 三、AUNClient 类(AID 状态)
771
-
772
- ### 3.1 构造方法与身份加载
773
-
774
- ```typescript
775
- class AUNClient {
776
- constructor(aid?: AID);
777
- loadIdentity(aid: AID): void; // 加载/重载身份,aid 必须 isPrivateKeyValid(),只在 NoIdentity 或 Closed 状态可调用
778
- connect(opts?: ConnectionOptions): Promise<void>;
779
- setProtectedHeaders(headers: Record<string, string> | null): void; // 设置/清除实例级 protected_headers,随时可调
780
- }
781
-
782
- type ConnectionOptions = {
783
- auto_reconnect?: boolean; // 是否自动重连,默认 true
784
- connect_timeout?: number; // 连接超时(秒),默认 5
785
- retry_initial_delay?: number; // 最小退避间隔(秒),默认 1
786
- retry_max_delay?: number; // 最大退避间隔(秒),默认 64
787
- retry_max_attempts?: number; // 最大重试次数,0=无限,默认 0
788
- heartbeat_interval?: number; // 心跳间隔(秒),默认 30
789
- call_timeout?: number; // RPC 调用超时(秒),默认 35
790
- };
791
- ```
792
-
793
- **说明**:
794
- - 构造时可选传入 AID 对象
795
- - 传入有效本地 AID(`isPrivateKeyValid() === true`)→ 直接进入 Standby 状态
796
- - 传入无效 AID 或不传 → 进入 NoIdentity 状态
797
- - 所有配置(aunPath、verifySsl、rootCaPath、debug 等)通过 AID 对象传递,AUNClient 不单独接受配置参数,必须通过 AIDStore 创建的 AID 使用
798
- - `loadIdentity(aid)` 只在 NoIdentity 或 Closed 状态可调用
799
- - `loadIdentity` 传入的 AID 必须 `isPrivateKeyValid() === true`,否则抛 `InvalidIdentityError`
800
- - `deviceId` + `slotId` 由 AIDStore 管理,AUNClient 通过 AID 实例间接获取
801
- - `connect(opts?)` 接受可选的 `ConnectionOptions`,用于控制连接行为(超时、重连退避等);gateway URL 和 token 来自 authenticate 缓存,不在 opts 中传入
802
- - `setProtectedHeaders(headers)` 随时可调,传 `null` 清除;设置后自动附加到所有 `call()`、`sendV2()`、`sendGroupV2()` 调用,无需在每次调用时传入
803
-
804
- **示例**:
805
- ```typescript
806
- const store = new AIDStore({ aunPath: '...', encryptionSeed: '...' });
807
- const me = store.load('alice.aid.pub').data!.aid;
808
- const client = new AUNClient(me);
809
-
810
- // 设置实例级 protected_headers
811
- client.setProtectedHeaders({ 'x-app': 'evolclaw', 'x-version': '3.0' });
812
-
813
- // 之后所有 call/sendV2/sendGroupV2 自动附带
814
- await client.connect();
815
-
816
- // 运行时更新
817
- client.setProtectedHeaders({ 'x-app': 'evolclaw', 'x-version': '3.1' });
818
-
819
- // 清除
820
- client.setProtectedHeaders(null);
821
- ```
822
-
823
- ---
824
-
825
- ### 3.2 状态机
826
-
827
- #### 3.2.1 状态转换图
828
-
829
- ```
830
- new AUNClient() new AUNClient(validAid)
831
- │ │
832
- ▼ ▼
833
- ┌──────────────┐ ┌──────────────┐
834
- │ NoIdentity │ │ Standby │
835
- │ (无身份) │ │ (待命中) │
836
- └──────┬───────┘ └──────┬───────┘
837
- │ │
838
- │ loadIdentity(aid) │ authenticate()
839
- │ │
840
- ▼ ▼
841
- ┌──────────────┐ ┌──────────────┐
842
- │ Standby │ │Authenticated │ 有 token,可上传 agent.md
843
- │ (待命中) │ │ (已认证) │
844
- └──────┬───────┘ └──────┬───────┘
845
- │ │
846
- │ authenticate() │ connect()
847
- │ │
848
- ▼ │
849
- ┌──────────────┐ │
850
- │Authenticated │ │
851
- │ (已认证) │────────────────┘
852
- └──────┬───────┘
853
-
854
- │ connect()
855
-
856
- ┌──────────────┐
857
- │ Connecting │
858
- │ (连接中) │
859
- └──────┬───────┘
860
- │ 成功
861
-
862
- ┌──────────────┐
863
- │ Ready │←──────────────────────────┐
864
- │ (就绪) │ │
865
- └──────┬───────┘ │
866
- │ 网络断开 │
867
- ▼ │
868
- ┌──────────────┐ │
869
- │ RetryBackoff │ │
870
- │(重连等待中) │ │
871
- │ nextRetryAt │ │
872
- └──────┬───────┘ │
873
- │ 退避到期 / connect() │
874
- ▼ │
875
- ┌──────────────┐ │
876
- │ Reconnecting │──────── 成功 ─────────────┘
877
- │ (重连中) │
878
- └──────┬───────┘
879
- │ 失败(还有次数)→ RetryBackoff
880
- │ 失败(重连耗尽)
881
-
882
- ┌───────────────────┐
883
- │ ConnectionFailed │
884
- │ (连接失败) │
885
- │ lastError/Code │
886
- └──────┬────────────┘
887
- │ connect() → Connecting
888
-
889
- 任意状态 ─── close() ──→ Closed ─── loadIdentity() ──→ Standby
890
-
891
- 任意连接状态(Connecting/Ready/RetryBackoff/Reconnecting/ConnectionFailed)
892
- ─── disconnect() ──→ Standby
893
- ```
894
-
895
- **状态闭环说明**:
896
- - 正常流程:Standby → Authenticated → Connecting → Ready → RetryBackoff → Reconnecting → Ready(循环)
897
- - `connect()` 在 Standby 状态时自动先 authenticate(内部完成),在 Authenticated 状态时直接连接
898
- - 主动断开:任意连接状态 → `disconnect()` → Standby
899
- - 重连耗尽:Reconnecting → ConnectionFailed → `connect()` → Connecting
900
- - 关闭重生:任意状态 → `close()` → Closed → `loadIdentity()` → Standby
901
-
902
- ---
903
-
904
- #### 3.2.2 状态详细说明表
905
-
906
- | 状态 | 含义 | 持有身份 | hasIdentity | canSign | canConnect | canSend | isOnline |
907
- |------|------|:-------:|:-----------:|:-------:|:----------:|:-------:|:--------:|
908
- | **NoIdentity** | 无身份,需先 `loadIdentity()` | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
909
- | **Standby** | 待命中,身份已加载,无 token | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
910
- | **Authenticated** | 已认证,有 token,未连接 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
911
- | **Connecting** | 正在建立连接 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
912
- | **Ready** | 就绪,全功能可用 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
913
- | **RetryBackoff** | 重连等待中(退避计时) | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
914
- | **Reconnecting** | 重连中 | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
915
- | **ConnectionFailed** | 连接失败(重连耗尽) | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
916
- | **Closed** | 已关闭,身份已清除 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
917
-
918
- **关键说明**:
919
-
920
- - **Authenticated**:有 token,可调 `publishAgentMd()`,不需要长连接
921
- - **Connecting**:`connect()` 在 Standby 时自动先 authenticate(内部完成),在 Authenticated 时直接建连接
922
- - **RetryBackoff**:`isOnline === true`,SDK 仍认为自己应该在线,只是暂时等待。可读 `nextRetryAt`
923
- - **ConnectionFailed**:保留身份,可调 `connect()` 重新尝试
924
- - **Closed** vs **ConnectionFailed**:前者清除身份(`hasIdentity = false`),后者保留身份
925
-
926
- ---
927
-
928
- #### 3.2.3 状态转换表
929
-
930
- | 当前状态 | 推进方法 / 触发 | 目标状态 | 说明 |
931
- |---------|---------------|---------|------|
932
- | **NoIdentity** | `loadIdentity(aid)` | Standby | aid 必须 `isPrivateKeyValid()` |
933
- | | `close()` | Closed | 幂等 |
934
- | **Standby** | `authenticate()` | Authenticated | 拿 token,不建长连接 |
935
- | | `connect(opts?)` | Connecting | 自动先 authenticate 再建连接 |
936
- | | `loadIdentity(aid)` | ❌ 抛 StateError | 仅 NoIdentity / Closed 可重载 |
937
- | | `close()` | Closed | 清除身份 |
938
- | **Authenticated** | `connect(opts?)` | Connecting | 直接建连接(已有 token) |
939
- | | `disconnect()` | Standby | 丢弃 token |
940
- | | `close()` | Closed | 清除身份 |
941
- | **Connecting** | 成功 | Ready | 自动推进 |
942
- | | 失败 | ConnectionFailed | 自动推进,记录 lastError |
943
- | | `disconnect()` | Standby | 取消连接 |
944
- | | `close()` | Closed | 清除身份 |
945
- | **Ready** | `disconnect()` | Standby | 主动断开 |
946
- | | 网络断开 | RetryBackoff | 自动推进,启动退避 |
947
- | | `close()` | Closed | 清除身份 |
948
- | **RetryBackoff** | 退避到期 | Reconnecting | 自动推进 |
949
- | | `connect(opts?)` | Reconnecting | 跳过退避,立即重连 |
950
- | | `disconnect()` | Standby | 取消重连 |
951
- | | `close()` | Closed | 清除身份 |
952
- | **Reconnecting** | 成功 | Ready | 自动推进 |
953
- | | 失败(还有次数) | RetryBackoff | 自动推进,递增退避 |
954
- | | 失败(重连耗尽) | ConnectionFailed | 自动推进,记录 lastError |
955
- | | `disconnect()` | Standby | 取消重连 |
956
- | | `close()` | Closed | 清除身份 |
957
- | **ConnectionFailed** | `connect(opts?)` | Connecting | 重新尝试 |
958
- | | `disconnect()` | Standby | 放弃重试 |
959
- | | `close()` | Closed | 清除身份 |
960
- | **Closed** | `loadIdentity(aid)` | Standby | 重新激活 |
961
-
962
- ---
963
-
964
- #### 3.2.4 方法可用性矩阵
965
-
966
- | 方法 | NoIdentity | Standby | Authenticated | Connecting | Ready | RetryBackoff | Reconnecting | ConnectionFailed | Closed |
967
- |------|:----------:|:-------:|:-------------:|:----------:|:-----:|:------------:|:------------:|:----------------:|:------:|
968
- | **状态推进** |
969
- | `loadIdentity(aid)` | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
970
- | `authenticate()` | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
971
- | `connect()` | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ❌ |
972
- | `disconnect()` | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
973
- | `close()` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
974
- | **状态查询** |
975
- | `state` (getter) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
976
- | `currentAid` (getter) | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
977
- | `aunPath` (getter) | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
978
- | `nextRetryAt` (getter) | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
979
- | `nextRetryInSeconds` (getter) | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
980
- | `lastError` (getter) | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ |
981
- | `lastErrorCode` (getter) | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ |
982
- | `gatewayHealth` (getter) | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
983
- | `hasIdentity` (getter) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
984
- | `canSign` (getter) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
985
- | `canConnect` (getter) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
986
- | `canSend` (getter) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
987
- | `isReady` (getter) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
988
- | `isOnline` (getter) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
989
- | `isClosed` (getter) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
990
- | **对端管理** |
991
- | `lookupPeer(aid)` | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
992
- | `getPeer(aid)` | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
993
- | `cachePeer(aid)` | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
994
- | `peers()` | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
995
- | **业务操作** |
996
- | `call()` | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
997
- | `on()` | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
998
- | `off()` | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
999
- | **agent.md 上传**(需 token) |
1000
- | `publishAgentMd()` | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
1001
-
1002
- **图例**:
1003
- - ✅ 可调用
1004
- - ❌ 不可调用(抛 StateError)
1005
-
1006
- **说明**:
1007
- - `publishAgentMd` 要求至少 Authenticated(有 token)。Standby 状态下先调 `authenticate()`
1008
- - `disconnect()` 在 ConnectionFailed 状态也可调,从"放弃重试"语义回到 Standby
1009
- - 签名/验签操作直接用 `AID` 实例(`me.signAgentMd()`、`peer.verifyAgentMd()`),不在 AUNClient 上
1010
-
1011
- ---
1012
-
1013
- #### 3.2.5 状态查询属性
1014
-
1015
- ```typescript
1016
- class AUNClient {
1017
- // ─── 基础状态 ─────────────────────────
1018
- get state(): ConnectionState;
1019
- // 'no_identity' | 'standby' | 'authenticated' | 'connecting' | 'ready'
1020
- // | 'retry_backoff' | 'reconnecting' | 'connection_failed' | 'closed'
1021
-
1022
- get currentAid(): AID | null;
1023
- get aunPath(): string | null;
1024
-
1025
- // ─── 重连相关(仅 RetryBackoff 状态有意义) ──
1026
- get nextRetryAt(): Date | null; // 下次重连的绝对时间
1027
- get nextRetryInSeconds(): number | null; // 距下次重连的秒数
1028
- get retryAttempt(): number; // 当前重连次数(从 1 开始)
1029
- get retryMaxAttempts(): number; // 最大重连次数
1030
-
1031
- // ─── 错误信息(RetryBackoff / Reconnecting / ConnectionFailed 时有意义) ──
1032
- get lastError(): Error | null;
1033
- get lastErrorCode(): string | null;
1034
-
1035
- // ─── 网关健康 ─────────────────────────
1036
- get gatewayHealth(): boolean | null;
1037
-
1038
- // ─── Capability Getters ─────────────────
1039
- get hasIdentity(): boolean; // state !== 'no_identity' && state !== 'closed'
1040
- get canSign(): boolean; // hasIdentity && currentAid.isPrivateKeyValid()
1041
- get canConnect(): boolean; // hasIdentity && state !== 'closed'
1042
- get canSend(): boolean; // state === 'ready'
1043
- get isReady(): boolean; // 同 canSend
1044
- get isOnline(): boolean; // ready | retry_backoff | reconnecting
1045
- get isClosed(): boolean; // state === 'closed'
1046
- }
1047
- ```
1048
-
1049
- ---
1050
-
1051
- #### 3.2.6 状态推进决策树
1052
-
1053
- ```
1054
- 我想做什么?
1055
-
1056
- ├─ 加载身份 → loadIdentity()(仅 NoIdentity/Closed 可调)
1057
-
1058
- ├─ 签名/验签 → 任意有身份的状态都可以
1059
-
1060
- ├─ 拿 token(不建长连接)
1061
- │ └─ state === 'standby' → authenticate()
1062
-
1063
- ├─ 上传 agent.md(需 token,不需要长连接)
1064
- │ ├─ state === 'standby' → authenticate() 先
1065
- │ └─ state >= 'authenticated' → publishAgentMd()
1066
-
1067
- ├─ 发送消息(RPC,需长连接)
1068
- │ └─ canSend?
1069
- │ ├─ 是 → call()
1070
- │ └─ 否 → 看当前状态
1071
- │ ├─ 'standby' / 'authenticated' → connect()
1072
- │ ├─ 'retry_backoff' → connect()(跳过退避立即重连)
1073
- │ ├─ 'connection_failed' → connect()(重新尝试)
1074
- │ └─ 'no_identity' / 'closed' → loadIdentity() 先
1075
-
1076
- ├─ 主动断开 → disconnect()(任意连接相关状态都行)
1077
-
1078
- └─ 关闭客户端 → close()(任意状态都可以)
1079
- ```
1080
-
1081
- ---
1082
-
1083
- #### 3.2.7 常见状态转换场景
1084
-
1085
- **场景 1:首次上线**
1086
-
1087
- ```typescript
1088
- const store = new AIDStore({ aunPath: '...', encryptionSeed: '...' });
1089
- const me = store.load('alice.aid.pub').data!.aid;
1090
- const client = new AUNClient(me); // → Standby
1091
- await client.connect(); // Standby → Authenticated → Connecting → Ready
1092
- // connect() 内部自动完成 authenticate(如果还没有 token)
1093
- ```
1094
-
1095
- **场景 2:只想上传 agent.md,不需要长连接**
1096
-
1097
- ```typescript
1098
- const store = new AIDStore({ aunPath: '...', encryptionSeed: '...' });
1099
- const me = store.load('alice.aid.pub').data!.aid;
1100
- const client = new AUNClient(me); // → Standby
1101
-
1102
- await client.authenticate(); // Standby → Authenticated
1103
- await client.publishAgentMd(); // 用 token 上传,不建立长连接
1104
- // 此时 state 仍是 Authenticated
1105
- ```
1106
-
1107
- **场景 3:网络断开后自动重连**
1108
-
1109
- ```typescript
1110
- // 网络断开(自动)
1111
- // Ready → RetryBackoff(退避计时启动)
1112
- console.log('下次重连:', client.nextRetryInSeconds, '秒后');
1113
-
1114
- // 退避到期(自动)
1115
- // RetryBackoff → Reconnecting
1116
-
1117
- // 重连成功(自动)
1118
- // Reconnecting → Ready
1119
- ```
1120
-
1121
- **场景 4:重连等待中应用想立即发消息**
1122
-
1123
- ```typescript
1124
- // 当前 state === 'retry_backoff'
1125
- // 应用想立即发消息,不想等退避
1126
-
1127
- if (!client.canSend) {
1128
- await client.connect(); // 跳过退避,立即进入 Reconnecting
1129
- }
1130
-
1131
- // 等待 Reconnecting → Ready,或监听 'state_change' 事件
1132
- client.on('state_change', ({ to }) => {
1133
- if (to === 'ready') {
1134
- client.call('message.send', {...});
1135
- }
1136
- });
1137
- ```
1138
-
1139
- **场景 5:重连耗尽后手动重试**
1140
-
1141
- ```typescript
1142
- // state === 'connection_failed'
1143
- console.log('重连失败:', client.lastError, client.lastErrorCode);
1144
-
1145
- // 应用决定再试一次(身份还在)
1146
- await client.connect(); // ConnectionFailed → Connecting
1147
- ```
1148
-
1149
- **场景 6:主动断开后回到待命**
1150
-
1151
- ```typescript
1152
- await client.disconnect(); // Ready → Standby
1153
- // ... 做其他事情,比如离线签名
1154
- const signed = client.currentAid!.signAgentMd(content).data!.signed;
1155
- // 想再次上线
1156
- await client.connect(); // Standby → Authenticated → Connecting → Ready
1157
- ```
1158
-
1159
- **场景 7:换个身份继续用**
1160
-
1161
- ```typescript
1162
- await client.close(); // → Closed(身份清除)
1163
- const newMe = store.load('bob.aid.pub').data!.aid;
1164
- client.loadIdentity(newMe); // Closed → Standby(新身份)
1165
- await client.connect(); // → Authenticated → Connecting → Ready
1166
- ```
1167
-
1168
- **场景 8:预热 token**
1169
-
1170
- ```typescript
1171
- // 应用启动时,预先认证以减少后续连接延迟
1172
- const store = new AIDStore({ aunPath: '...', encryptionSeed: '...' });
1173
- const me = store.load('alice.aid.pub').data!.aid;
1174
- const client = new AUNClient(me);
1175
- await client.authenticate(); // → Authenticated(提前拿好 token)
1176
-
1177
- // ... 之后某个时刻需要发消息
1178
- await client.connect(); // 直接从 Authenticated 建连接,不用再认证
1179
- await client.call('message.send', {...});
1180
- ```
1181
-
1182
- ---
1183
-
1184
- #### 3.2.8 状态检查最佳实践
1185
-
1186
- **推荐:用 capability getter**
1187
-
1188
- ```typescript
1189
- // ✅ 推荐
1190
- if (client.canSend) {
1191
- await client.call('message.send', {...});
1192
- } else if (client.canConnect) {
1193
- await client.connect();
1194
- }
1195
-
1196
- if (client.isOnline) {
1197
- console.log('连接活跃中(Ready / RetryBackoff / Reconnecting)');
1198
- }
1199
-
1200
- if (!client.hasIdentity) {
1201
- client.loadIdentity(store.load('alice.aid.pub').data!.aid);
1202
- }
1203
- ```
1204
-
1205
- **RetryBackoff 状态下的常见模式**:
1206
-
1207
- ```typescript
1208
- if (client.state === 'retry_backoff') {
1209
- console.log(`将在 ${client.nextRetryInSeconds} 秒后自动重连`);
1210
- console.log(`已尝试 ${client.retryAttempt}/${client.retryMaxAttempts} 次`);
1211
-
1212
- // 用户点了"立即重连"按钮
1213
- if (userClickedRetryNow) {
1214
- await client.connect(); // 跳过退避
1215
- }
1216
- }
1217
- ```
1218
-
1219
- ---
1220
-
1221
- ### 3.3 AUNClient 错误状态汇总
1222
-
1223
- #### 3.3.1 身份加载错误
1224
-
1225
- | 错误类型 | 触发条件 | 错误码 | 说明 | 恢复建议 |
1226
- |---------|---------|--------|------|---------|
1227
- | `InvalidIdentityError` | `loadIdentity` 传入的 AID 不是有效本地身份 | `INVALID_IDENTITY` | `aid.isPrivateKeyValid() === false` | 传入有效的本地 AID |
1228
- | `StateError` | 在不允许的状态调 `loadIdentity` | `STATE_ERROR` | 仅 NoIdentity / Closed 可调 | 先 `close()` 再 `loadIdentity()` |
1229
-
1230
- #### 3.3.2 连接阶段错误
1231
-
1232
- | 错误类型 | 触发条件 | 错误码 | 说明 | 恢复建议 |
1233
- |---------|---------|--------|------|---------|
1234
- | `AuthError` | 认证失败 | `AUTH_ERROR` | 两阶段登录失败 | 检查 `currentAid.isPrivateKeyValid()` |
1235
- | `TokenExpiredError` | Token 过期 | `TOKEN_EXPIRED` | 访问令牌已过期 | SDK 自动续期,否则重连 |
1236
- | `ConnectionError` | 连接失败 | `CONNECTION_ERROR` | 无法建立 WebSocket 连接 | 检查 gateway 地址和网络 |
1237
- | `GatewayUnreachableError` | 网关不可达 | `GATEWAY_UNREACHABLE` | 网关服务不可用 | 稍后重试或更换 gateway |
1238
- | `NetworkError` | 网络故障 | `NETWORK_ERROR` | 无法连接服务器 | 检查网络连接 |
1239
- | `StateError` | 状态错误 | `STATE_ERROR` | 在不允许的状态调用方法 | 检查 capability getter |
1240
-
1241
- #### 3.3.3 ConnectionFailed 状态的 lastErrorCode
1242
-
1243
- 进入 `ConnectionFailed` 状态时,`lastErrorCode` 会指明具体原因:
1244
-
1245
- | `lastErrorCode` | 含义 | 应对 |
1246
- |----------------|------|------|
1247
- | `RECONNECT_EXHAUSTED` | 重连次数耗尽 | 检查网络后 `connect()` 重试 |
1248
- | `AUTH_REJECTED` | 服务端拒绝认证(身份失效) | 检查 AID 状态,可能需要 rekey |
1249
- | `GATEWAY_UNREACHABLE` | 所有网关都不可达 | 检查 gateway 配置 |
1250
- | `TOKEN_INVALID` | Token 被服务端废弃 | 重新走 connect 流程 |
1251
-
1252
- #### 3.3.4 业务操作错误
1253
-
1254
- | 错误类型 | 触发条件 | 错误码 | 说明 | 恢复建议 |
1255
- |---------|---------|--------|------|---------|
1256
- | `RpcError` | RPC 调用失败 | `RPC_ERROR` | 服务端返回错误 | 检查参数和权限 |
1257
- | `TimeoutError` | 超时 | `TIMEOUT_ERROR` | 请求超时 | 检查网络或增加超时时间 |
1258
- | `NotReadyError` | 未就绪 | `NOT_READY` | 调用 `call()` 但 `canSend === false` | 检查 `state`,必要时 `connect()` |
1259
-
1260
- ---
1261
-
1262
- ## 三附、protected_headers 机制
1263
-
1264
- ### 概述
1265
-
1266
- `protected_headers` 是消息信封的元数据层,随消息明文传输,**网关和接收方都可见**。它独立于消息 payload,用于路由、过滤、审计等场景。对于 V2 加密消息,SDK 会用消息 master_key 对其 HMAC 签名(`_auth` 字段),接收方可验证未被篡改,但网关仍能读取内容。
1267
-
1268
- ### 传输范围
1269
-
1270
- | 场景 | 是否携带 | 网关可见 |
1271
- |------|:-------:|:-------:|
1272
- | `message.send`(加密,默认) | 是 | 是(envelope 外层) |
1273
- | `message.send`(明文) | 是 | 是 |
1274
- | `group.send`(加密,默认) | 是 | 是(envelope 外层) |
1275
- | `group.send`(明文) | 是 | 是 |
1276
- | `message.thought.put` | 是 | 是 |
1277
- | `group.thought.put` | 是 | 是 |
1278
- | `connect()` / `call()` 普通 RPC | 是(实例默认值) | 是 |
1279
-
1280
- ### 字段规范
1281
-
1282
- - 键:只能小写字母、数字、下划线、连字符 `[a-z0-9_-]`
1283
- - 值:自动 toString()
1284
- - 保留键:`_auth`(SDK 内部 HMAC 签名,不可设置)
1285
- - SDK 自动注入:`payload_type`、`sdk_lang`、`sdk_version`
1286
-
1287
- 统一使用 `protected_headers`(snake_case),不再支持 `protectedHeaders` / `headers` 别名。
1288
-
1289
- ### 设置与读取
1290
-
1291
- ```typescript
1292
- client.setProtectedHeaders({ 'x-app': 'evolclaw', 'x-channel': 'aun' }); // 设置
1293
- client.setProtectedHeaders(null); // 清除
1294
- const headers = client.getProtectedHeaders(); // 读取当前值
1295
- ```
1296
-
1297
- 设置后自动附加到所有 `call()`、`sendV2()`、`sendGroupV2()` 调用,无需在每次调用时传入。
1298
-
1299
- ### 接收消息时读取
1300
-
1301
- 收到消息后,`protected_headers` 直接挂在消息对象顶层:
1302
-
1303
- ```typescript
1304
- client.on('message.received', (msg) => {
1305
- // msg.protected_headers — 发送方设置的字段(_auth 已去除)
1306
- const appName = msg.protected_headers?.['x-app'];
1307
- const priority = msg.protected_headers?.['x-priority'];
1308
-
1309
- // msg.payload — 消息内容
1310
- // msg.from — 发送方 AID
1311
- });
1312
-
1313
- client.on('group.message_created', (msg) => {
1314
- const appName = msg.protected_headers?.['x-app'];
1315
- // msg.group_id — 群组 ID
1316
- });
1317
- ```
1318
-
1319
- 解密失败时(`message.undecryptable` / `group.message_undecryptable`),`protected_headers` 同样可读(来自 envelope 外层,无需解密)。
1320
-
1321
- ---
1322
-
1323
- ## 四、完整操作表
1324
-
1325
- ### 4.1 AIDStore 操作表
1326
-
1327
- | 分类 | 方法 | 联网? | 说明 |
1328
- |------|------|:----:|------|
1329
- | **构造** | `new AIDStore({ aunPath, encryptionSeed })` | 否 | 创建 keystore 管理器 |
1330
- | **加载与注册** | `load(aid)` | 否 | 从 keystore 加载 AID 实例 |
1331
- | | `register(aid)` | 是 | 注册新身份并落盘(不返回 AID 实例) |
1332
- | | `list()` | 否 | 列出本地所有有私钥的 AID 元信息 |
1333
- | | `exists(aid)` | 是 | HEAD PKI 证书端点,判断 AID 是否已注册 |
1334
- | **解析对端** | `resolve(aid, opts?)` | 是 | 一站式解析对端:拉证书 + 缓存 + 拉 agent.md + 验签 |
1335
- | | `fetchAgentMd(aid)` | 是 | 下载 agent.md + 自动拉证书 + 验签 |
1336
- | | `checkAgentMd(aid, ttl?)` | 是 | HEAD 比对 etag |
1337
- | | `headAgentMd(aid)` | 是 | HEAD 拿 agent.md 元数据 |
1338
- | **证书运维** | `renewCert(aid)` | 是 | 续签证书并落盘 |
1339
- | | `rekey(aid)` | 是 | 密钥轮换并落盘 |
1340
- | | `changeSeed(oldSeed, newSeed)` | 否 | 更换加密种子 |
1341
- | | `diagnose(aid)` | 是 | 本地 + 远端状态对比 |
1342
- | **资源管理** | `close()` | 否 | 释放内部 HTTP client 等资源 |
1343
-
1344
- ### 4.2 AID 操作表
1345
-
1346
- | 分类 | 方法/属性 | 说明 |
1347
- |------|----------|------|
1348
- | **只读属性** | `aid` `certPem` `publicKey` `certSubject` `certNotBefore` `certNotAfter` `certIssuer` `certFingerprint` `aunPath` | 身份元数据 |
1349
- | **状态判断** | `isCertValid()` | 公钥有效性(链验证 + 有效期) |
1350
- | | `isPrivateKeyValid()` | 私钥有效性(有私钥 + 与公钥配对) |
1351
- | **密码学** | `verify(payload, sig)` | 验签任意 payload |
1352
- | | `verifyAgentMd(content)` | 验签 agent.md |
1353
- | | `sign(payload)` | 签名任意 payload |
1354
- | | `signAgentMd(content)` | 签名 agent.md |
1355
-
1356
- ### 4.3 AUNClient 操作表
1357
-
1358
- | 分类 | 方法 | 联网? | 前置状态 | 状态变迁 | 说明 |
1359
- |------|------|:----:|---------|----------|------|
1360
- | **构造** | `new AUNClient()` | 否 | — | → NoIdentity | 不传身份 |
1361
- | | `new AUNClient(aid)` | 否 | — | → Standby | aid 必须 `isPrivateKeyValid()` |
1362
- | **状态推进** | `loadIdentity(aid)` | 否 | NoIdentity \| Closed | → Standby | 加载/重载身份 |
1363
- | | `connect(opts?)` | 是 | Standby \| Authenticated \| RetryBackoff \| ConnectionFailed | → Connecting / Reconnecting | Standby 时自动先 authenticate;可选 ConnectionOptions 控制连接行为;gateway URL 和 token 来自 authenticate 缓存,不在 opts 中传入 |
1364
- | | `authenticate()` | 是 | Standby | → Authenticated | 拿 token,不建长连接 |
1365
- | | `disconnect()` | 是 | Authenticated \| Connecting \| Ready \| RetryBackoff \| Reconnecting \| ConnectionFailed | → Standby | 主动断开 |
1366
- | | `close()` | 否 | * | → Closed | 清除身份 + 资源 |
1367
- | **状态查询** | `state` (getter) | 否 | — | — | 当前状态字符串 |
1368
- | | `currentAid` (getter) | 否 | hasIdentity | — | 当前本端 AID |
1369
- | | `aunPath` (getter) | 否 | hasIdentity | — | 从 currentAid 取 |
1370
- | | `nextRetryAt` (getter) | 否 | RetryBackoff | — | 下次重连时间 |
1371
- | | `nextRetryInSeconds` (getter) | 否 | RetryBackoff | — | 距下次重连秒数 |
1372
- | | `retryAttempt` (getter) | 否 | — | — | 当前重连次数 |
1373
- | | `lastError` (getter) | 否 | — | — | 最后一次错误对象 |
1374
- | | `lastErrorCode` (getter) | 否 | — | — | 最后一次错误码 |
1375
- | | `gatewayHealth` (getter) | 否 | hasIdentity | — | 最近健康检查 |
1376
- | | `hasIdentity` (getter) | 否 | — | — | 是否已加载身份 |
1377
- | | `canSign` (getter) | 否 | — | — | hasIdentity && 私钥有效 |
1378
- | | `canConnect` (getter) | 否 | — | — | hasIdentity 且非 Closed |
1379
- | | `canSend` (getter) | 否 | — | — | state === 'ready' |
1380
- | | `isReady` (getter) | 否 | — | — | 同 canSend |
1381
- | | `isOnline` (getter) | 否 | — | — | ready \| retry_backoff \| reconnecting |
1382
- | | `isClosed` (getter) | 否 | — | — | state === 'closed' |
1383
- | **对端管理** | `lookupPeer(aid)` | 视缓存 | hasIdentity | — | 查缓存 → 无则解析 |
1384
- | | `getPeer(aid)` | 否 | hasIdentity | — | 仅查缓存 |
1385
- | | `cachePeer(aid)` | 否 | hasIdentity | — | 加入缓存 |
1386
- | | `peers()` | 否 | hasIdentity | — | 列出所有缓存对端 |
1387
- | **业务操作** | `call(method, params)` | 是 | Ready | — | 通用 RPC |
1388
- | | `on(event, handler)` | 否 | hasIdentity | — | 事件订阅 |
1389
- | | `off(event, handler)` | 否 | hasIdentity | — | 取消订阅 |
1390
- | **agent.md 上传** | `publishAgentMd(content?)` | 是 | Authenticated \| Connecting \| Ready | — | 签名 + 上传 |
1391
- | **配置** | `setProtectedHeaders(headers)` | 否 | * | — | 设置实例级 protected_headers,传 null 清除,随时可调 |
1392
- | | `getProtectedHeaders()` | 否 | * | — | 读取当前实例级 protected_headers |
1393
-
1394
- **事件**:
1395
-
1396
- | 事件 | 触发时机 | 数据 |
1397
- |------|---------|------|
1398
- | `state_change` | 状态变化 | `{ from, to }` |
1399
- | `message.received` | 收到 P2P 消息 | `{ from, payload, protected_headers?, context?, ... }` |
1400
- | `group.message_created` | 收到群消息 | `{ from, group_id, payload, protected_headers?, context?, ... }` |
1401
- | `message.recalled` | 消息被撤回 | `{ message_id, from, ... }` |
1402
- | `message.undecryptable` | P2P 消息解密失败 | `{ from, seq, _decrypt_error, protected_headers?, ... }` |
1403
- | `group.message_undecryptable` | 群消息解密失败 | `{ from, group_id, seq, _decrypt_error, protected_headers?, ... }` |
1404
- | `token.refreshed` | token 自动续期 | `{ expiresAt }` |
1405
- | `gateway.disconnect` | 网关主动断开 | `{ reason, code }` |
1406
- | `connection.error` | 连接异常 | `{ error, code }` |
1407
-
1408
- ---
1409
-
1410
- ## 五、典型场景示例
1411
-
1412
- ### 5.1 注册前检查名字是否可用
1413
-
1414
- ```typescript
1415
- const store = new AIDStore({
1416
- aunPath: '/home/user/.evolclaw/aun',
1417
- encryptionSeed: process.env.ENCRYPTION_SEED || ''
1418
- });
1419
-
1420
- const result = await store.exists('alice.aid.pub');
1421
-
1422
- if (result.ok) {
1423
- if (!result.data.exists) {
1424
- // 可以注册
1425
- const reg = await store.register('alice.aid.pub');
1426
- if (reg.ok) console.log('注册成功');
1427
- } else {
1428
- console.log('名字已被占用');
1429
- }
1430
- } else {
1431
- console.log('网络故障,无法确定');
1432
- }
1433
- ```
1434
-
1435
- **网络开销**:1 次 HEAD(~100ms,零 body)
1436
-
1437
- ---
1438
-
1439
- ### 5.2 下载对端 agent.md 并验签
1440
-
1441
- ```typescript
1442
- const store = new AIDStore({
1443
- aunPath: '/home/user/.evolclaw/aun',
1444
- encryptionSeed: ''
1445
- });
1446
-
1447
- const result = await store.fetchAgentMd('bob.aid.pub');
1448
-
1449
- if (result.ok) {
1450
- if (result.data.verification.status === 'verified') {
1451
- console.log('名片有效:', result.data.content);
1452
- } else {
1453
- console.log('验签失败:', result.data.verification.reason);
1454
- }
1455
- } else {
1456
- console.log('下载失败:', result.error.code);
1457
- }
1458
- ```
1459
-
1460
- **网络开销**:
1461
- - 本地有证书缓存:1 次 GET agent.md
1462
- - 本地无证书缓存:1 次 GET agent.md + 1 次 GET cert(可并行)
1463
-
1464
- ---
1465
-
1466
- ### 5.3 离线签 agent.md
1467
-
1468
- ```typescript
1469
- const store = new AIDStore({
1470
- aunPath: '/home/user/.evolclaw/aun',
1471
- encryptionSeed: process.env.ENCRYPTION_SEED || ''
1472
- });
1473
-
1474
- const loadResult = store.load('alice.aid.pub');
1475
- if (!loadResult.ok) {
1476
- console.log('加载失败:', loadResult.error.code);
1477
- return;
1478
- }
1479
-
1480
- const me = loadResult.data.aid;
1481
- if (me.isPrivateKeyValid()) {
1482
- const content = '---\naid: "alice.aid.pub"\nname: "Alice"\n---';
1483
- const signResult = me.signAgentMd(content);
1484
- if (signResult.ok) {
1485
- console.log('签名完成:', signResult.data.signed);
1486
- }
1487
- } else {
1488
- console.log('私钥无效,无法签名');
1489
- }
1490
- ```
1491
-
1492
- ---
1493
-
1494
- ### 5.4 上线发消息
1495
-
1496
- ```typescript
1497
- const store = new AIDStore({
1498
- aunPath: '/home/user/.evolclaw/aun',
1499
- encryptionSeed: process.env.ENCRYPTION_SEED || ''
1500
- });
1501
-
1502
- const me = store.load('alice.aid.pub').data!.aid;
1503
- const client = new AUNClient(me);
1504
-
1505
- await client.connect(); // 自动完成认证 + 建立连接
1506
-
1507
- if (client.canSend) {
1508
- await client.call('message.send', {
1509
- to: 'bob.aid.pub',
1510
- payload: { text: 'Hello' }
1511
- });
1512
- }
1513
-
1514
- await client.close();
1515
- ```
1516
-
1517
- ---
1518
-
1519
- ### 5.5 验对端签名(自动拉证书)
1520
-
1521
- ```typescript
1522
- const store = new AIDStore({
1523
- aunPath: '/home/user/.evolclaw/aun',
1524
- encryptionSeed: ''
1525
- });
1526
-
1527
- // 无本地缓存:resolve 自动拉证书 + 缓存
1528
- const resolveResult = await store.resolve('bob.aid.pub');
1529
- if (!resolveResult.ok) {
1530
- console.log('解析失败:', resolveResult.error.code);
1531
- return;
1532
- }
1533
-
1534
- const peer = resolveResult.data.aid;
1535
- const verifyResult = peer.verifyAgentMd(signedContent);
1536
- if (verifyResult.ok && verifyResult.data.status === 'verified') {
1537
- console.log('验签通过:', verifyResult.data.payload);
1538
- }
1539
- ```
1540
-
1541
- ---
1542
-
1543
- ## 六、设计优势总结
1544
-
1545
- | 维度 | 优势 |
1546
- |------|------|
1547
- | **职责清晰** | AIDStore(管理器)/ AID(值对象)/ AUNClient(连接)三者正交 |
1548
- | **错误处理** | 统一 Result 字典,不抛异常,TypeScript 类型收窄友好 |
1549
- | **性能** | exists 用 HEAD(零 body),resolve 自动缓存证书 |
1550
- | **简洁** | 判断方法精简到 2 个,公开 API 最小化 |
1551
- | **不可变** | AID 加载后不可变,可安全并发使用 |
1552
- | **类型安全** | capability getter 替代字符串比较,编译期检查 |
1553
- | **可测试** | AID 是值对象(无副作用),AUNClient 可注入 AID(易 mock) |
1554
- | **状态清晰** | 状态机图 + 转换表 + 可用性矩阵,一目了然 |
1555
-
1556
- ---
1557
-
1558
- ## 附录:完整 API 迁移对照表
1559
-
1560
- ### A.1 AuthNamespace 方法迁移
1561
-
1562
- | 当前方法 | 新归宿 | 迁移状态 |
1563
- |---------|--------|:--------:|
1564
- | `auth.registerAid({ aid })` | `AIDStore.register(aid)` | ✅ |
1565
- | `auth.loadIdentity({ aid })` | `AIDStore.load(aid)` | ✅ |
1566
- | `auth.authenticate({ aid })` | `AUNClient.connect()` 内部自动完成 | ✅ |
1567
- | `auth.fetchPeerCert({ aid })` | `AIDStore.resolve()` 内部自动完成 | ✅ |
1568
- | `auth.signAgentMd(content, { aid })` | `aid.signAgentMd(content)` | ✅ |
1569
- | `auth.verifyAgentMd(content, { aid, certPem })` | `aid.verifyAgentMd(content)` | ✅ |
1570
- | `auth.uploadAgentMd(content)` | `AUNClient._uploadAgentMd(content)`(内部私有方法) | ✅ |
1571
- | `auth.downloadAgentMd(aid)` | `AIDStore.fetchAgentMd(aid)` | ✅ |
1572
- | `auth.headAgentMd(aid)` | `AIDStore.headAgentMd(aid)` | ✅ |
1573
- | `auth.checkAid({ aid })` | `AIDStore.diagnose(aid)` | ✅ |
1574
- | `auth.renewCert()` | `AIDStore.renewCert(aid)` | ✅ |
1575
- | `auth.rekey()` | `AIDStore.rekey(aid)` | ✅ |
1576
- | `auth.downloadCert(params)` | `AIDStore.resolve()` 内部自动完成 | ✅ |
1577
- | `auth.requestCert(params)` | `AIDStore.register()` 内部自动完成 | ✅ |
1578
- | `auth.trustRoots(params)` | `AUNClient.call('meta.trust_roots', params)` | ✅ RPC 透传 |
1579
-
1580
- ### A.2 AUNClient 方法迁移
1581
-
1582
- | 当前方法 | 新归宿 | 迁移状态 |
1583
- |---------|--------|:--------:|
1584
- | `client.connect(auth, opts)` | `AUNClient.connect()` | ✅ |
1585
- | `client.disconnect()` | `AUNClient.disconnect()` | ✅ |
1586
- | `client.close()` | `AUNClient.close()` | ✅ |
1587
- | `client.call(method, params)` | `AUNClient.call(method, params)` | ✅ |
1588
- | `client.on(event, handler)` | `AUNClient.on(event, handler)` | ✅ |
1589
- | `client.off(event, handler)` | `AUNClient.off(event, handler)` | ✅ |
1590
- | `client.ping(params)` | `AUNClient.call('meta.ping', params)` | ✅ RPC 透传 |
1591
- | `client.status(params)` | `AUNClient.call('meta.status', params)` | ✅ RPC 透传 |
1592
- | `client.trustRoots(params)` | `AUNClient.call('meta.trust_roots', params)` | ✅ RPC 透传 |
1593
- | `client.publishAgentMd()` | `AUNClient.publishAgentMd(content?)` | ✅ |
1594
- | `client.fetchAgentMd(aid)` | `AIDStore.fetchAgentMd(aid)` / `AIDStore.resolve(aid)` | ✅ |
1595
- | `client.checkAgentMd(aid, ttl)` | `AIDStore.checkAgentMd(aid, ttl)` | ✅ |
1596
- | `client.checkGatewayHealth(url, timeout)` | `AUNClient.gatewayHealth` getter + 内部自动检查 | ✅ |
1597
- | `client.listIdentities()` | `AIDStore.list()` | ✅ |
1598
- | `client.setAgentMdPath(path)` | 移除(构造参数) | ✅ 移除 |
1599
- | `FileKeyStore.ChangeSeed(root, old, new)` | `AIDStore.changeSeed(oldSeed, newSeed)` | ✅ |
1600
- | `FileKeyStore.changeSeed(old, new)` | `AIDStore.changeSeed(oldSeed, newSeed)` | ✅ |
1601
- | `client.state` (getter) | `AUNClient.state` (getter) | ✅ |
1602
- | `client.aid` (getter) | `AUNClient.currentAid` (getter) | ✅ |
1603
- | `client.gatewayHealth` (getter) | `AUNClient.gatewayHealth` (getter) | ✅ |
1604
-
1605
- ### A.3 V2 E2EE 方法迁移
1606
-
1607
- | 当前方法 | 新归宿 | 迁移状态 |
1608
- |---------|--------|:--------:|
1609
- | `client.initV2Session()` | `AUNClient.connect()` 内部自动初始化 | ✅ |
1610
- | `client.sendV2(to, payload, opts)` | `AUNClient.call('message.v2.send', {...})` | ✅ RPC 透传 |
1611
- | `client.pullV2()` | `AUNClient.call('message.v2.pull', {...})` | ✅ RPC 透传 |
1612
- | `client.ackV2(seq)` | `AUNClient.call('message.v2.ack', {...})` | ✅ RPC 透传 |
1613
- | `client.sendGroupV2(groupId, payload, opts)` | `AUNClient.call('group.v2.send', {...})` | ✅ RPC 透传 |
1614
- | `client.pullGroupV2(groupId)` | `AUNClient.call('group.v2.pull', {...})` | ✅ RPC 透传 |
1615
- | `client.ackGroupV2(groupId, seq)` | `AUNClient.call('group.v2.ack', {...})` | ✅ RPC 透传 |
1616
-
1617
- ### A.4 Group 方法迁移
1618
-
1619
- | 当前方法 | 新归宿 | 迁移状态 |
1620
- |---------|--------|:--------:|
1621
- | `client.createNamedGroup(name, opts)` | `AUNClient.call('group.create', {...})` | ✅ RPC 透传 |
1622
- | `client.bindGroupAid(groupId, name)` | `AUNClient.call('group.bind_aid', {...})` | ✅ RPC 透传 |
1623
-
1624
- ### A.5 CustodyNamespace 方法迁移
1625
-
1626
- | 当前方法 | 新归宿 | 迁移状态 |
1627
- |---------|--------|:--------:|
1628
- | `custody.setUrl(url)` | 构造参数或配置 | ✅ |
1629
- | `custody.configureUrl(url)` | 构造参数或配置 | ✅ |
1630
- | `custody.discoverUrl(params)` | 内部自动发现 | ✅ |
1631
- | `custody.sendCode(params)` | `AUNClient.call('custody.send_code', {...})` | ✅ RPC 透传 |
1632
- | `custody.bindPhone(params)` | `AUNClient.call('custody.bind_phone', {...})` | ✅ RPC 透传 |
1633
- | `custody.restorePhone(params)` | `AUNClient.call('custody.restore_phone', {...})` | ✅ RPC 透传 |
1634
- | `custody.createDeviceCopy(params)` | `AUNClient.call('custody.create_device_copy', {...})` | ✅ RPC 透传 |
1635
- | `custody.uploadDeviceCopyMaterials(params)` | `AUNClient.call('custody.upload_device_copy_materials', {...})` | ✅ RPC 透传 |
1636
- | `custody.claimDeviceCopy(params)` | `AUNClient.call('custody.claim_device_copy', {...})` | ✅ RPC 透传 |
1637
-
1638
- ### A.6 MetaNamespace 方法迁移
1639
-
1640
- | 当前方法 | 新归宿 | 迁移状态 |
1641
- |---------|--------|:--------:|
1642
- | `meta.ping(params)` | `AUNClient.call('meta.ping', params)` | ✅ RPC 透传 |
1643
- | `meta.status(params)` | `AUNClient.call('meta.status', params)` | ✅ RPC 透传 |
1644
- | `meta.trustRoots(params)` | `AUNClient.call('meta.trust_roots', params)` | ✅ RPC 透传 |
1645
- | `meta.downloadTrustRoots(opts)` | `AIDStore` 构造时自动下载 / `AUNClient.connect()` 内部处理 | ✅ |
1646
- | `meta.verifyTrustRoots(trustList, opts)` | `AIDStore.load()` 内部链验证使用 | ✅ |
1647
- | `meta.importTrustRoots(trustList, opts)` | `AIDStore` 构造时自动导入 | ✅ |
1648
- | `meta.refreshTrustRoots(opts)` | `AIDStore` 内部按需刷新 | ✅ |
1649
- | `meta.downloadIssuerRootCert(issuer, opts)` | `AIDStore.load()` 内部链验证使用 | ✅ |
1650
- | `meta.updateIssuerRootCert(issuer, opts)` | `AIDStore` 内部按需更新 | ✅ |
1651
-
1652
- ### A.7 新增方法(当前 SDK 无对应)
1653
-
1654
- | 新方法 | 归属 | 说明 |
1655
- |--------|------|------|
1656
- | `new AIDStore({ aunPath, encryptionSeed })` | AIDStore | keystore 管理器 |
1657
- | `AIDStore.exists(aid)` | AIDStore | HEAD 检查 AID 是否存在 |
1658
- | `AIDStore.resolve(aid, opts?)` | AIDStore | 一站式解析对端(证书 + agent.md + 验签) |
1659
- | `AIDStore.list()` | AIDStore | 列出本地身份元信息 |
1660
- | `AIDStore.diagnose(aid)` | AIDStore | 本地+远端一致性诊断 |
1661
- | `AUNClient.loadIdentity(aid)` | AUNClient | 加载/重载身份 |
1662
- | `AUNClient.lookupPeer(aid)` | AUNClient | 对端管理 |
1663
- | `AUNClient.getPeer(aid)` | AUNClient | 查缓存 |
1664
- | `AUNClient.cachePeer(aid)` | AUNClient | 加入缓存 |
1665
- | `AUNClient.peers()` | AUNClient | 列出缓存对端 |
1666
- | `AUNClient.hasIdentity` | AUNClient | 是否已加载身份 |
1667
- | `AUNClient.canSign` | AUNClient | 能否签名 |
1668
- | `AUNClient.canConnect` | AUNClient | 能否连接 |
1669
- | `AUNClient.canSend` | AUNClient | 能否发送 |
1670
- | `AUNClient.isReady` | AUNClient | 是否就绪 |
1671
- | `AUNClient.isOnline` | AUNClient | 是否在线 |
1672
- | `AUNClient.isClosed` | AUNClient | 是否已关闭 |
1673
- | `AUNClient.nextRetryAt` | AUNClient | 下次重连时间 |
1674
- | `AUNClient.nextRetryInSeconds` | AUNClient | 距下次重连秒数 |
1675
- | `AUNClient.retryAttempt` | AUNClient | 当前重连次数 |
1676
- | `AUNClient.lastError` | AUNClient | 最后错误对象 |
1677
- | `AUNClient.lastErrorCode` | AUNClient | 最后错误码 |
1678
-
1679
- ### A.8 迁移统计
1680
-
1681
- | 分类 | 数量 | 迁移方式 |
1682
- |------|:----:|---------|
1683
- | 迁移到 AIDStore | 12 | 实例方法 |
1684
- | 迁移到 AID(值对象) | 4 | 密码学操作 |
1685
- | 迁移到 AUNClient | 8 | 实例方法 |
1686
- | 通过 `call()` RPC 透传 | 20 | 不需要专门封装 |
1687
- | 内部自动完成 | 12 | connect/load 内部处理 |
1688
- | 移除 | 1 | `setAgentMdPath` |
1689
- | 新增 | 22 | 新设计独有(含 AIDStore 本身) |
1690
-
1691
- **结论**:当前 SDK 的所有公开功能在新设计中都有对应的实现路径。大量 RPC 方法(V2 E2EE、Group、Custody、Meta)通过 `client.call()` 透传,不需要单独封装——这些是协议层方法,SDK 只需提供通道。
1692
-
1693
- ---
1694
-
1695
- **文档版本**:v4.2
1696
- **最后更新**:2026-05-30
1697
-