@agentunion/fastaun-browser 0.5.20 → 0.5.21

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 (130) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/_packed_docs/CHANGELOG.md +47 -0
  3. package/_packed_docs/INDEX.md +453 -440
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +164 -161
  5. package/_packed_docs/agent.md/working/AID/347/261/273/345/236/213-owner/345/205/263/347/263/273/344/270/216/346/215/242owner/347/274/226/346/216/222/346/226/271/346/241/210.md +530 -0
  6. package/_packed_docs/agent.md/working/AID/347/261/273/345/236/213-owner/345/205/263/347/263/273/345/256/214/346/225/264/350/256/276/350/256/241/344/270/216/345/256/236/346/226/275/350/256/241/345/210/222.md +685 -0
  7. package/_packed_docs/agent.md/working/agent.md-schema/344/270/216/347/255/276/345/217/221/351/252/214/347/255/276/346/265/201/347/250/213/346/276/204/346/270/205.md +349 -0
  8. package/_packed_docs/agent.md/working//346/234/254/345/234/260Docker/346/265/213/350/257/225Bug/346/270/205/345/215/225.md +40 -0
  9. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1433 -1392
  10. package/_packed_docs/protocol/08-AUN-E2EE.md +17 -12
  11. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +2 -0
  12. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +666 -658
  13. package/_packed_docs/sdk/0.5.21-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +368 -0
  14. package/_packed_docs/sdk/0.5.21-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +350 -0
  15. package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +1 -1
  16. package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +5 -1
  17. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +64 -64
  18. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +4 -1
  19. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +42 -39
  20. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +147 -14
  21. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +48 -47
  22. package/_packed_docs/sdk/09-group-rpc-manual.md +5 -2
  23. package/_packed_docs/sdk/09-message-rpc-manual.md +250 -160
  24. package/_packed_docs/sdk/09-/345/207/255/350/257/201/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +71 -0
  25. package/_packed_docs/sdk/09-/347/273/204/347/273/207/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +88 -0
  26. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +134 -130
  27. package/_packed_docs/sdk/INDEX.md +239 -221
  28. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +930 -922
  29. package/_packed_docs/sdk/Ownership2.1-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +106 -43
  30. package/_packed_docs/sdk/Ownership2.1-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +180 -9
  31. package/_packed_docs/sdk/Ownership2.1-TDD/345/210/206/351/230/266/346/256/265/345/256/236/346/226/275/350/256/241/345/210/222.md +708 -708
  32. package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +480 -476
  33. package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +1 -1
  34. package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +2 -2
  35. package/dist/agent-md-schema.d.ts +1 -0
  36. package/dist/agent-md-schema.d.ts.map +1 -1
  37. package/dist/agent-md-schema.js +5 -4
  38. package/dist/agent-md-schema.js.map +1 -1
  39. package/dist/agent-md.d.ts +25 -1
  40. package/dist/agent-md.d.ts.map +1 -1
  41. package/dist/agent-md.js +148 -71
  42. package/dist/agent-md.js.map +1 -1
  43. package/dist/aid-store.d.ts +13 -0
  44. package/dist/aid-store.d.ts.map +1 -1
  45. package/dist/aid-store.js +55 -8
  46. package/dist/aid-store.js.map +1 -1
  47. package/dist/aid.d.ts.map +1 -1
  48. package/dist/aid.js +2 -1
  49. package/dist/aid.js.map +1 -1
  50. package/dist/auth.d.ts.map +1 -1
  51. package/dist/auth.js +4 -0
  52. package/dist/auth.js.map +1 -1
  53. package/dist/bundle.js +4981 -1219
  54. package/dist/cert-utils.d.ts +1 -0
  55. package/dist/cert-utils.d.ts.map +1 -1
  56. package/dist/cert-utils.js +21 -0
  57. package/dist/cert-utils.js.map +1 -1
  58. package/dist/client/delivery.d.ts +101 -16
  59. package/dist/client/delivery.d.ts.map +1 -1
  60. package/dist/client/delivery.js +1172 -160
  61. package/dist/client/delivery.js.map +1 -1
  62. package/dist/client/rpc-pipeline.d.ts +4 -1
  63. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  64. package/dist/client/rpc-pipeline.js +122 -48
  65. package/dist/client/rpc-pipeline.js.map +1 -1
  66. package/dist/client/v2-e2ee.d.ts +22 -3
  67. package/dist/client/v2-e2ee.d.ts.map +1 -1
  68. package/dist/client/v2-e2ee.js +1258 -649
  69. package/dist/client/v2-e2ee.js.map +1 -1
  70. package/dist/client.d.ts +23 -2
  71. package/dist/client.d.ts.map +1 -1
  72. package/dist/client.js +391 -87
  73. package/dist/client.js.map +1 -1
  74. package/dist/config.d.ts +4 -0
  75. package/dist/config.d.ts.map +1 -1
  76. package/dist/config.js +1 -0
  77. package/dist/config.js.map +1 -1
  78. package/dist/events.d.ts +19 -1
  79. package/dist/events.d.ts.map +1 -1
  80. package/dist/events.js +146 -23
  81. package/dist/events.js.map +1 -1
  82. package/dist/facades.d.ts +42 -1
  83. package/dist/facades.d.ts.map +1 -1
  84. package/dist/facades.js +363 -7
  85. package/dist/facades.js.map +1 -1
  86. package/dist/index.d.ts +5 -3
  87. package/dist/index.d.ts.map +1 -1
  88. package/dist/index.js +1 -1
  89. package/dist/index.js.map +1 -1
  90. package/dist/keystore/index.d.ts +14 -1
  91. package/dist/keystore/index.d.ts.map +1 -1
  92. package/dist/keystore/indexeddb-identity-store.d.ts +4 -0
  93. package/dist/keystore/indexeddb-identity-store.d.ts.map +1 -1
  94. package/dist/keystore/indexeddb-identity-store.js +30 -13
  95. package/dist/keystore/indexeddb-identity-store.js.map +1 -1
  96. package/dist/keystore/indexeddb-shared.d.ts +19 -2
  97. package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
  98. package/dist/keystore/indexeddb-shared.js +193 -6
  99. package/dist/keystore/indexeddb-shared.js.map +1 -1
  100. package/dist/keystore/indexeddb-token-store.d.ts +15 -2
  101. package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
  102. package/dist/keystore/indexeddb-token-store.js +308 -27
  103. package/dist/keystore/indexeddb-token-store.js.map +1 -1
  104. package/dist/org-protocol.d.ts +45 -0
  105. package/dist/org-protocol.d.ts.map +1 -1
  106. package/dist/org-protocol.js +757 -11
  107. package/dist/org-protocol.js.map +1 -1
  108. package/dist/ownership-v21.d.ts +21 -4
  109. package/dist/ownership-v21.d.ts.map +1 -1
  110. package/dist/ownership-v21.js +49 -16
  111. package/dist/ownership-v21.js.map +1 -1
  112. package/dist/ownership-workflow.d.ts +30 -0
  113. package/dist/ownership-workflow.d.ts.map +1 -1
  114. package/dist/ownership-workflow.js +412 -54
  115. package/dist/ownership-workflow.js.map +1 -1
  116. package/dist/seq-tracker.d.ts +2 -0
  117. package/dist/seq-tracker.d.ts.map +1 -1
  118. package/dist/seq-tracker.js +3 -2
  119. package/dist/seq-tracker.js.map +1 -1
  120. package/dist/tools/cross-sdk-agent.d.ts +60 -3
  121. package/dist/tools/cross-sdk-agent.d.ts.map +1 -1
  122. package/dist/tools/cross-sdk-agent.js +433 -37
  123. package/dist/tools/cross-sdk-agent.js.map +1 -1
  124. package/dist/transport.d.ts +3 -0
  125. package/dist/transport.d.ts.map +1 -1
  126. package/dist/transport.js +62 -30
  127. package/dist/transport.js.map +1 -1
  128. package/dist/version.d.ts +1 -1
  129. package/dist/version.js +1 -1
  130. package/package.json +1 -1
@@ -41,20 +41,22 @@ await client.call("message.send", {
41
41
  })
42
42
  ```
43
43
 
44
- 当前 SDK 使用 E2EE V2 多设备 wrap 主路径:
45
-
46
- 1. 发送前通过 `message.v2.bootstrap` 或 `group.v2.bootstrap` 获取接收方当前活跃设备、设备 prekey、self-sync 设备和 audit recipients。
47
- 2. SDK 为每条消息生成独立 `master_key`、消息 nonce 和发送方临时 session key,只加密一次正文。
48
- 3. SDK 为每个接收设备生成一条 recipient wrap。设备有可用 SPK 时使用 `3DH` wrap;缺少 SPK 的兼容场景才使用 `1DH` wrap。
49
- 4. E2EE 信封包含 `sender_signature`、AAD、`recipients_digest` / Merkle proof,接收端必须验签、验 AAD、验 recipient proof 后再解密。
50
-
51
- 每条消息独立密钥;V2 当前主路径不再使用旧 `prekey_ecdh_v2` / `long_term_key` 信封作为默认发送格式。旧术语只用于历史兼容文档或迁移排查。
52
-
53
- 接收端校验签名需要发送方 IK。若本地 session 和 PKI 证书都没有目标 sender/device 的 IK,Python、TypeScript、Go、JavaScript SDK 会先给当前消息一次有界同步补救机会:同一发送端的并发请求通过 single-flight 合并,最多阻塞 3 秒调用 `message.v2.bootstrap`;群消息仍未命中时再调用 `group.v2.bootstrap`。获取成功后立即重试当前消息解密;获取失败或超时才进入原有 pending 后台重试,History 则返回结构化解密失败项而不进入实时 pending。该补救会增加少量首条消息延迟,但避免冷启动时“消息已 ACK、IK 稍后才到、当前消息无回调”。
54
-
55
- 同步获取失败意味着当前时点仍不具备验签和解密条件,SDK 不伪造明文,也不因永久坏密文阻塞 Forward/Tail 水位。实时消息通过 `message.undecryptable` / `group.message_undecryptable` 暴露诊断;应用仍需按 `message_id` 幂等处理后续 pending 重试可能产生的补交付。
56
-
57
- ## ProtectedHeaders 与可验证上下文
44
+ 当前 SDK 使用 E2EE V2 多设备 wrap 主路径:
45
+
46
+ 1. 发送前通过 `message.v2.bootstrap` 或 `group.v2.bootstrap` 获取接收方当前活跃设备、设备 prekey、self-sync 设备和 audit recipients。
47
+ 2. SDK 为每条消息生成独立 `master_key`、消息 nonce 和发送方临时 session key,只加密一次正文。
48
+ 3. SDK 为每个接收设备生成一条 recipient wrap。设备有可用 SPK 时使用 `3DH` wrap;缺少 SPK 的兼容场景才使用 `1DH` wrap。
49
+ 4. E2EE 信封包含 `sender_signature`、AAD、`recipients_digest` / Merkle proof,接收端必须验签、验 AAD、验 recipient proof 后再解密。
50
+
51
+ 每条消息独立密钥;V2 当前主路径不再使用旧 `prekey_ecdh_v2` / `long_term_key` 信封作为默认发送格式。旧术语只用于历史兼容文档或迁移排查。
52
+
53
+ 接收端校验签名需要发送方 IK。若本地 session 和 PKI 证书都没有目标 sender/device 的 IK,Python、TypeScript、Go、JavaScript SDK 会先给当前消息一次有界同步补救机会:同一发送端的并发请求通过 single-flight 合并,最多阻塞 3 秒调用 `message.v2.bootstrap`;群消息仍未命中时再调用 `group.v2.bootstrap`。获取成功后立即重试当前消息解密;获取失败或超时才进入原有 pending 后台重试,History 则返回结构化解密失败项而不进入实时 pending。该补救会增加少量首条消息延迟,但避免冷启动时“消息已 ACK、IK 稍后才到、当前消息无回调”。
54
+
55
+ 同步获取失败意味着当前时点仍不具备验签和解密条件,SDK 不伪造明文。P2P peer 路径不能仅凭扫描证明越过未成功解密或交付的消息,永久坏密文同样不推进该处 A/ACK;Group 保持原有终态规则。实时消息通过 `message.undecryptable` / `group.message_undecryptable` 暴露诊断;应用仍需按消息业务身份幂等处理后续 pending 重试可能产生的补交付。
56
+
57
+ ## ProtectedHeaders 与可验证上下文
58
+
59
+ `message.send` / `group.send` 的可选 `need_render` 默认 `true`,由 SDK 提取到外层信封;缺字段及历史 NULL 也按 `true`。它是展示/统计提示,不进入 AAD、`protected_headers`、消息保护签名或元数据 HMAC。RPC `client_signature` 仍按既有完整业务参数规则覆盖它,不能因为不进 AAD 就从 RPC canonical 中排除。
58
60
 
59
61
  `protected_headers` 是 E2EE 信封里的可选元数据字典,语义接近 HTTP headers:适合放 `device_id`、`slot_id`、`device_name`、`os`、`sdk_version`、`app_name` 等需要被接收端识别、且需要防篡改的非业务内容。`payload` 仍然类似 HTTP body,业务内容应放在 `payload` 内。
60
62
 
@@ -69,11 +71,11 @@ await client.call("message.send", {
69
71
 
70
72
  `payload_type` 不需要应用层传入。SDK 会读取加密前 `payload.type`,自动写入 `protected_headers.payload_type`,接收端解密后会校验它与明文 `payload.type` 一致。
71
73
 
72
- `protected_headers` / `headers` 是 send/thought 参数的顶层字段,不放入单独的 `envelope` 入参对象,也不属于业务 `payload`。裸 WebSocket 客户端若自行发送已加密信封,需要把 protected headers 放在自构造的 E2EE 信封内并自行完成 `_auth`,服务端不会替裸 RPC 调用生成或校验明文侧的 protected headers。
73
-
74
- `agent_md` 是独立于 E2EE 的版本提示元数据,不属于 `protected_headers`,也不参与 AAD 或业务鉴权。当前 Message Service V2 P2P 信封可携带 `agent_md.sender`;SDK 也识别 `agent_md.group`。Gateway 在 RPC response / event push 的 `_meta.agent_md_etags` 中注入 `requester`、`peer`、`group`,四端 SDK 会自动写入对应 AID 的 `remote_etag` / `last_modified`,后续仍以下载后的 agent.md 签名验证作为可信依据。
75
-
76
- 示例:
74
+ `protected_headers` / `headers` 是 send/thought 参数的顶层字段,不放入单独的 `envelope` 入参对象,也不属于业务 `payload`。裸 WebSocket 客户端若自行发送已加密信封,需要把 protected headers 放在自构造的 E2EE 信封内并自行完成 `_auth`,服务端不会替裸 RPC 调用生成或校验明文侧的 protected headers。
75
+
76
+ `agent_md` 是独立于 E2EE 的版本提示元数据,不属于 `protected_headers`,也不参与 AAD 或业务鉴权。当前 Message Service V2 P2P 信封可携带 `agent_md.sender`;SDK 也识别 `agent_md.group`。Gateway 在 RPC response / event push 的 `_meta.agent_md_etags` 中注入 `requester`、`peer`、`group`,四端 SDK 会自动写入对应 AID 的 `remote_etag` / `last_modified`,后续仍以下载后的 agent.md 签名验证作为可信依据。
77
+
78
+ 示例:
77
79
 
78
80
  ```python
79
81
  from aun_core import ProtectedHeaders
@@ -109,8 +111,8 @@ await client.call("group.send", {
109
111
 
110
112
  ```json
111
113
  {
112
- "type": "e2ee.p2p_encrypted",
113
- "version": "v2",
114
+ "type": "e2ee.p2p_encrypted",
115
+ "version": "v2",
114
116
  "ciphertext": "...",
115
117
  "protected_headers": {
116
118
  "device_id": "dev-123",
@@ -134,8 +136,8 @@ await client.call("group.send", {
134
136
 
135
137
  计算规则:
136
138
 
137
- 1. 解密流程会得到本条消息的 `master_key`。
138
- 2. `metadata_key = HMAC-SHA256(master_key, "aun-envelope-metadata-key-v1")`。
139
+ 1. 解密流程会得到本条消息的 `master_key`。
140
+ 2. `metadata_key = HMAC-SHA256(master_key, "aun-envelope-metadata-key-v1")`。
139
141
  3. 对字典去掉 `_auth` 后做 canonical JSON:UTF-8、key 排序、紧凑分隔符。
140
142
  4. `tag = HMAC-SHA256(metadata_key, domain + "\0" + canonical_json(body))`。
141
143
  5. `domain` 对 `protected_headers` 为 `aun-protected-headers-v1`,对 `context` 为 `aun-protected-context-v1`。
@@ -292,7 +294,7 @@ client.on("message.received", handler)
292
294
  `message.pull` 返回的消息已自动解密:
293
295
 
294
296
  ```python
295
- result = await client.call("message.pull", {"after_seq": 0, "limit": 50})
297
+ result = await client.call("message.pull", {"sender_aid": "alice.agentid.pub", "limit": 200})
296
298
  for msg in result["messages"]:
297
299
  if msg.get("encrypted"):
298
300
  print(f"加密消息: {msg['payload']}")
@@ -301,20 +303,21 @@ for msg in result["messages"]:
301
303
 
302
304
  ---
303
305
 
304
- ## V2 设备公钥与 SPK 管理
305
-
306
- 连接成功后,SDK 会初始化本设备 V2 session,生成或加载 IK / SPK,并通过 `message.v2.put_peer_pk` 幂等注册当前 P2P 设备 SPK。群组路径会按群生成独立 group SPK,并通过 `group.v2.put_group_pk` 注册。应用层一般无需手动管理这些密钥。
307
-
308
- 当前主路径的要点:
309
-
310
- - P2P 设备 SPK 的 `key_source` 为 `peer_device_prekey`,由 AID 私钥签名背书。
311
- - 群内独立 group SPK 的 `key_source` 为 `group_device_prekey`,按规范化后的 `group_aid` 隔离。
306
+ ## V2 设备公钥与 SPK 管理
307
+
308
+ 连接成功后,SDK 会初始化本设备 V2 session,生成或加载 IK / SPK,并通过 `message.v2.put_peer_pk` 幂等注册当前 P2P 设备 SPK。群组路径会按群生成独立 group SPK,并通过 `group.v2.put_group_pk` 注册。应用层一般无需手动管理这些密钥。
309
+
310
+ 当前主路径的要点:
311
+
312
+ - P2P 设备 SPK 的 `key_source` 为 `peer_device_prekey`,由 AID 私钥签名背书。
313
+ - 群内独立 group SPK 的 `key_source` 为 `group_device_prekey`,按规范化后的 `group_aid` 隔离。
312
314
  - SDK 发送前通过 `message.v2.bootstrap` / `group.v2.bootstrap` 获取目标设备集合和当前 SPK;P2P 定向设备未命中缓存时的单次新鲜重试与失败边界见 [message.send](09-message-rpc-manual.md#messagesend)。
313
- - 旧 SPK 会在本地保留一段安全窗口,用于解密引用旧 SPK 的历史消息;满足已消费和保留窗口条件后才销毁。
314
-
315
- 裸 WebSocket 客户端如果绕过 SDK,需要自行完成同等的 SPK 生成、AID 私钥签名、注册和 bootstrap 逻辑。旧 `message.e2ee.put_prekey/get_prekey` 只用于 legacy `prekey_ecdh_v2` 信封兼容,不是当前 SDK 的默认路径。
316
-
317
- SPK 签名里的 `spk_timestamp` 使用 Unix 秒;消息、群事件和服务端 `timestamp` / `created_at` 等主路径时间字段仍使用 Unix 毫秒。
315
+ - 旧 SPK 会在本地保留一段安全窗口,用于解密引用旧 SPK 的历史消息;满足已消费和保留窗口条件后才销毁。
316
+ - peer 拉取保留原 seq 与历史密文,不更改 AAD。peer ACK 和 slot 级 `legacy_safe_ack_seq` 不是设备全部消费 slot 的安全证明;当前实现可省略 `device_safe_ack_seq`,SDK 不据前两者销毁旧 SPK。
317
+
318
+ 裸 WebSocket 客户端如果绕过 SDK,需要自行完成同等的 SPK 生成、AID 私钥签名、注册和 bootstrap 逻辑。旧 `message.e2ee.put_prekey/get_prekey` 只用于 legacy `prekey_ecdh_v2` 信封兼容,不是当前 SDK 的默认路径。
319
+
320
+ SPK 签名里的 `spk_timestamp` 使用 Unix 秒;消息、群事件和服务端 `timestamp` / `created_at` 等主路径时间字段仍使用 Unix 毫秒。
318
321
 
319
322
  ---
320
323
 
@@ -364,7 +367,7 @@ client = AUNClient(aid)
364
367
  | P2P 消息默认要求发送方签名 | 无 `sender_signature` 的消息被拒绝 |
365
368
  | 群组消息默认要求发送方签名 | `require_signature=True`,无签名或无发送方证书的消息被拒绝 |
366
369
  | 群组 E2EE 为固定启用能力 | `group_e2ee=true`,不可关闭 |
367
- | 默认要求前向保密 | V2 优先使用设备 SPK 的 `3DH` wrap;缺少 SPK 的 `1DH` 路径仅作为兼容降级 |
368
- | 客户端操作签名 | SDK 会为关键操作附加 `client_signature`;Gateway 对 `send/pull/ack` 等常规 RPC 优先使用连接级身份认证,只有身份声明与连接不一致、敏感操作、能力身份或主动携签场景才执行 ECDSA 验签 |
370
+ | 默认要求前向保密 | V2 优先使用设备 SPK 的 `3DH` wrap;缺少 SPK 的 `1DH` 路径仅作为兼容降级 |
371
+ | 客户端操作签名 | SDK 会为关键操作附加 `client_signature`;Gateway 对 `send/pull/ack` 等常规 RPC 优先使用连接级身份认证,只有身份声明与连接不一致、敏感操作、能力身份或主动携签场景才执行 ECDSA 验签 |
369
372
 
370
373
 
@@ -67,7 +67,7 @@ store := aun.NewAIDStore(aunPath, encryptionSeed, aun.AIDStoreOptions{
67
67
  | `register(aid)` | `register(aid)` | `Register(ctx, aid[, opts])` | 注册并落盘证书和私钥;Go 两参数旧调用仍可用,显式选项使用 `AIDStoreRegisterOptions` |
68
68
  | `list()` | `list()` | `List()` | 列出本地 AID |
69
69
  | `exists(aid)` | `exists(aid)` | `Exists(ctx, aid)` | 远端存在性检查 |
70
- | `resolve(aid, opts=None)` | `resolve(aid, opts?)` | `Resolve(ctx, aid, opts...)` | 拉证书并缓存;自有私钥身份优先本地名片草稿/缓存,可用 `skip_agent_md` / `skipAgentMd` / `SkipAgentMD` 跳过名片 |
70
+ | `resolve(aid, opts=None)` | `resolve(aid, opts?)` | `Resolve(ctx, aid, opts...)` | 解析身份及名片,返回结构化 `agent_md.frontmatter`;可按需复用 ownership 共识校验并缓存 owner.md,完整输入输出见下节 |
71
71
  | `upload_agent_md(aid, content=None, *, uploader_aid=None, co_signer_aid=None)` | `uploadAgentMd(aid, content?, options?)` | `UploadAgentMD(ctx, aid, content...)` / `UploadAgentMDWithOptions(ctx, aid, opts, content...)` | 普通主体自签自传;Group/Org 实体主签、owner token 上传及可选 owner 附签,见协议 §2.5 |
72
72
  | `download_agent_md(aid, timeout_s=None)` | `downloadAgentMd(aid, timeoutMs=30000)` | `DownloadAgentMD(ctx, aid)` | 下载 agent.md,返回 `DownloadAgentMdResult` / `AgentMDInfo` |
73
73
  | `check_agent_md(aid, ttl_days=1)` | `checkAgentMd(aid, ttlDays=1)` | `CheckAgentMD(ctx, aid, maxUnsyncedDays...)` | 通过 HEAD 和本地记录检查一致性 |
@@ -83,6 +83,125 @@ Python、TS/Node 与 Go 的 `AIDStore` 本地方法返回 Result 包装;浏览
83
83
 
84
84
  自有私钥身份的 `resolve` 默认优先返回本地草稿/缓存,标记 `verification.status=unverified`、`source.agent_md_fetched=false`;草稿的 `in_sync=false`。需要读取远端内容时,显式下载或强制刷新,本地草稿仍保留;刷新选项分别为 Python `force_refresh`、TS/JS `forceRefresh`、Go `ForceRefresh`。失败稿由应用显式重试上传,重连不自动重传。
85
85
 
86
+ ### resolve 输入与输出
87
+
88
+ 沿用 `AIDStore.resolve`,不增加独立的 frontmatter 查询方法。`agent_md.content` 保留完整原文,`agent_md.frontmatter` 提供同一名片的 YAML 元数据;普通调用不额外查询 ownership。
89
+
90
+ | SDK | 签名 |
91
+ |-----|------|
92
+ | Python | `await store.resolve(aid: str, opts: dict \| None = None) -> Result[ResolveResult]` |
93
+ | TypeScript / JavaScript | `await store.resolve(aid: string, opts?: ResolveOpts): Promise<Result<ResolveResult>>` |
94
+ | Go | `store.Resolve(ctx context.Context, aid string, opts ...AIDStoreResolveOptions) Result[AIDStoreResolveResult]` |
95
+
96
+ #### 输入与 opts
97
+
98
+ `aid` 为目标 AID 字符串,如 `alice.agentid.pub`。`opts` 全部可选;Go 不传 options 时使用零值选项。
99
+
100
+ | TS/JS | Python | Go | 类型、默认值与行为 |
101
+ |-------|--------|----|--------------------|
102
+ | `forceRefresh` | `force_refresh` | `ForceRefresh` | bool,默认 false;绕过本地身份/名片优先路径并刷新证书和远端名片;启用 ownership 时也绕过共识缓存 |
103
+ | `skipAgentMd` | `skip_agent_md` | `SkipAgentMD` | bool,默认 false;省略输出的 `agent_md`。与 ownership 同用时,共识校验仍可在内部读取当前 agent.md |
104
+ | `timeout` | `timeout` | `Timeout` | TS/JS 为毫秒数,名片下载默认 10000 ms;Python 为毫秒数,未传时名片下载默认 30 s;Go 为 `time.Duration`,默认 10 s,约束整个 Resolve context |
105
+ | `ownership` | `ownership` | `Ownership` | 默认不传;传入已有 `AUNClient` 或实现共识查询接口的对象,调用 `checkOwnershipConsensus(aid)` / `check_ownership_consensus(aid)` / `CheckOwnershipConsensus(ctx, aid)`;不接受布尔开关 |
106
+ | `ownershipTtlMs` | `ownership_ttl_ms` | `OwnershipTTL` | TS/JS/Python 为毫秒数,默认 60000;Go 为 `*time.Duration`,nil 表示一分钟,指向 0 表示不复用缓存;各端的 0 均表示每次查询 |
107
+
108
+ Python 兼容 `forceRefresh`、`skipAgentMd`、`timeoutMs`、`ownershipTtlMs` 别名。TS/JS/Python 的 timeout 沿用名片下载的超时范围,共识请求沿用所传 client 的配置;Go 将 Resolve context 传给共识入口。`ownership` 使用现有身份和连接,不在 resolve 中新建 client。
109
+
110
+ #### 成功输出
111
+
112
+ 返回既有 Result 包装:Python/TS/JS 读取 `ok/data/error`,Go 读取 `Ok/Data/Error`。
113
+
114
+ | Python / TS / JS 的 `data` 字段 | Go 字段 | 类型与含义 |
115
+ |-------------------------------|---------|------------|
116
+ | `aid` | `AID` | 目标 `AID` 对象,含既有证书与身份能力;不是 AID 字符串 |
117
+ | `agent_md` | `AgentMD` | `DownloadAgentMdResult` / `AgentMDInfo`,含原文、frontmatter、验签与缓存信息;skip 时省略,Go 为 nil |
118
+ | `ownership` | `Ownership` | 共识结果加 `checked_at/cached`;未传 ownership 时省略,Go 为 nil |
119
+ | `source.cert_from_cache` | `Source.CertFromCache` | bool,身份材料是否由本地加载 |
120
+ | `source.agent_md_fetched` | `Source.AgentMDFetched` | bool,是否执行名片下载;包含使用本地内容完成的 HTTP 304;本地草稿/缓存和 skip 路径为 false |
121
+
122
+ 身份解析、证书或名片下载失败沿用原有外层 `ok=false` 和错误码。身份及名片已成功时,额外的 ownership 查询失败写入 `ownership.status=unavailable`,外层仍成功,不回退为过期的成功关系。
123
+
124
+ #### frontmatter 结构与公开字段
125
+
126
+ 公开原文 frontmatter 中的全部字段,包括扩展字段;不把字段统一转换成字符串、不扁平化嵌套结构。对象、数组、字符串、数字、布尔值及 null 保留各语言 YAML 解析后的类型。TS/JS 为公开的 `AgentMdFrontmatter` 接口,扩展值类型为 `unknown`;Python 为 `AgentMdFrontmatter = dict[str, Any]`;Go 为 `AgentMDFrontmatter map[string]any`,通过 `AgentMD.Frontmatter` 访问。
127
+
128
+ | 字段 | 类型 | 当前 Schema 含义 |
129
+ |------|------|-----------------|
130
+ | `aid` | string | 名片所属主体的 AID |
131
+ | `name` | string | 显示名 |
132
+ | `type` | string | 开放字符串;常见 human、agent、service、device、group、org、host |
133
+ | `version` | string | 名片 Schema 版本,SemVer,如 `1.0.0` |
134
+ | `exttype` | string | 主体扩展类型 |
135
+ | `description` | string | 简介,不超过 100 个 Unicode 字符 |
136
+ | `avatar` | string | 头像地址,不超过 256 个 Unicode 字符 |
137
+ | `tags` | string[] | 标签 |
138
+ | `owner` | string | 名片声明的 owner;普通主体需结合 2.1 共识状态判断;Group/Org 属于专属生命周期 |
139
+ | `ownership` | object | 普通主体的声明容器,当前核心字段为 `statement` |
140
+ | `entity_cert_fingerprint` | string | Group/Org 实体证书的 `sha256:<hex>` 指纹 |
141
+ | 其它键 | 任意解析值 | 如 visibility、skills 或应用自定义字段,存在时原样返回 |
142
+
143
+ 当前发布 Schema 要求 `aid/name/type/version`;读取接口兼容历史名片,因此类型声明中的标准字段可缺省,不补默认值,裸 Markdown 正文返回 `{}`。`ownership.statement` 保留六字段:`version=2`(整数)、`declaration_id`(小写 UUIDv4)、`owner_aid`、`sub_aid`、`relation_version`(正整数)和 `declared_at`(Unix 秒)。普通主体的 owner 与 statement 同时存在或同时缺省;human 禁止通用 ownership,Group/Org 不使用此声明容器。
144
+
145
+ 字段定义与签名规则以 [当前协议 §1.3、§2](../protocol/Ownership协议定义2.1.md) 为准;[agent.md/SCHEMA.md](../agent.md/SCHEMA.md) 是 legacy Ownership 2.0。`frontmatter.owner` 和已有 `agent_md.verification.ownership_status` 都不替代下面的 2.1 共识结果。
146
+
147
+ #### ownership 输出
148
+
149
+ 查询复用现有双文件共识校验:读取当前 NS agent.md 与 owner Storage 的 `default/sub_aids/{aid}/owner.md`,沿用声明、签名、证书、前后新鲜读取及 30 秒窗口检查。
150
+
151
+ | 字段 | 类型 | 含义 |
152
+ |------|------|------|
153
+ | `status` | string | 下表七种共识状态之一 |
154
+ | `sub_aid` | string | 被查询的目标 AID |
155
+ | `owner_aid` | string \| null | 仅 consensus 返回当前有效 owner,其余状态为 null |
156
+ | `relation_version` | integer \| null | 本次观察的关系代际;尚未取得材料时 TS/JS/Go 为 0,Python 可为 null |
157
+ | `lock` | string \| null | 本次 NS 的 locked/unlocked;尚未取得材料时 TS/JS/Go 为 unlocked,Python 可为 null |
158
+ | `agent_md_etag` | string \| null | 本次共识所读 agent.md 的 ETag |
159
+ | `agent_md_sha256` | string \| null | 本次共识所读完整 agent.md 的 SHA-256 十六进制摘要 |
160
+ | `owner_md_sha256` | string \| null | owner.md 完整原文摘要;缺失时 TS/JS/Go 为空串,Python 可为 null |
161
+ | `ns_protocol` | string,可选 | 既有共识入口提供时保留的 NS 协议标记 |
162
+ | `owner_md` | object,可选 | 实际读取到的 owner 文件快照,结构见下表;未进入 owner 读取阶段时省略 |
163
+ | `code/reason/retryable` | string/string/bool,可选 | 沿用共识入口的错误说明及可重试性,如 unsupported_protocol;不根据缺省值推断成功 |
164
+ | `checked_at` | integer | 本次共识查询完成时间,Unix 毫秒;缓存命中时保留原值 |
165
+ | `cached` | bool | 是否复用了本地共识快照 |
166
+
167
+ | status | 含义 |
168
+ |--------|------|
169
+ | `consensus` | 双方当前文件匹配且通过校验,关系成立 |
170
+ | `released` | 当前材料确认关系已释放 |
171
+ | `unbound` | 当前没有成立的绑定 |
172
+ | `single_sided` | 只有一方声明或待确认材料,关系尚未成立 |
173
+ | `invalid` | 声明、签名或身份绑定等材料无效 |
174
+ | `unavailable` | 网络、证书、服务能力、权威性或读取一致性不足,当前无法确认 |
175
+ | `not_applicable` | Group/Org 等使用专属生命周期,不适用通用共识 |
176
+
177
+ `owner_md` 包含 `owner_aid: string`、`bucket: "default"`、`path: "sub_aids/{aid}/owner.md"`、`exists: bool`、`content: string`(完整原文,含签名)、`sha256: string`、`observed_at: integer`(Storage 观察时间,Unix 秒)。确认不存在时 exists=false、content/sha256 为空串;存在快照不表示整体共识成功,须检查 status。
178
+
179
+ #### 缓存与刷新
180
+
181
+ 共识快照连同 owner.md 原文保存在该 AID 的 `agentmd.json.ownership_snapshot`;浏览器使用 IndexedDB 等价记录,沿用现有存储不可用时的内存回退。该记录不进入名片 frontmatter 或上传内容。
182
+
183
+ 仅在目标匹配、时间未倒退且仍在 TTL 内、上次状态不是 unavailable,并且本次返回的 agent.md 原文摘要与快照一致时复用;skip 路径没有名片内容可比时按目标和 TTL 判断。forceRefresh 绕过缓存,TTL=0 每次重新查询;返回对象与缓存隔离,修改返回值不污染后续结果。
184
+
185
+ 自有身份可返回本地草稿,同时 ownership 检查当前远端原件;两者以 `agent_md_sha256` 区分,不能把草稿中的 owner 当成已成立关系。缓存结果供读取和展示;绑定、释放、转移及其它依赖 owner 的授权操作继续执行原有的新鲜共识校验。
186
+
187
+ ```ts
188
+ const result = await store.resolve('alice.agentid.pub', { ownership: client });
189
+ if (result.ok) console.log(result.data.agent_md?.frontmatter, result.data.ownership?.status);
190
+ ```
191
+
192
+ ```python
193
+ result = await store.resolve("alice.agentid.pub", {"ownership": client})
194
+ if result.ok:
195
+ print(result.data["agent_md"]["frontmatter"], result.data["ownership"]["status"])
196
+ ```
197
+
198
+ ```go
199
+ result := store.Resolve(ctx, "alice.agentid.pub", aun.AIDStoreResolveOptions{Ownership: client})
200
+ if result.Ok && result.Data.AgentMD != nil {
201
+ fmt.Println(result.Data.AgentMD.Frontmatter, result.Data.Ownership.Status)
202
+ }
203
+ ```
204
+
86
205
  ### 通用 sign / verify
87
206
 
88
207
  `format="raw"` 为默认,保留 detached Base64 签名/验签;`format="document"` 处理任意 UTF-8 文本文档的连续尾签,不要求 YAML 或 agent.md 业务字段。sign 返回 `signed` 全文、本次 `signature` 和实际 `signatures` 列表;verify 返回 `valid`、`status`、首块前 `payload` 及逐块 `signatures`。普通 agent.md 与 owner.md 主签不输出且业务不接受 signer_aid;实体主块显式 signer_aid=target,主签身份、Schema、资源及授权由调用者另验,SDK 文件流程和服务端均复用各自唯一同构签验 core。
@@ -107,14 +226,18 @@ Python、TS/Node 与 Go 的 `AIDStore` 本地方法返回 Result 包装;浏览
107
226
  |------|------|------|
108
227
  | `aid` | str | AID 字符串 |
109
228
  | `content` | str | agent.md 原始内容 |
110
- | `verification` | `{status, reason?}` | 验签结果;`status` 为 `"ok"` / `"no_cert"` / `"invalid"` 等 |
229
+ | `frontmatter` | object | 原文 YAML 元数据,字段和类型见 [resolve 规格](#resolve-输入与输出);Go 为 `Frontmatter` |
230
+ | `verification` | `{status, reason?, ownership_status?}` | 名片读取的签名状态,如 verified、unsigned;本地草稿为 unverified,验签失败沿用外层错误;ownership_status 保留既有语义 |
111
231
  | `signature` | dict | 低层签名解析和验签结果 |
112
232
  | `cert_pem` | str | 签名所用证书 PEM |
113
233
  | `etag` | str | HTTP ETag |
114
234
  | `last_modified` | str | HTTP Last-Modified |
115
- | `status` | int | HTTP 状态码;异常 304 且本地有内容时可返回 304 |
235
+ | `status` | int | Python/TS/JS 下载的 HTTP 状态码,缓存内容完成读取时可为 304;Go 的 AgentMDInfo 不提供该字段 |
116
236
  | `in_sync` | bool\|null | 目标是当前 AID 时表示本地内容 ETag 是否等于远端 ETag;对端 AID 通常为 null |
117
- | `saved_to` | str | SDK 管理的本地 agent.md 位置或浏览器 logical key |
237
+ | `saved_to` | str\|null | SDK 管理的本地 agent.md 位置或浏览器 logical key |
238
+ | `save_error` | str\|null | 既有本地保存错误信息,无错误时为空;Go 空值可省略 |
239
+
240
+ resolve 的本地草稿/缓存路径不执行远程下载,其辅助字段沿用各端现状:TS 的 status 为 200、浏览器为 0,Python 可省略 status/signature/saved_to;验签信息不代表本轮重新验签。HTTP 200 与 304 路径均根据实际返回的 content 提供 frontmatter。
118
241
 
119
242
  ### CheckAgentMdResult
120
243
 
@@ -265,6 +388,8 @@ result = await client.call("message.send", {
265
388
 
266
389
  `ttl` 单位为秒:缺省或 `<=0` 保持持久化;正数钳制到 1–3600 秒,只存于消息服务内存且不分配持久 `seq`。四个 SDK 自动维护 volatile 游标、去重及 ACK;返回和事件中的 `storage`、`volatile_cursor`、`created_at`、`expires_at` 可用于识别此类消息。
267
390
 
391
+ `message.send` / `group.send` 的可选 `need_render` 默认为 `true`;SDK 将它放入外层信封,历史缺字段或 NULL 也按 `true`。它用于展示和待同步数量统计,不改变拉取顺序,不进入 E2EE AAD、protected headers、消息保护签名或元数据 HMAC;RPC `client_signature` 仍按原业务参数规则覆盖该字段。
392
+
268
393
  常用 meta RPC 直接透传:
269
394
 
270
395
  ```python
@@ -350,7 +475,7 @@ headers = client.get_protected_headers()
350
475
 
351
476
  | 领域 | 当前入口 | 详细指南 |
352
477
  |------|----------|----------|
353
- | Ownership 2.1 | 七个 Facade:`bindOwneraid`、`bindSubaid`、`releaseSubaid`、`transferSubaid`、`acceptOwnershipRequest`、`rejectOwnershipRequest`、`checkOwnershipConsensus`(TS/JS 命名) | [应用集成](Ownership2.1-SDK应用集成指南.md#4-七个-facade-的参数角色与结果)、[开发与移植](Ownership2.1-SDK开发与移植指南.md) |
478
+ | Ownership 2.1 | 七个关系业务Facade:`bindOwneraid`、`bindSubaid`、`releaseSubaid`、`transferSubaid`、`acceptOwnershipRequest`、`rejectOwnershipRequest`、`checkOwnershipConsensus`;无参数任务管理Facade `resetOwnershipTask()`(Python `reset_ownership_task()`、Go `ResetOwnershipTask(ctx)`),批量清理当前AID发起的未完成任务,返回`{reset_operation_ids:[],skipped_operation_ids:[]}`(其余为TS/JS命名) | [应用集成](Ownership2.1-SDK应用集成指南.md#4-七个-facade-的参数角色与结果)、[开发与移植](Ownership2.1-SDK开发与移植指南.md) |
354
479
  | Credential/Org | Python/TS/JS:`client.credential`、`client.org`;Go:`client.Credential()`、`client.Org()`;包含本地协议助手和业务 facade,服务端可用方法以 RPC 手册为准 | [应用集成](凭证与组织服务SDK应用集成指南.md)、[开发与移植](凭证与组织服务SDK开发与移植指南.md) |
355
480
 
356
481
  Ownership 2.1 关系须检查当前双文件共识;Group/Org 使用专属生命周期,通用关系操作返回 `not_applicable`。Credential/Org 的本地验签不代替当前状态和请求授权检查。
@@ -371,7 +496,7 @@ Ownership 2.1 关系须检查当前双文件共识;Group/Org 使用专属生
371
496
 
372
497
  | 能力 | Python | TS/JS | Go | 说明 |
373
498
  |------|--------|-------|----|------|
374
- | Message | `client.message` | `client.message` | `client.Message()` | 消息便利方法:`send()`、`pull()`、`history()`、`ack()`、`read()`、`read_state/readState/ReadState`、`recall()`、`queryOnline()` |
499
+ | Message | `client.message` | `client.message` | `client.Message()` | 消息便利方法:`send()`、`peers()`、`pull()`、`history()`、`ack()`、`read()`、`read_state/readState/ReadState`、`recall()`、`queryOnline()` |
375
500
  | Message Thought | `client.message.thought` | `client.message.thought` | `client.Message().Thought()` | P2P 思考内容:`put()`、`get()`(不持久化、不分配 seq、强制 E2EE) |
376
501
  | Group | `client.group` | `client.group` | `client.Group()` | 群组便利方法:`create()`、`send()`、`pull()`、`history()`、`ack()`、`read()`、`read_state/readState/ReadState`、群管理方法、群查询方法、群设置便利方法 |
377
502
  | Group Thought | `client.group.thought` | `client.group.thought` | `client.Group().Thought()` | 群思考内容:`put()`、`get()`(不持久化、不分配 seq、强制 E2EE) |
@@ -380,9 +505,11 @@ Ownership 2.1 关系须检查当前双文件共识;Group/Org 使用专属生
380
505
 
381
506
  业务已读与投递 ACK 独立。Python 使用 `read/read_state`,TS/JS 使用 `read/readState`,Go 使用 `Read/ReadState`;四个已读门面保留显式 `None/null/nil` 字段交服务端校验。只有省略 `states` 时兼容顶层单条,显式空/null 批次拒绝;写入回执不是共享汇总。完整字段与事件契约见 [Message RPC](09-message-rpc-manual.md#messageread) 和 [Group RPC](09-group-rpc-manual.md#groupread)。
382
507
 
383
- `message.history` 和 `group.history` 使用 `before_seq` 排他上界向左翻页,下一页直接使用响应的 `next_before_seq`。History 只读,不推进实时 A/T/H、不 ACK、不触发实时消息事件。实时 Push 的最新页同步完全由 SDK 内部自动执行,再按 A/T/H 决定是否补一页 Forward;没有需要应用调用的 Tail 方法或参数。
508
+ 新 SDK 声明严格布尔能力 `p2p_peer_pull_v1: true`,保留设备原共享 seq,按完整 `sender_aid` 隔离 P2P 游标。`message.pull` 指定 sender 时读取该对端;省略时先查询完整 pending `message.peers`,再通过共享 N=4 Gate 并发读取全部待同步对端。显式游标(包括 `after_seq=0`)、force 或 Tail 必须指定 sender。pending 列表不分页、不截断到 200,peer 单页 Pull/History 默认及最大 200 条。目录 `next_pending_seq` 直接使用 peer 服务端 A 作近似;调度优先采用有效非负整数提示的原值(含 0),缺失或非法才回退到 `max(已确认覆盖水位,本地A)`,不取 `max(A,hint)`,不改变应用列表顺序。
384
509
 
385
- 消息回调只提供页级语义:单个 Tail/Forward 响应页内按 seq 升序、同 seq 最多一次;跨 Push/Tail/Forward 页可能重复或乱序。A/T/H 不能用于判断某条消息是否已经回调。全局去重与排序不是 SDK 业务逻辑保证;应用仓库应以 `(namespace, message_id)` 做唯一键,并按 `seq` 排序。SDK 的进程内折叠仅是 best-effort 优化。
510
+ `message.history` 必须指定 `sender_aid`,与 `group.history` 一样使用 `before_seq` 排他上界向左翻页,下一页复用 `next_before_seq`。History 不推进实时 A/T/H、不 ACK、不触发实时消息事件。P2P 仅在已验证自身 `agent.md.type=human` 时自动优先 Tail→Forward,其余类型直接 Forward;指定 sender 后也可显式传 `window_mode="tail"`。Group 保持原实时窗口模型。
511
+
512
+ SDK 保留运行期内存去重:发布序号窗口按 namespace 保留最高 50,000 个 seq;P2P 成功交付绑定最多 50,000 条,按接收 AID、设备、slot、事件类别及原正 delivery seq 识别,另校验 sender/message_id,不因 namespace 或 Push/Pull 来源改变该 P2P 身份。消息热路径不逐条读写耐久 receipt,自定义 store 不要求提供 receipt 能力。所选应用 handler 全部成功后才完成内存标记;持久消息 Forward 页依据有效 proof 提交页 A/T/H 后,再发送本页新增累计 seq ACK,实际耐久性取决于 store 的持久化能力。volatile 与群事件 ACK 沿用各自原路径,失败不提前确认。淘汰、重建 client、进程崩溃或新旧 SDK 切换后允许重放,应用须将消息幂等与业务副作用放在同一持久事务中;持久 P2P 使用 message_id 唯一键,volatile 保留原 sender+message_id 隔离;多个 client/进程没有跨实例原子 claim。跨 peer 不保证全局回调顺序,显式 History/force/Tail 返回列表不因内存去重删行。
386
513
 
387
514
  **群成员管理与状态方法**:
388
515
 
@@ -781,11 +908,15 @@ sub.unsubscribe()
781
908
  | `token.refreshed` | token 刷新完成 |
782
909
  | `token.refresh_exhausted` | refresh_token 缺失、过期或刷新链耗尽,SDK 已清理本地 token 并等待重新登录 |
783
910
  | `message.received` | 收到 P2P 消息 |
911
+ | `message.batch` | P2P 消息/撤回批次,items 保留原 `message.received` / `message.recalled` |
912
+ | `message.peers_snapshot` | SDK 本地待拉对端、水位、可选 next_pending_seq 与整体积压估计;列表保持响应原顺序,应用查询成功时空/非空均发布,后台仅发布非空列表 |
784
913
  | `message.read_changed` / `group.read_changed` | 同一 AID 共享已读位置提交并前进;owner 必须为当前身份、scope 匹配原事件 |
785
914
  | `read_state_sync` | SDK 本地已读补偿完成,含本轮 `owner_aid`、去重 `states` 和逐项 `errors`;连接返回不表示补偿完成 |
786
915
  | `message.undecryptable` | P2P E2EE 解密失败 |
787
916
  | `group.message_created` | 收到群消息 |
917
+ | `group.message_batch` | 群消息/撤回批次,items 保留原 `group.message_created` / `group.message_recalled` |
788
918
  | `group.changed` | 群组事件 |
919
+ | `group.event_batch` | 群事件批次,items 保留原 `group.changed` |
789
920
  | `group.message_recalled` | 群消息撤回 |
790
921
  | `group.message_undecryptable` | 群 E2EE 解密失败 |
791
922
  | `sync.started` / `sync.progress` / `sync.stopped` | Pull 同步生命周期 |
@@ -793,13 +924,15 @@ sub.unsubscribe()
793
924
  | `push.offline_message` | Push Server 收到离线唤醒批次;普通终端无需订阅 |
794
925
  | `storage.object_changed` | Storage 对象变更事件透传 |
795
926
 
796
- 服务端不产生 `message.ack` 通知;SDK 保留的透传订阅不能作为可依赖的 ACK 或对端已读事件。已读事件只向 owner AID 分发且不进入 Pull 队列;重连查询补偿不受消息 `background_sync=false` 影响。身份切换按代际隔离旧缓存与补偿完成事件;已排队的 raw/public `read_changed` 与 `read_state_sync` 在出队和每个处理器执行前重新核对身份代际,处理器等待期间切换身份也会停止后续交付,A→B→A 同样隔离;原在途 RPC 仍可返回。详见 [跨设备已读未读 SDK 集成指南](../跨设备已读未读SDK集成指南.md)。
927
+ `message.ack` 通知与应用业务已读独立。peer ACK 通知携带 `cursor_scope="peer"`、`sender_aid`、`peer_ack_seq`,不能代替本地交付证明或推进旧 global 游标。`message.peers_snapshot` 包含待拉对端、水位、可选 `next_pending_seq` 与整体 `pending_count_estimate`,列表保持响应原顺序,调度只重排内部副本;next_pending_seq只作尽力调度提示,不推进游标或证明完成;取消每 peer/整体三项精确数量;估计仅透传、不求和/缺省填 0,排除 volatile,不用于同步完成或调度;Pull 最终返回页保留含本页 volatile 的三项页内计数;它通过应用事件队列发布,不等待 handler 才继续 Pull,完整字段见 [Message RPC](09-message-rpc-manual.md#messagepeers_snapshot)。已读事件只向 owner AID 分发且不进入 Pull 队列;重连查询补偿不受消息 `background_sync=false` 影响。身份切换按代际隔离旧缓存与补偿完成事件;已排队的 raw/public `read_changed` 与 `read_state_sync` 在出队和每个处理器执行前重新核对身份代际,处理器等待期间切换身份也会停止后续交付,A→B→A 同样隔离;原在途 RPC 仍可返回。详见 [跨设备已读未读 SDK 集成指南](../跨设备已读未读SDK集成指南.md)。
797
928
 
798
929
  `message.received` 的应用层消息保留 `sender_device_id`,表示发送方设备;需要只回复原设备时,将该值作为下一次 `message.send.device_id`。volatile 消息还保留 `storage="volatile"`、`volatile_cursor`、`created_at`、`expires_at`,不含持久 `seq`;内部 slot 不会暴露给应用。
799
930
 
931
+ 三类批量事件统一为 `{namespace,items:[{event,payload}],source}`。每个实际页固定 batch 模式与 batch handlers:订阅该类 batch 时只发 batch,否则完整沿用原逐条发布路径;single listeners 仍动态查找,没有 single 订阅也不跳过原处理链,不双发。批次不跨页或 namespace,空 items 不发布,Push/inline 单条使用单元素批次;保留原 event/payload、页内顺序和 need_render=false。全部所需 handler 成功后才进入各类型原页提交/ACK 路径,失败保留重试责任;Go 可用 `OnBatch(event, func(any) error)`,返回 error 阻止成功确认,原 `On` 保持兼容。详见 [SDK 批量消息事件](09-message-rpc-manual.md#sdk-批量消息事件)。
932
+
800
933
  ### delivery.changed
801
934
 
802
- `delivery.changed` 是 Python、Go、JavaScript 和 TypeScript SDK 统一提供的本地事件,用于通知应用层一批消息已经完成交付,可以从业务仓库刷新相关会话 UI。它不替代 `message.received` / `group.message_created`:应用仍应逐条处理和持久化原消息事件,再把 `delivery.changed` 作为批量刷新信号。
935
+ `delivery.changed` 是 Python、Go、JavaScript 和 TypeScript SDK 统一提供的本地事件,用于通知应用层一批消息已经完成交付,可以从业务仓库刷新相关会话 UI。它不替代消息明细交付:应用通过已订阅的批量事件或原逐条事件完成业务幂等与持久化,再把 `delivery.changed` 作为批量刷新信号。
803
936
 
804
937
  该事件由 SDK 本地 dispatcher 发布,不是 Gateway 推送事件,不属于 JSON-RPC `event/...` 协议;裸 WebSocket 客户端不会收到它。
805
938
 
@@ -819,7 +952,7 @@ sub.unsubscribe()
819
952
  |------|------|------|
820
953
  | `trigger` | string | `pull_drained`、`push` 或 `inline_push` |
821
954
  | `changes` | array | 本次刷新涉及的 namespace,按 `namespace` 稳定排序且不重复 |
822
- | `changes[].namespace` | string | P2P 固定为 `p2p`;群消息为 `group:<canonical-group-aid>` |
955
+ | `changes[].namespace` | string | P2P 仍聚合为逻辑名称 `p2p`,不是内部 peer Pull namespace;群消息为 `group:<canonical-group-aid>` |
823
956
  | `changes[].delivered_count` | integer | 本次合并窗口内已发布到应用层的原消息事件数;不是未读数、服务端页大小或最大 seq |
824
957
 
825
958
  触发与折叠规则:
@@ -829,7 +962,7 @@ sub.unsubscribe()
829
962
  - 有效的 `push` / `inline_push` 可以原子吸收尚未通知的 Pull 变更;这些变更不会在后续 `pull_drained` 中再次通知。
830
963
  - 空 Pull、重复或过滤消息、非法 payload、解密失败,以及未带来有效消息交付的 Head 通知,不发布 `delivery.changed`。
831
964
 
832
- `delivery.changed` 只表示 UI 刷新时机,不是消息明细或持久化账本。应用仓库仍应按照原消息事件的 `(namespace, message_id)` 做幂等,并按 `seq` 排序。
965
+ `delivery.changed` 只表示 UI 刷新时机,不是消息明细或持久化账本。应用仓库仍应按照原消息业务身份幂等,并在对应会话内按 `seq` 排序。
833
966
 
834
967
  ### 消息来源 `source`
835
968
 
@@ -856,7 +989,7 @@ sub.unsubscribe()
856
989
  "source": "pull",
857
990
  "syncing": true,
858
991
  "namespaces_pending": 1,
859
- "namespace": "p2p",
992
+ "namespace": "p2p.peer.v1/YWxpY2UuYWdlbnRpZC5wdWI",
860
993
  "received_total": 0,
861
994
  "started_at": 1730000000000
862
995
  }
@@ -868,7 +1001,7 @@ sub.unsubscribe()
868
1001
  {
869
1002
  "run_id": 7,
870
1003
  "source": "pull",
871
- "namespace": "p2p",
1004
+ "namespace": "p2p.peer.v1/YWxpY2UuYWdlbnRpZC5wdWI",
872
1005
  "page": 2,
873
1006
  "page_raw_count": 50,
874
1007
  "page_pulled_count": 48,
@@ -16,7 +16,7 @@ async def ensure_ready(aid: str) -> AUNClient:
16
16
  loaded = store.load(aid)
17
17
 
18
18
  me = loaded["data"]["aid"]
19
- client = AUNClient(me)
19
+ client = AUNClient(me)
20
20
  await client.connect({"slot_id": "main", "auto_reconnect": True})
21
21
  return client
22
22
  ```
@@ -65,7 +65,7 @@ async def close_all(*clients: AUNClient):
65
65
  sender.e2ee.invalidate_prekey_cache(peer_aid=receiver_aid)
66
66
  receiver.e2ee.invalidate_prekey_cache(peer_aid=sender_aid)
67
67
 
68
- cursor = await receiver.call("message.pull", {"after_seq": 0, "limit": 1})
68
+ cursor = await receiver.call("message.pull", {"sender_aid": sender_aid, "after_seq": 0, "limit": 1})
69
69
  recv_cursor = cursor.get("latest_seq", 0)
70
70
  ```
71
71
 
@@ -78,62 +78,63 @@ recv_cursor = cursor.get("latest_seq", 0)
78
78
  实例级 `protected_headers` 适合放 SDK 版本、运行环境、调用方链路标识等需要签名保护的元数据:
79
79
 
80
80
  ```python
81
- client = AUNClient(me)
82
- client.set_protected_headers({"sdk": "python", "trace": "abc"})
81
+ client = AUNClient(me)
82
+ client.set_protected_headers({"sdk": "python", "trace": "abc"})
83
83
  ```
84
84
 
85
85
  只对 `message.send`、`group.send`、`message.thought.put`、`group.thought.put` 生效。
86
86
 
87
87
  ---
88
88
 
89
- ## 6. Flow Control
90
-
91
- SDK 内部自动管理 RPC 并发,应用层无需配置:
89
+ ## 6. Flow Control
90
+
91
+ SDK 内部自动管理 RPC 并发,应用层无需配置:
92
92
 
93
93
  | 机制 | 说明 |
94
94
  |------|------|
95
- | RPC 并发上限 | 全局最多 16 个并发 RPC 请求 |
96
- | 后台 RPC 限制 | 后台任务额外限制为 8 个 |
95
+ | RPC 并发上限 | 全局最多 16 个并发 RPC 请求 |
96
+ | 后台 RPC 限制 | 后台任务额外限制为 8 个 |
97
97
  | Pull Gate | 四端均为每个 `AUNClient` 一个客户端级 Gate;P2P Message、Group Message、Group Event Pull 共享,同 namespace 单飞,不同 namespace 最多 4 个并行 |
98
- | 队列优先级 | Tail/History 前台 FIFO 优先于 Forward/Gap Fill 后台 FIFO;Group Event Pull 为后台;同 key 自动折叠 |
99
-
98
+ | 队列优先级 | Tail/History 前台 FIFO 优先于 Forward/Gap Fill 后台 FIFO;Group Event Pull 为后台;同 key 自动折叠 |
99
+
100
100
  同一 namespace 的 Tail、History、Forward 保持单飞;相同 key 的调用共享同一结果,其余可运行请求按前台优先、队列顺序调度。namespace 划分见 [核心概念](03-核心概念.md#消息实时窗口与历史拉取)。应用层保持普通 `await client.call(...)` 即可,不应自行实现重复并发控制。
101
-
102
- ---
103
-
104
- ## 7. 消息同步与历史读取
105
-
106
- - V2 Push 只作为服务端 Head 通知。只要本地 H 落后,就先 Tail;Tail 后仍有未扫描 Gap 才执行一页后台 Forward。
107
- - 不要把回调成功当作 ACK 条件。SDK 按服务端原始页最大 seq 推进 Forward 水位;坏密文通过 `message.undecryptable` / `group.message_undecryptable` 单独处理。
108
- - 冷启动缺 sender IK 时允许 SDK 最多同步等待 3 秒 bootstrap;同发送端并发请求自动合并。成功后当前消息立即重试解密,失败或超时才 pending,不要在应用层并发重复拉取 IK。
109
- - SDK 只保证单个 Tail/Forward 响应页内按 seq 升序、同 seq 最多发布一次;A/T/H 只是扫描水位,不是发布账本。
110
- - 实时消息可能从 Push、Tail、Forward 重复到达,跨页、跨通道都不保证全局回调顺序。全局去重与排序由应用业务仓库负责,不属于 SDK 业务逻辑保证;业务仓库必须按 `(namespace, message_id)` 幂等,并按 seq 排序展示。SDK 进程内折叠只能作为 best-effort 优化。
111
- - 用 `message.history(before_seq=..., limit=...)` 或 `group.history(group_id=..., before_seq=..., limit=...)` 向左翻页;下一页直接复用响应的 `next_before_seq`。History 不影响实时游标和 ACK。
112
- - Forward 仅在原始页实际前进且 `has_more`、已知 Head 或旧服务满页兼容规则证明仍有后页时续拉;volatile 消息另按其游标前进及 `volatile_has_more` 续页。正常后页携带已提交水位的 Piggy ACK,末页以独立 ACK 收尾,不会只为 ACK 额外拉取空页。ACK 失败保留待确认水位,供后续 Pull 重试。
113
-
114
- ---
115
-
116
- ## 8. Group Index 更新流程
117
-
118
- 公告、规则、入群要求属于 indexed settings。owner/admin 修改这些内容时,不要直接裸调 `group.set_settings` 写单个 key,应使用 SDK facade 的 `updateGroupIndex` 语义。
119
-
120
- 推荐流程:
121
-
122
- 1. 调用 `checkGroupIndex` 判断本地已处理 etag 与观察到的远端 etag 是否不同步。
123
- 2. 如果不同步,由应用层决定以本地还是远端为准:选择远端时调用 `getGroupIndex` 显式 pull,选择本地时继续保留本地缓存并准备 push。
124
- 3. 在选定基线上合并本地要修改的 settings 和附件稳定引用。
125
- 4. 调用 `updateGroupIndex`。SDK 会读取当前服务端 index、生成新签名 index,并带 `expected_index_etag` 调用 `group.set_settings`。
126
- 5. 如果服务端返回 `group.index etag conflict`,说明提交基线已变化;应用层需要重新决定 pull、merge 或保留本地修改后再提交。
127
-
128
- `getAnnouncement`、`getRules`、`getJoinRequirements` 只读 SDK 本地缓存;本地没有对应值时才读取相应 settings 初始化缓存,不会因为 etag 不一致自动 pull 远端。`updateAnnouncement`、`updateRules`、`updateJoinRequirements` 已内置 indexed 写入路径。普通不进入 index 的设置,例如 `mention_mode`,仍可直接使用 `setSettings` / `set_settings` / `SetSettings`。
129
-
130
- ---
131
-
132
- ## 9. 测试数据保护
133
-
134
- - 不要删除 `AIDs/` 下的私钥、证书、seed、数据库或 token 文件。
135
- - 不要并行跑共享同一身份材料的集成 / E2E / 跨域测试。
136
- - 需要换身份时使用新的 AID 名称,避免制造不可恢复的 key mismatch。
101
+
102
+ ---
103
+
104
+ ## 7. 消息同步与历史读取
105
+
106
+ - P2P 上线/Push 提示由 SDK 查询完整 pending peers 并按 peer 同步;已验证自身 type=human 才自动优先 Tail→Forward,其余直接 Forward。不要用对端 type 决定接收策略,Group 保持原行为。
107
+ - peer A/ACK 只越过已解密并成功交付的消息,覆盖证明不能跳过失败回调;运行中的 server ACK 不替代本地交付。Group 保持原终态规则。
108
+ - 冷启动缺 sender IK 时允许 SDK 最多同步等待 3 秒 bootstrap;同发送端并发请求自动合并。成功后当前消息立即重试解密,失败或超时才 pending,不要在应用层并发重复拉取 IK。
109
+ - 单个 Tail/Forward 响应页内按 seq 升序处理;A/T/H 描述可靠同步进度,SDK 用 50,000 条运行期内存成功记录协调重复交付,不逐条读写持久 receipt。
110
+ - 订阅批量事件时只走 batch,否则完整沿用原单条发布链;全部所需 handler 成功→内存成功标记→可靠页 A/T/H→ACK。内存淘汰、重建 client、崩溃及版本切换后允许重放,应用将消息业务身份去重与副作用放在同一持久事务中。跨 peer 不保证全局回调顺序,volatile 和 Group 维持原去重边界。
111
+ - 用 `message.history(sender_aid=..., before_seq=..., limit=...)` 或 `group.history(group_id=..., before_seq=..., limit=...)` 向左翻页;下一页复用 `next_before_seq`。History 不影响实时游标和 ACK,History/force 返回列表不因内存去重删行。
112
+ - peer Forward 仅按验证通过的覆盖证明及严格 `has_more` 续拉;Group 保持原续页规则。volatile 消息按独立游标及 `volatile_has_more` 续页。正常后页携带已提交水位的 Piggy ACK,末页以独立 ACK 收尾,不会只为 ACK 额外拉取空页。ACK 失败保留待确认水位,供后续 Pull 重试。
113
+ - peer Forward 使用稀疏覆盖证明及严格的持久/volatile `has_more`,不由恰好满页推断;后台 `max_pages` 是工作片预算,达到 100 页后仍有积压会自主续调度。公开无 sender 的 Pull 是全部 pending peer 的聚合,显式游标/force/Tail 必须指定 sender。
114
+
115
+ ---
116
+
117
+ ## 8. Group Index 更新流程
118
+
119
+ 公告、规则、入群要求属于 indexed settings。owner/admin 修改这些内容时,不要直接裸调 `group.set_settings` 写单个 key,应使用 SDK facade 的 `updateGroupIndex` 语义。
120
+
121
+ 推荐流程:
122
+
123
+ 1. 调用 `checkGroupIndex` 判断本地已处理 etag 与观察到的远端 etag 是否不同步。
124
+ 2. 如果不同步,由应用层决定以本地还是远端为准:选择远端时调用 `getGroupIndex` 显式 pull,选择本地时继续保留本地缓存并准备 push。
125
+ 3. 在选定基线上合并本地要修改的 settings 和附件稳定引用。
126
+ 4. 调用 `updateGroupIndex`。SDK 会读取当前服务端 index、生成新签名 index,并带 `expected_index_etag` 调用 `group.set_settings`。
127
+ 5. 如果服务端返回 `group.index etag conflict`,说明提交基线已变化;应用层需要重新决定 pull、merge 或保留本地修改后再提交。
128
+
129
+ `getAnnouncement`、`getRules`、`getJoinRequirements` 只读 SDK 本地缓存;本地没有对应值时才读取相应 settings 初始化缓存,不会因为 etag 不一致自动 pull 远端。`updateAnnouncement`、`updateRules`、`updateJoinRequirements` 已内置 indexed 写入路径。普通不进入 index 的设置,例如 `mention_mode`,仍可直接使用 `setSettings` / `set_settings` / `SetSettings`。
130
+
131
+ ---
132
+
133
+ ## 9. 测试数据保护
134
+
135
+ - 不要删除 `AIDs/` 下的私钥、证书、seed、数据库或 token 文件。
136
+ - 不要并行跑共享同一身份材料的集成 / E2E / 跨域测试。
137
+ - 需要换身份时使用新的 AID 名称,避免制造不可恢复的 key mismatch。
137
138
 
138
139
  ---
139
140
 
@@ -1269,6 +1269,7 @@ SDK 观察到 `_meta.group_indexes` 只记录远端 etag,不会自动覆盖本
1269
1269
  | `payload` | object | 否 | 消息内容 |
1270
1270
  | `type` | string | 否 | 信封/封装类型,普通业务消息无需填写;SDK 加密群消息时自动使用 `e2ee.group_encrypted` |
1271
1271
  | `attachments` | array | 否 | 兼容旧接口的顶层附件元数据;推荐把业务附件放入 `payload.attachments` |
1272
+ | `need_render` | boolean | 否 | 默认 `true`,表示消息是否需要应用渲染;不改变群 seq/Pull/ACK 模型 |
1272
1273
  | `protected_headers` / `headers` | object | 否 | SDK 加密前读取的 E2EE 信封元数据,类似 HTTP headers;推荐使用 `protected_headers`,`headers` 仅作为兼容别名;服务端不解释,接收端验 `_auth` 后在 `e2ee.protected_headers` 暴露 |
1273
1274
 
1274
1275
  ### Payload 参考约定
@@ -1277,6 +1278,8 @@ SDK 观察到 `_meta.group_indexes` 只记录远端 etag,不会自动覆盖本
1277
1278
 
1278
1279
  `protected_headers` 只在 SDK 加密路径生效;裸 RPC 发送明文或已加密信封时,调用方需自行遵守 [05-E2EE加密通信](05-E2EE加密通信.md#protectedheaders-与可验证上下文) 的格式和校验规则。
1279
1280
 
1281
+ `need_render` 与 P2P 一致,由 SDK 提取到外层信封,接收消息保留该标记;历史缺字段或 NULL 按 `true`。该字段不参与 E2EE AAD、`protected_headers`、消息保护签名或元数据 HMAC,RPC `client_signature` 仍按既有完整业务参数规则覆盖它。服务端不按此标记拆流或优先拉取。
1282
+
1280
1283
  **响应**:
1281
1284
 
1282
1285
  ```json
@@ -1438,7 +1441,7 @@ result = await client.call("group.thought.get", {
1438
1441
  |------|------|------|--------|------|
1439
1442
  | `group_id` | string | 是 | — | 群组标识;兼容字段,值语义为目标态 `group_aid` |
1440
1443
  | `after_message_seq` | integer | 否 | 0 | 从该消息 seq 之后拉取 |
1441
- | `limit` | integer | 否 | 50 | SDK 单页上限,最大 50;`pull_max_limit` 配置可进一步收紧 |
1444
+ | `limit` | integer | 否 | 50 | Python V2 Forward 最大 200;其余模式保留原限制,`pull_max_limit` 配置可进一步收紧 |
1442
1445
  | `device_id` | string | 否 | — | 设备 ID(多设备模式) |
1443
1446
 
1444
1447
  **响应**:
@@ -1459,7 +1462,7 @@ result = await client.call("group.thought.get", {
1459
1462
 
1460
1463
  旧 `group.pull` 的服务端 `has_more` 通过 `limit + 1` 可见消息探测及响应截页确定,表示仍有当前成员可见的消息未返回,不由 `unread_count` 或恰好满页推断。底层 `group.v2.pull` 的 Forward 响应可不含该字段,SDK 仍结合 head 与游标判断是否续页。
1461
1464
 
1462
- 当前四个 SDK 的 V2 路由仍将单页限制为 50。直接调用底层 `group.v2.pull` 时,服务端默认 50、最大 200,受 `pull_max_limit` 配置进一步限制;旧 `group.pull`、`group.history` 与 `group.pull_events` 仍为最大 50。
1465
+ Group V2 拉取默认每页 50;Python SDK 的 Forward 上限已为 200。直接调用底层 `group.v2.pull` 时,服务端默认 50、最大 200,受 `pull_max_limit` 配置进一步限制;旧 `group.pull`、`group.history` 与 `group.pull_events` 仍为最大 50。
1463
1466
 
1464
1467
  `group.pull` 是公开的 Forward Pull。实时 A/T/H 最新页同步由 SDK 内部自动完成,不提供需要应用传入的模式参数。普通 Forward 中,`retention_floor_message_seq` 只表示已提交物理 GC;`earliest_available_message_seq-1` 表示服务端综合 retention、入群点和 epoch 后的当前成员可见性排他下界。SDK 以 `max(retention_floor_message_seq, cursor.join_seq, earliest_available_message_seq-1)` 推进 A;History 不修改 A/T/H。
1465
1468