@agentunion/fastaun-browser 0.5.4 → 0.5.8

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 (184) hide show
  1. package/CHANGELOG.md +177 -67
  2. package/_packed_docs/AUN/347/246/273/347/272/277/346/216/250/351/200/201/346/216/245/345/205/245/344/270/216/346/274/224/347/244/272/346/214/207/345/215/227.md +290 -0
  3. package/_packed_docs/AUN/347/246/273/347/272/277/346/216/250/351/200/201/346/234/215/345/212/241/346/236/266/346/236/204/344/270/216/350/257/246/347/273/206/345/256/236/347/216/260/350/256/241/345/210/222-codex.md +994 -0
  4. package/_packed_docs/AUN/347/246/273/347/272/277/346/216/250/351/200/201/346/234/215/345/212/241/350/277/220/347/273/264/344/270/216/345/217/221/345/270/203/346/214/207/345/215/227.md +144 -0
  5. package/_packed_docs/CHANGELOG.md +177 -67
  6. package/_packed_docs/INDEX.md +124 -65
  7. package/_packed_docs/KITE_DOCS_GUIDE.md +65 -30
  8. package/_packed_docs/agent.md/SCHEMA.md +83 -58
  9. package/_packed_docs/agent.md/examples/codeagent-claudecode.md +1 -1
  10. package/_packed_docs/agent.md/examples/openclaw-lobster.md +1 -1
  11. package/_packed_docs/agent.md/examples/signed-openclaw-lobster.md +1 -1
  12. package/_packed_docs/audit/AUN/346/234/215/345/212/241Go/345/214/226/351/207/215/346/236/204/347/262/276/347/273/206/345/214/226/346/272/220/347/240/201/345/256/241/346/237/245-20260718.md +366 -0
  13. package/_packed_docs/aun/345/205/254/347/275/221/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +234 -0
  14. package/_packed_docs/aun/345/210/206/345/270/203/345/274/217/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +1095 -0
  15. package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +1199 -0
  16. package/_packed_docs/cli/CLI/346/211/213/345/206/214.md +6 -4
  17. package/_packed_docs/group-message-rpc-alignment-gaps.md +741 -0
  18. package/_packed_docs/message-online-push-alignment.md +572 -0
  19. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +58 -20
  20. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +219 -247
  21. package/_packed_docs/protocol/12-Stream-/345/255/220/345/215/217/350/256/256.md +14 -14
  22. package/_packed_docs/protocol/13-Agent/350/241/214/344/270/272/350/247/204/350/214/203.md +3 -3
  23. 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 +165 -421
  24. package/_packed_docs/protocol/README.md +1 -0
  25. package/_packed_docs/protocol/aun-docs-guide.md +7 -4
  26. package/_packed_docs/protocol/index.md +25 -19
  27. package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +63 -18
  28. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +22 -0
  29. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +93 -84
  30. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +6 -2
  31. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +73 -34
  32. package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +3 -3
  33. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +21 -9
  34. package/_packed_docs/sdk/09-group-rpc-manual.md +319 -285
  35. package/_packed_docs/sdk/09-message-rpc-manual.md +144 -103
  36. package/_packed_docs/sdk/09-payload-reference.md +1 -1
  37. package/_packed_docs/sdk/09-storage-rpc-manual.md +14 -3
  38. package/_packed_docs/sdk/09-stream-rpc-manual.md +8 -8
  39. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +34 -23
  40. package/_packed_docs/sdk/CHANGELOG-0.5.6.md +73 -0
  41. package/_packed_docs/sdk/INDEX.md +45 -37
  42. package/_packed_docs/sdk/README.md +11 -9
  43. package/_packed_docs//345/217/221/345/270/203/346/212/245/345/221/212-0.5.6.md +260 -0
  44. package/_packed_docs//346/266/210/346/201/257/345/220/214/346/255/245/344/270/216/345/216/206/345/217/262/346/213/211/345/217/226/344/273/243/347/240/201/345/256/241/346/237/245/351/227/256/351/242/230/346/270/205/345/215/225.md +229 -0
  45. package/_packed_docs//346/266/210/346/201/257/345/220/214/346/255/245/344/270/216/345/216/206/345/217/262/346/213/211/345/217/226/346/224/271/351/200/240/346/226/271/346/241/210.md +750 -0
  46. package/dist/agent-md-schema.d.ts +14 -0
  47. package/dist/agent-md-schema.d.ts.map +1 -0
  48. package/dist/agent-md-schema.js +103 -0
  49. package/dist/agent-md-schema.js.map +1 -0
  50. package/dist/agent-md.d.ts +11 -5
  51. package/dist/agent-md.d.ts.map +1 -1
  52. package/dist/agent-md.js +295 -53
  53. package/dist/agent-md.js.map +1 -1
  54. package/dist/aid-store.d.ts +7 -7
  55. package/dist/aid-store.d.ts.map +1 -1
  56. package/dist/aid-store.js +46 -18
  57. package/dist/aid-store.js.map +1 -1
  58. package/dist/aid.d.ts +2 -0
  59. package/dist/aid.d.ts.map +1 -1
  60. package/dist/aid.js +29 -7
  61. package/dist/aid.js.map +1 -1
  62. package/dist/auth.d.ts.map +1 -1
  63. package/dist/auth.js +31 -11
  64. package/dist/auth.js.map +1 -1
  65. package/dist/bundle.js +29920 -19719
  66. package/dist/cert-utils.d.ts +1 -0
  67. package/dist/cert-utils.d.ts.map +1 -1
  68. package/dist/cert-utils.js +36 -16
  69. package/dist/cert-utils.js.map +1 -1
  70. package/dist/client/delivery.d.ts +106 -11
  71. package/dist/client/delivery.d.ts.map +1 -1
  72. package/dist/client/delivery.js +1925 -374
  73. package/dist/client/delivery.js.map +1 -1
  74. package/dist/client/group-state.d.ts.map +1 -1
  75. package/dist/client/group-state.js +27 -24
  76. package/dist/client/group-state.js.map +1 -1
  77. package/dist/client/lifecycle.d.ts +15 -1
  78. package/dist/client/lifecycle.d.ts.map +1 -1
  79. package/dist/client/lifecycle.js +477 -132
  80. package/dist/client/lifecycle.js.map +1 -1
  81. package/dist/client/mention-mode.d.ts +7 -0
  82. package/dist/client/mention-mode.d.ts.map +1 -0
  83. package/dist/client/mention-mode.js +184 -0
  84. package/dist/client/mention-mode.js.map +1 -0
  85. package/dist/client/peers.d.ts +1 -1
  86. package/dist/client/peers.d.ts.map +1 -1
  87. package/dist/client/peers.js +26 -3
  88. package/dist/client/peers.js.map +1 -1
  89. package/dist/client/rpc-pipeline.d.ts +34 -2
  90. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  91. package/dist/client/rpc-pipeline.js +503 -99
  92. package/dist/client/rpc-pipeline.js.map +1 -1
  93. package/dist/client/runtime.d.ts +1 -3
  94. package/dist/client/runtime.d.ts.map +1 -1
  95. package/dist/client/runtime.js +4 -7
  96. package/dist/client/runtime.js.map +1 -1
  97. package/dist/client/v2-e2ee.d.ts +43 -2
  98. package/dist/client/v2-e2ee.d.ts.map +1 -1
  99. package/dist/client/v2-e2ee.js +931 -138
  100. package/dist/client/v2-e2ee.js.map +1 -1
  101. package/dist/client.d.ts +46 -15
  102. package/dist/client.d.ts.map +1 -1
  103. package/dist/client.js +785 -304
  104. package/dist/client.js.map +1 -1
  105. package/dist/errors.d.ts.map +1 -1
  106. package/dist/errors.js +4 -1
  107. package/dist/errors.js.map +1 -1
  108. package/dist/facades.d.ts +26 -1
  109. package/dist/facades.d.ts.map +1 -1
  110. package/dist/facades.js +142 -60
  111. package/dist/facades.js.map +1 -1
  112. package/dist/group-id.d.ts.map +1 -1
  113. package/dist/group-id.js +2 -17
  114. package/dist/group-id.js.map +1 -1
  115. package/dist/group-index.d.ts +6 -1
  116. package/dist/group-index.d.ts.map +1 -1
  117. package/dist/group-index.js +44 -25
  118. package/dist/group-index.js.map +1 -1
  119. package/dist/index.d.ts +5 -4
  120. package/dist/index.d.ts.map +1 -1
  121. package/dist/index.js +3 -2
  122. package/dist/index.js.map +1 -1
  123. package/dist/keystore/index.d.ts +10 -5
  124. package/dist/keystore/index.d.ts.map +1 -1
  125. package/dist/keystore/indexeddb-identity-store.d.ts +0 -12
  126. package/dist/keystore/indexeddb-identity-store.d.ts.map +1 -1
  127. package/dist/keystore/indexeddb-identity-store.js +0 -60
  128. package/dist/keystore/indexeddb-identity-store.js.map +1 -1
  129. package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
  130. package/dist/keystore/indexeddb-shared.js +9 -5
  131. package/dist/keystore/indexeddb-shared.js.map +1 -1
  132. package/dist/keystore/indexeddb-token-store.d.ts +3 -1
  133. package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
  134. package/dist/keystore/indexeddb-token-store.js +47 -2
  135. package/dist/keystore/indexeddb-token-store.js.map +1 -1
  136. package/dist/register-flow.d.ts.map +1 -1
  137. package/dist/register-flow.js +28 -3
  138. package/dist/register-flow.js.map +1 -1
  139. package/dist/seq-tracker.d.ts +28 -8
  140. package/dist/seq-tracker.d.ts.map +1 -1
  141. package/dist/seq-tracker.js +224 -61
  142. package/dist/seq-tracker.js.map +1 -1
  143. package/dist/storage/vfs.d.ts +1 -0
  144. package/dist/storage/vfs.d.ts.map +1 -1
  145. package/dist/storage/vfs.js +26 -2
  146. package/dist/storage/vfs.js.map +1 -1
  147. package/dist/tools/cross-sdk-agent.js +399 -11
  148. package/dist/tools/cross-sdk-agent.js.map +1 -1
  149. package/dist/transport.d.ts +12 -0
  150. package/dist/transport.d.ts.map +1 -1
  151. package/dist/transport.js +226 -102
  152. package/dist/transport.js.map +1 -1
  153. package/dist/v2/session/session.d.ts.map +1 -1
  154. package/dist/v2/session/session.js +3 -4
  155. package/dist/v2/session/session.js.map +1 -1
  156. package/dist/v2/state/commitment.d.ts.map +1 -1
  157. package/dist/v2/state/commitment.js +1 -2
  158. package/dist/v2/state/commitment.js.map +1 -1
  159. package/dist/validators.d.ts.map +1 -1
  160. package/dist/validators.js +8 -14
  161. package/dist/validators.js.map +1 -1
  162. package/dist/version.d.ts +1 -1
  163. package/dist/version.js +1 -1
  164. package/package.json +11 -9
  165. package/dist/group-resources.d.ts +0 -98
  166. package/dist/group-resources.d.ts.map +0 -1
  167. package/dist/group-resources.js +0 -635
  168. package/dist/group-resources.js.map +0 -1
  169. package/dist/keystore/indexeddb.d.ts +0 -179
  170. package/dist/keystore/indexeddb.d.ts.map +0 -1
  171. package/dist/keystore/indexeddb.js +0 -2031
  172. package/dist/keystore/indexeddb.js.map +0 -1
  173. package/dist/namespaces/auth.d.ts +0 -98
  174. package/dist/namespaces/auth.d.ts.map +0 -1
  175. package/dist/namespaces/auth.js +0 -992
  176. package/dist/namespaces/auth.js.map +0 -1
  177. package/dist/namespaces/custody.d.ts +0 -51
  178. package/dist/namespaces/custody.d.ts.map +0 -1
  179. package/dist/namespaces/custody.js +0 -302
  180. package/dist/namespaces/custody.js.map +0 -1
  181. package/dist/namespaces/meta.d.ts +0 -109
  182. package/dist/namespaces/meta.d.ts.map +0 -1
  183. package/dist/namespaces/meta.js +0 -549
  184. package/dist/namespaces/meta.js.map +0 -1
@@ -0,0 +1,572 @@
1
+ # Message 在线推送场景对齐分析
2
+
3
+ **审查日期**: 2026-08-16
4
+ **审查范围**: Python vs Go message 在线推送机制实现
5
+ **审查文件**:
6
+ - Python: `D:\modelunion\kite\extensions\services\message\entry.py`
7
+ - Python: `D:\modelunion\kite\extensions\services\message\online_tracker.py`
8
+ - Go: `D:\modelunion\kite\extensions\services\message\go\cmd\message-go\main.go`
9
+ - Go: `D:\modelunion\kite\extensions\services\message\go\internal\online\tracker.go`
10
+
11
+ ---
12
+
13
+ ## 1. 在线状态管理对齐
14
+
15
+ ### Python 实现 (`online_tracker.py`)
16
+
17
+ **数据结构**:
18
+ ```python
19
+ # OnlineTracker 类
20
+ _aid_to_connections: dict[str, set[str]] # aid → connection_ids
21
+ _aid_device_to_connections: dict[tuple[str, str], set[str]] # (aid, device_id) → connection_ids
22
+ _connection_to_aid: dict[str, str] # connection_id → aid
23
+ _connection_meta: dict[str, dict] # connection_id → metadata
24
+ _aid_delivery_mode: dict[str, dict] # aid → delivery_mode
25
+ _gateway_snapshot_connection_ids: set[str] # Gateway 同步的连接集合
26
+ _redis: redis_client # Redis 连接存储
27
+ ```
28
+
29
+ **在线状态更新机制**:
30
+ - `on_client_online(connection_id, aid, device_id, slot_id, delivery_mode)` — 连接上线
31
+ - `on_client_offline(connection_id)` — 连接下线
32
+ - `sync_with_gateway(snapshot)` — 从 Gateway 在线快照同步
33
+ - `is_online_async(aid)` — 支持 Redis 远程查询
34
+
35
+ **多设备支持**:
36
+ - ✅ 同一 AID 多个 connection_id(`_aid_to_connections` 是 set)
37
+ - ✅ 同一 (aid, device_id) 多个 connection(`_aid_device_to_connections`)
38
+ - ✅ delivery_mode 采用 first-writer-wins 策略(第一个设备的模式生效)
39
+
40
+ ### Go 实现 (`internal/online/tracker.go`)
41
+
42
+ **数据结构**:
43
+ ```go
44
+ // Tracker 结构
45
+ aidToConnections map[string]map[string]struct{} // aid → connection_ids
46
+ aidDeviceToConnections map[deviceKey]map[string]struct{} // (aid, device_id) → connection_ids
47
+ connectionToAID map[string]string // connection_id → aid
48
+ connectionMeta map[string]OnlineInstance // connection_id → metadata
49
+ aidDeliveryMode map[string]map[string]any // aid → delivery_mode
50
+ gatewaySnapshotIDs map[string]struct{} // Gateway 同步的连接集合
51
+ connectionStore ConnectionStore // Redis 连接存储接口
52
+ ```
53
+
54
+ **在线状态更新机制**:
55
+ - `OnClientOnline(connectionID, aid, ClientOnlineOptions)` — 连接上线
56
+ - `OnClientOffline(connectionID)` — 连接下线
57
+ - `SyncWithGateway(snapshot)` — 从 Gateway 在线快照同步
58
+ - `IsOnlineContext(ctx, aid)` — 支持 ConnectionStore 远程查询
59
+
60
+ **多设备支持**:
61
+ - ✅ 同一 AID 多个 connection_id(`aidToConnections` 是 map[string]struct{})
62
+ - ✅ 同一 (aid, device_id) 多个 connection(`aidDeviceToConnections`)
63
+ - ✅ delivery_mode 采用 first-writer-wins 策略(一致)
64
+
65
+ ### 对齐结论
66
+
67
+ ✅ **完全对齐** — 数据结构、多设备支持、delivery_mode 策略完全一致
68
+
69
+ ---
70
+
71
+ ## 2. 推送路径选择对齐
72
+
73
+ ### Python 实现
74
+
75
+ **推送路径**:
76
+ 1. **gateway.dispatch_event** (`_emit_client_event`) — 主路径
77
+ - 直接调用 Gateway 的 `dispatch_event` RPC
78
+ - 支持 fire-and-forget 模式(`AUN_DIRECT_EVENT_MESSAGE=true`)
79
+ - 支持同步模式(等待 Gateway 响应)
80
+ - 路径: `_emit_client_event` → `_gateway_business_call/notify("gateway.dispatch_event", params)`
81
+
82
+ 2. **kernel.event.publish** (`_publish_kernel_event`) — 弃用路径
83
+ - 通过 Kernel 发布事件(非客户端事件)
84
+ - ⚠️ 仅用于非客户端事件(不含 `message.received`/`message.recalled`)
85
+
86
+ **路径选择逻辑**:
87
+ ```python
88
+ async def _publish_event(ws, event: dict):
89
+ event_name = event.get("event", "")
90
+ data = event.get("data", {})
91
+ # 客户端事件走 gateway.dispatch_event
92
+ if event_name in {"message.received", "message.recalled"} or event_name.startswith("aun.service."):
93
+ await _emit_client_event(event_name, data)
94
+ return
95
+ # 其他事件走 kernel.event.publish
96
+ await _publish_kernel_event(ws, event_name, data, _next_fast_id("evt"))
97
+ ```
98
+
99
+ **联邦跨域推送**:
100
+ - ✅ 支持 `federation_forward: true` 选项
101
+ - ✅ Gateway 自动转发到远程 issuer
102
+ - 实现: `_emit_client_event` 中设置 `options: {"federation_forward": True}`
103
+
104
+ ### Go 实现
105
+
106
+ **推送路径**:
107
+ 1. **gatewayattach.DispatchEvent** — 唯一路径
108
+ - 直接调用 Gateway Client 的 `DispatchEvent` 方法
109
+ - 路径: `gatewayMessageEventSink.EmitMessageReceived` → `client.DispatchEvent(ctx, DispatchEventRequest)`
110
+
111
+ 2. **无 kernel.event.publish 路径**
112
+ - ✅ Go 版本完全移除了 Kernel event.publish 路径
113
+
114
+ **路径选择逻辑**:
115
+ ```go
116
+ // 所有客户端事件统一走 DispatchEvent
117
+ func (s gatewayMessageEventSink) EmitMessageReceived(ctx context.Context, data map[string]any) error {
118
+ // ...
119
+ result, err := s.client.DispatchEvent(ctx, gatewayattach.DispatchEventRequest{
120
+ EventID: eventIDForMessageReceived(payload),
121
+ Event: "message.received",
122
+ Targets: map[string]any{
123
+ "aids": []string{toAID},
124
+ "connection_ids": sessionGroup.connectionIDs,
125
+ },
126
+ Payload: gatewayPayloadWithTrace(ctx, payload),
127
+ })
128
+ // ...
129
+ }
130
+ ```
131
+
132
+ **联邦跨域推送**:
133
+ - ✅ 支持通过 `ForwardFederation` 接口转发
134
+ - ⚠️ **但 dispatch_event 本身不携带 federation_forward 选项**
135
+
136
+ ### 对齐结论
137
+
138
+ | 维度 | Python | Go | 对齐状态 |
139
+ |------|--------|-----|----------|
140
+ | 主推送路径 | gateway.dispatch_event | DispatchEvent | ✅ 对齐 |
141
+ | kernel.event.publish | 存在(非客户端事件) | 不存在 | ⚠️ Go 更纯净 |
142
+ | federation_forward 选项 | ✅ 显式传递 | ❌ 缺失 | ⚠️ **P1 缺口** |
143
+
144
+ ---
145
+
146
+ ## 3. 推送逻辑对齐
147
+
148
+ ### Python 实现
149
+
150
+ **推送事件格式**:
151
+ ```python
152
+ params = {
153
+ "event_id": event_id,
154
+ "event": "message.received", # 或 "peer.v2.message_received"
155
+ "source_module": "message",
156
+ "targets": {
157
+ "aids": [to_aid],
158
+ "connection_ids": [...] # 可选
159
+ },
160
+ "payload": {
161
+ "to": to_aid,
162
+ "message_id": message_id,
163
+ "seq": seq,
164
+ "device_id": device_id,
165
+ "_route": {"aids": [...], "connection_ids": [...]},
166
+ "_trace": {"trace_id": "...", "mode": "..."}
167
+ },
168
+ "options": {"federation_forward": True}
169
+ }
170
+ ```
171
+
172
+ **推送参数构造**:
173
+ - `targets` — 由 `_targets_for_message_event` 自动计算
174
+ - 从 `data._route` 提取 aids/connection_ids
175
+ - 兜底从 `data.to` 提取目标 AID
176
+ - `payload` — 包含完整消息体 + `_route` + `_trace`
177
+ - `options.federation_forward` — 启用跨域转发
178
+
179
+ **推送失败降级**:
180
+ ```python
181
+ if _gateway_event_fire_and_forget_enabled(event_name):
182
+ await _gateway_business_notify("gateway.dispatch_event", params)
183
+ return {} # 不等待响应
184
+ else:
185
+ result = await _gateway_business_call("gateway.dispatch_event", params)
186
+ _enqueue_message_offline_push_delivery_evidence(event_name, data, result)
187
+ return result # 记录离线推送证据
188
+ ```
189
+ - fire-and-forget: 静默失败(不影响 send 返回)
190
+ - 同步模式: 记录 `delivered_devices` 用于离线推送决策
191
+
192
+ **推送性能追踪**:
193
+ - ❌ Python 无显式性能追踪(仅依赖 `_trace` 传递)
194
+
195
+ **批量推送 vs 逐个推送**:
196
+ - Python 逐个设备推送(每个 device_id 单独调用 `_emit_client_event`)
197
+
198
+ ### Go 实现
199
+
200
+ **推送事件格式**:
201
+ ```go
202
+ gatewayattach.DispatchEventRequest{
203
+ EventID: eventIDForMessageReceived(payload),
204
+ Event: "message.received", // 或 "peer.v2.message_received"
205
+ Targets: map[string]any{
206
+ "aids": []string{toAID},
207
+ "connection_ids": sessionGroup.connectionIDs,
208
+ },
209
+ Payload: gatewayPayloadWithTrace(ctx, payload),
210
+ }
211
+ ```
212
+
213
+ **推送参数构造**:
214
+ - `Targets` — 显式构造
215
+ - 查询 Gateway sessions: `client.QuerySessions(ctx, QuerySessionsRequest{AID: toAID, DeviceID: deviceID})`
216
+ - 按 slot_id 分组: `groupGatewaySessionConnectionsBySlot(sessions)`
217
+ - 每个 slot 单独推送
218
+ - `Payload` — 包含 `_route` + trace 上下文
219
+ - ⚠️ **无 federation_forward 选项**
220
+
221
+ **推送失败降级**:
222
+ ```go
223
+ delivered := false
224
+ for _, sessionGroup := range sessionGroups {
225
+ result, err := s.client.DispatchEvent(ctx, DispatchEventRequest{...})
226
+ if err != nil {
227
+ dispatchErr = errors.Join(dispatchErr, fmt.Errorf("dispatch failed: %w", err))
228
+ continue // 继续尝试其他 slot
229
+ }
230
+ if !gatewayDispatchSucceeded(result) {
231
+ dispatchErr = errors.Join(dispatchErr, fmt.Errorf("Gateway rejected: result=%v", result))
232
+ continue
233
+ }
234
+ delivered = true
235
+ }
236
+ if !delivered {
237
+ return dispatchErr // 返回失败(影响 send 结果)
238
+ }
239
+ ```
240
+ - **同步模式** — 所有 slot 失败才返回错误
241
+ - 记录 `delivered_devices` 到 `offlinePushCandidates`
242
+
243
+ **推送性能追踪**:
244
+ - ✅ `LogPerf(ctx, "msg_v2_source_self_sync_done", map[string]any{"push_scheduled": 0})`
245
+ - ✅ 详细日志: `servicelog.Warningf("V2 push dispatch failed aid=%s device=%s seq=%d error=%v", ...)`
246
+
247
+ **批量推送 vs 逐个推送**:
248
+ - Go 按 slot 分组批量推送(同一 slot 的多个 connection 一次推送)
249
+
250
+ ### 对齐结论
251
+
252
+ | 维度 | Python | Go | 对齐状态 |
253
+ |------|--------|-----|----------|
254
+ | 事件格式 | ✅ 标准 | ✅ 标准 | ✅ 对齐 |
255
+ | targets 计算 | 自动提取 | QuerySessions + 分组 | ⚠️ Go 更精确 |
256
+ | federation_forward | ✅ 显式传递 | ❌ 缺失 | ⚠️ **P1 缺口** |
257
+ | 失败降级 | fire-and-forget / 同步 | 同步 + 多 slot 重试 | ⚠️ Go 更健壮 |
258
+ | 性能追踪 | ❌ 无 | ✅ 有 | ⚠️ Go 更好 |
259
+ | 推送模式 | 逐设备 | 按 slot 批量 | ⚠️ Go 更高效 |
260
+
261
+ ---
262
+
263
+ ## 4. 推送时机对齐
264
+
265
+ ### Python 实现
266
+
267
+ **send 成功后立即推送**:
268
+ ```python
269
+ # legacy send (entry.py:8700+)
270
+ async def _rpc_send(params: dict):
271
+ # 1. 落库持久化
272
+ await _store_message(...)
273
+ # 2. 立即推送(不影响 send 返回)
274
+ _publish_event(_ws_global, {"event": "message.received", "data": evt_data})
275
+ # 3. 返回 send 结果
276
+ return {"message_id": message_id, "timestamp": timestamp, "seq": seq}
277
+ ```
278
+
279
+ **V2 send 推送**:
280
+ ```python
281
+ # v2 send (entry.py:11100+)
282
+ # 1. 落库
283
+ result = await _v2_store_message_with_recipients(...)
284
+ # 2. 异步推送(后台 task)
285
+ for row in recipients:
286
+ try:
287
+ await _emit_client_event("peer.v2.message_received", push_event_data)
288
+ except Exception as e:
289
+ print(f"WARNING: V2 push event failed (non-fatal)") # 静默失败
290
+ # 3. 返回结果
291
+ return result
292
+ ```
293
+
294
+ **推送与持久化顺序**:
295
+ - 持久化 → 推送(先写库,再推送)
296
+ - 推送失败不影响 send 返回(fire-and-forget 或异常捕获)
297
+
298
+ **推送失败影响**:
299
+ - ❌ 不影响 send 返回(静默失败或后台重试)
300
+
301
+ ### Go 实现
302
+
303
+ **send 成功后立即推送**:
304
+ ```go
305
+ // legacy send (main.go:8620+)
306
+ func (s gatewayMessageEventSink) EmitMessageReceived(ctx context.Context, data map[string]any) error {
307
+ // 1. 查询 Gateway sessions
308
+ sessions, err := s.client.QuerySessions(ctx, QuerySessionsRequest{...})
309
+ // 2. 按 slot 推送
310
+ for _, sessionGroup := range sessionGroups {
311
+ result, err := s.client.DispatchEvent(ctx, DispatchEventRequest{...})
312
+ if err != nil || !gatewayDispatchSucceeded(result) {
313
+ dispatchErr = errors.Join(dispatchErr, err)
314
+ continue
315
+ }
316
+ delivered = true
317
+ }
318
+ // 3. 标记已投递
319
+ if delivered {
320
+ s.markDelivered(ctx, toAID, messageID, deviceID)
321
+ }
322
+ return dispatchErr // ⚠️ 返回错误会影响调用方
323
+ }
324
+ ```
325
+
326
+ **V2 send 推送**:
327
+ ```go
328
+ // v2 send (main.go:8570+)
329
+ for _, pair := range pairs {
330
+ err = sink.EmitV2PeerMessageReceived(ctx, event)
331
+ if err != nil {
332
+ servicelog.Warningf("message V2 push emit failed: %v", err) // 记录警告但不中断
333
+ }
334
+ scheduled++
335
+ }
336
+ ```
337
+
338
+ **推送与持久化顺序**:
339
+ - 持久化 → 推送(先写库,再推送)
340
+ - 推送失败记录到日志但不影响 send 返回
341
+
342
+ **推送失败影响**:
343
+ - ⚠️ **潜在问题**: `EmitMessageReceived` 返回错误,但调用方是否捕获?
344
+ - 需确认 `sink.EmitV2PeerMessageReceived` 调用时是否会传播错误
345
+
346
+ ### 对齐结论
347
+
348
+ | 维度 | Python | Go | 对齐状态 |
349
+ |------|--------|-----|----------|
350
+ | 推送时机 | 持久化后立即推送 | 持久化后立即推送 | ✅ 对齐 |
351
+ | 持久化顺序 | 先写库,再推送 | 先写库,再推送 | ✅ 对齐 |
352
+ | 推送失败影响 send | ❌ 不影响 | ⚠️ 可能影响 | ⚠️ **P2 不一致** |
353
+ | 失败日志 | WARNING 静默 | Warningf 记录 | ✅ 对齐 |
354
+
355
+ ---
356
+
357
+ ## 5. 修复清单
358
+
359
+ ### P0 缺口(严重,影响核心功能)
360
+
361
+ **无**
362
+
363
+ ---
364
+
365
+ ### P1 缺口(重要,影响跨域或可靠性)
366
+
367
+ #### P1-1: Go 缺失 `federation_forward` 选项
368
+
369
+ **位置**: `main.go:8643` / `8702` / `8887` / `8923` / `8952` / `8978`
370
+
371
+ **问题**:
372
+ - Go 在调用 `DispatchEvent` 时未传递 `Options: {"federation_forward": true}`
373
+ - 导致跨域推送无法自动转发到远程 issuer
374
+ - Python 版本通过 `options: {"federation_forward": True}` 启用联邦转发
375
+
376
+ **影响**:
377
+ - 跨 issuer 的在线推送无法送达(仅本地 issuer 生效)
378
+ - 联邦场景下消息只能通过 pull 获取(无实时推送)
379
+
380
+ **修复**:
381
+ ```go
382
+ // 修复前
383
+ result, err := s.client.DispatchEvent(ctx, gatewayattach.DispatchEventRequest{
384
+ EventID: eventIDForMessageReceived(payload),
385
+ Event: "message.received",
386
+ Targets: map[string]any{
387
+ "aids": []string{toAID},
388
+ "connection_ids": sessionGroup.connectionIDs,
389
+ },
390
+ Payload: gatewayPayloadWithTrace(ctx, payload),
391
+ })
392
+
393
+ // 修复后
394
+ result, err := s.client.DispatchEvent(ctx, gatewayattach.DispatchEventRequest{
395
+ EventID: eventIDForMessageReceived(payload),
396
+ Event: "message.received",
397
+ Targets: map[string]any{
398
+ "aids": []string{toAID},
399
+ "connection_ids": sessionGroup.connectionIDs,
400
+ },
401
+ Payload: gatewayPayloadWithTrace(ctx, payload),
402
+ Options: map[string]any{"federation_forward": true}, // 新增
403
+ })
404
+ ```
405
+
406
+ **受影响方法**:
407
+ 1. `EmitMessageReceived` (main.go:8643, 8702)
408
+ 2. `emitV2PeerMessageReceived` (main.go:8887)
409
+ 3. `EmitMessageRecalled` (main.go:8923, 8952, 8978)
410
+ 4. `EmitMessageAck` (main.go:8997)
411
+
412
+ **验证方法**:
413
+ 1. 启动双域测试环境(issuer-a.test + issuer-b.test)
414
+ 2. alice.issuer-a.test 发送消息给 bob.issuer-b.test
415
+ 3. 确认 bob 在线时能实时收到推送(不依赖 pull)
416
+ 4. 检查 gateway logs 确认 `federation.forward` 被调用
417
+
418
+ ---
419
+
420
+ ### P2 缺口(次要,影响体验或一致性)
421
+
422
+ #### P2-1: Go 推送失败可能影响 send 返回
423
+
424
+ **位置**: `main.go:8620` (`EmitMessageReceived` 返回值处理)
425
+
426
+ **问题**:
427
+ - `EmitMessageReceived` 返回 error 时,调用方是否会传播到 send 响应?
428
+ - Python 版本明确静默失败(`except Exception: print(f"WARNING: ...")`)
429
+ - Go 版本返回 `dispatchErr`,但不确定是否被捕获
430
+
431
+ **影响**:
432
+ - 如果错误传播,推送失败会导致 send 失败(不符合异步推送语义)
433
+ - 客户端收到 send 错误,但消息已持久化(状态不一致)
434
+
435
+ **修复**:
436
+ 确认调用方是否捕获错误,如果需要保持 Python 行为,修改为:
437
+ ```go
438
+ // 修复后
439
+ func (s gatewayMessageEventSink) EmitMessageReceived(ctx context.Context, data map[string]any) error {
440
+ // ... 推送逻辑
441
+ if !delivered {
442
+ servicelog.Warningf("message push failed (non-fatal): %v", dispatchErr)
443
+ return nil // 静默失败,不影响 send 返回
444
+ }
445
+ return nil
446
+ }
447
+ ```
448
+
449
+ **验证方法**:
450
+ 1. 模拟 Gateway 不可用(关闭 gateway 服务)
451
+ 2. 发送消息,确认 send 返回成功(不受推送失败影响)
452
+ 3. 确认消息已持久化到数据库
453
+
454
+ ---
455
+
456
+ #### P2-2: Go 缺少 fire-and-forget 配置支持
457
+
458
+ **位置**: 全局推送逻辑
459
+
460
+ **问题**:
461
+ - Python 支持 `AUN_DIRECT_EVENT_MESSAGE` / `AUN_MESSAGE_ACK_EVENT_FIRE_AND_FORGET` 环境变量
462
+ - Go 版本所有推送都是同步模式(等待 Gateway 响应)
463
+ - 高吞吐场景下,同步等待会成为瓶颈
464
+
465
+ **影响**:
466
+ - 推送延迟影响 send 吞吐量
467
+ - 无法通过配置优化性能
468
+
469
+ **修复**:
470
+ ```go
471
+ // 新增配置检查
472
+ func (s gatewayMessageEventSink) shouldFireAndForget(eventName string) bool {
473
+ if eventName == "message.received" || eventName == "peer.v2.message_received" || eventName == "message.recalled" {
474
+ return envOrConfigBool("AUN_DIRECT_EVENT_MESSAGE", false)
475
+ }
476
+ if eventName == "message.ack" {
477
+ return envOrConfigBool("AUN_MESSAGE_ACK_EVENT_FIRE_AND_FORGET", false)
478
+ }
479
+ return false
480
+ }
481
+
482
+ // 在推送时应用
483
+ if s.shouldFireAndForget(event) {
484
+ go func() {
485
+ _, _ = s.client.DispatchEvent(ctx, req) // 后台发送,不等待
486
+ }()
487
+ return nil
488
+ } else {
489
+ result, err := s.client.DispatchEvent(ctx, req)
490
+ // 处理响应
491
+ }
492
+ ```
493
+
494
+ ---
495
+
496
+ #### P2-3: Python 无性能追踪,Go 有
497
+
498
+ **位置**: Python `_emit_client_event` / Go `LogPerf`
499
+
500
+ **问题**:
501
+ - Python 版本没有推送性能追踪(仅传递 `_trace`)
502
+ - Go 版本有详细的性能日志(`LogPerf`)
503
+
504
+ **影响**:
505
+ - Python 环境下难以诊断推送性能问题
506
+
507
+ **修复**:
508
+ 在 Python `_emit_client_event` 中添加性能日志:
509
+ ```python
510
+ async def _emit_client_event(event_name: str, data: dict, *, event_id: str | None = None):
511
+ start = time.perf_counter()
512
+ try:
513
+ # ... 推送逻辑
514
+ result = await _gateway_business_call("gateway.dispatch_event", params)
515
+ elapsed_ms = (time.perf_counter() - start) * 1000
516
+ if elapsed_ms > 100: # 超过 100ms 记录
517
+ print(f"[message] PERF: push_slow event={event_name} elapsed={elapsed_ms:.1f}ms")
518
+ return result
519
+ except Exception as e:
520
+ elapsed_ms = (time.perf_counter() - start) * 1000
521
+ print(f"[message] PERF: push_failed event={event_name} elapsed={elapsed_ms:.1f}ms error={e}")
522
+ raise
523
+ ```
524
+
525
+ ---
526
+
527
+ #### P2-4: Go 按 slot 批量推送更高效
528
+
529
+ **位置**: Go `groupGatewaySessionConnectionsBySlot` / Python 逐设备推送
530
+
531
+ **问题**:
532
+ - Python 逐个 device_id 调用 `_emit_client_event`
533
+ - Go 查询 sessions 后按 slot 分组,同一 slot 的多个 connection 一次推送
534
+
535
+ **影响**:
536
+ - Python 版本在多设备场景下推送次数更多(性能劣势)
537
+
538
+ **修复**:
539
+ Python 采用 Go 的批量推送策略(需重构 `_emit_client_event` 调用逻辑)
540
+
541
+ ---
542
+
543
+ ## 6. 总结
544
+
545
+ ### 整体对齐度
546
+
547
+ | 模块 | 对齐度 | 主要差异 |
548
+ |------|--------|----------|
549
+ | 在线状态管理 | ✅ 100% | 数据结构和逻辑完全一致 |
550
+ | 推送路径选择 | ⚠️ 90% | Go 缺失 federation_forward |
551
+ | 推送逻辑 | ⚠️ 85% | Go 更健壮,但缺少配置项 |
552
+ | 推送时机 | ✅ 95% | 基本一致,仅错误处理略有差异 |
553
+
554
+ ### 关键发现
555
+
556
+ 1. **P1 严重缺口**: Go 缺失 `federation_forward` 选项,跨域推送失效
557
+ 2. **Go 更健壮**: 多 slot 重试、性能追踪、按 slot 批量推送
558
+ 3. **Python 更灵活**: 支持 fire-and-forget 配置
559
+ 4. **一致性良好**: 在线状态管理、推送时机、事件格式高度一致
560
+
561
+ ### 修复优先级
562
+
563
+ 1. **立即修复** (P1-1): 添加 `federation_forward` 选项
564
+ 2. **近期修复** (P2-1): 确认推送失败不影响 send 返回
565
+ 3. **中期优化** (P2-2/3/4): 添加配置支持、性能追踪对齐、批量推送优化
566
+
567
+ ### 验证计划
568
+
569
+ 1. 单元测试: 验证 `federation_forward` 选项传递
570
+ 2. 集成测试: 跨域推送端到端验证
571
+ 3. 性能测试: 对比 Python vs Go 推送吞吐量
572
+ 4. 容错测试: Gateway 不可用时 send 行为一致性
@@ -16,7 +16,7 @@
16
16
  |---------|------|------|---------|---------|
17
17
  | `auth.*` | 核心 | AID 注册、认证、令牌管理 | auth 服务 | WebSocket |
18
18
  | `ca.*` | 核心 | 证书签发、续期、吊销 | ca 服务 | WebSocket / HTTP |
19
- | `message.*` | 核心 | 点对点消息路由、E2EE V2 设备密钥/bootstrap | message 服务 | WebSocket |
19
+ | `message.*` | 核心 | 点对点消息路由、E2EE prekey 管理 | message 服务 | WebSocket |
20
20
  | `meta.*` | 核心 | 健康检查、状态查询、受信根证书 | meta 服务 | WebSocket |
21
21
  | `storage.*` | 扩展 | 对象存储、大文件上传下载(详见 [11-Storage-子协议.md](11-Storage-子协议.md)) | storage 服务 | WebSocket + HTTP |
22
22
  | `group.*` | 扩展 | 群组管理、群消息、资源共享(详见 [10-Group-子协议.md](10-Group-子协议.md)) | group 服务 | WebSocket |
@@ -290,7 +290,7 @@ proxy-server 处理 `https://proxy.{issuer}/{user_name}/{svc_name}/...` 或等
290
290
  |------|:----:|------|
291
291
  | `to` | ✅ | 接收者 AID |
292
292
  | `payload` | ✅ | 消息载荷,对协议层透明,由应用层定义 |
293
- | `type` | ❌ | 信封/封装类型,普通业务消息无需填写;SDK V2 加密发送时自动使用 `e2ee.p2p_encrypted` |
293
+ | `type` | ❌ | 信封/封装类型,普通业务消息无需填写;SDK 加密发送时自动使用 `e2ee.encrypted` |
294
294
  | `encrypted` | ❌ | E2EE 标记(默认 `false`) |
295
295
  | `message_id` | ❌ | 幂等键(客户端提供或服务端生成 UUID) |
296
296
  | `timestamp` | ❌ | 客户端时间戳(毫秒),服务端忽略此字段,始终使用服务端时间 |
@@ -382,9 +382,10 @@ proxy-server 处理 `https://proxy.{issuer}/{user_name}/{svc_name}/...` 或等
382
382
  | 参数 | 必需 | 说明 |
383
383
  |------|:----:|------|
384
384
  | `after_seq` | ❌ | 起始序列号(默认 0),返回 `seq > after_seq` 的消息 |
385
- | `limit` | ❌ | 单次返回上限(默认 50,最大 50;`pull_max_limit` 配置只能进一步收紧) |
385
+ | `limit` | ❌ | 单次返回上限(默认 50,最大 50 |
386
386
  | `device_id` | ❌ | 多实例消费上下文中的设备标识;缺省时使用连接认证上下文 |
387
387
  | `slot_id` | ❌ | 同一设备下的消费槽位;空字符串表示设备单实例模式 |
388
+ | `window_mode` | ❌ | 可选 Pull 模式;`tail` 表示读取以安全 Head 结尾的最新一页 |
388
389
 
389
390
  **响应**:
390
391
  ```json
@@ -419,11 +420,48 @@ proxy-server 处理 `https://proxy.{issuer}/{user_name}/{svc_name}/...` 或等
419
420
  | `ephemeral_earliest_available_seq` | integer\|null | 当前临时缓冲中最早可用的 `seq`,`null` 表示无临时消息 |
420
421
  | `ephemeral_dropped_count` | integer | 因缓冲淘汰而丢弃的临时消息计数 |
421
422
 
422
- > 客户端可通过 `ephemeral_earliest_available_seq` 判断是否有临时消息丢失:若 `after_seq < ephemeral_earliest_available_seq`,说明存在已被淘汰的消息。
423
-
424
- **说明**:不修改消息状态,多设备安全(各 `(aid, device_id, slot_id)` 维护各自的 `after_seq`),同一 `after_seq` 多次调用返回相同结果(幂等)。显式传入的 `device_id` / `slot_id` 若与连接认证上下文不一致,服务端 **MUST** 拒绝请求。
425
-
426
- #### `message.ack`
423
+ > 客户端可通过 `ephemeral_earliest_available_seq` 判断是否有临时消息丢失:若 `after_seq < ephemeral_earliest_available_seq`,说明存在已被淘汰的消息。
424
+
425
+ **说明**:不修改消息状态,多设备安全(各 `(aid, device_id, slot_id)` 维护各自的 `after_seq`),同一 `after_seq` 多次调用返回相同结果(幂等)。显式传入的 `device_id` / `slot_id` 若与连接认证上下文不一致,服务端 **MUST** 拒绝请求。
426
+
427
+ **Tail 模式**:`window_mode="tail"` 时,服务端固定安全 `head_seq`,返回跨度不超过 `limit` 的最新 seq band,并额外返回:
428
+
429
+ | 字段 | 说明 |
430
+ |------|------|
431
+ | `window_mode` | 固定为 `tail` |
432
+ | `window_start_seq` | 已扫描窗口的包含式左边界 |
433
+ | `head_seq` | 本次请求固定的安全 Head |
434
+ | `covered_through_seq` | 覆盖终点,正常等于 `head_seq` |
435
+
436
+ Tail 不推进 ACK、retention/visibility floor、Pull activity 或 catch-up 状态,不接受 `ack_up_to_seq` 或 `force=true`。seq band 内未返回的序号视为服务端永久空号,客户端不得为页内缺号创建 Gap。无 `window_mode` 的旧请求必须保持原请求、响应和副作用语义,以兼容旧 SDK。
437
+
438
+ #### `message.history`
439
+
440
+ 按 `before_seq` 排他上界向前只读翻页,不修改 ACK、cursor、floor 或 Pull activity。
441
+
442
+ **参数**:
443
+
444
+ | 参数 | 必需 | 说明 |
445
+ |------|:----:|------|
446
+ | `before_seq` | ✅ | 正整数排他上界,只返回 `seq < before_seq` 的消息 |
447
+ | `limit` | ❌ | 单页上限(默认 50,最大 50) |
448
+
449
+ **响应字段**:
450
+
451
+ | 字段 | 说明 |
452
+ |------|------|
453
+ | `messages` | 页内按 seq 升序 |
454
+ | `window_start_seq` | 本页最小边界;空页为 `null` |
455
+ | `next_before_seq` | 下一页直接使用的排他上界;无更早消息为 `null` |
456
+ | `has_older` | 是否仍有更早的可见消息 |
457
+ | `retention_floor_seq` | 已实际删除、不可恢复的 seq 下界 |
458
+ | `earliest_available_seq` | 当前最早可拉取边界 |
459
+
460
+ History 不触发实时消息事件。客户端解密 History 结果时不得修改实时 A/T/H 或发送 ACK。
461
+
462
+ **客户端发布语义**:单个通过校验的 Tail/Forward 响应页内必须按 seq 升序处理,并折叠页内重复 seq。A/T/H 只描述扫描进度,不是应用发布账本;Tail 与 Forward 窗口可重叠,因此跨 Push/Tail/Forward 页不承诺唯一或有序。全局去重与排序属于应用层业务仓库职责,不是 SDK 交付契约;应用层必须在 namespace 内按 `message_id` 幂等,并按 `seq` 排序。SDK 进程内折叠只能作为 best-effort 优化。History 不进入实时发布流。
463
+
464
+ #### `message.ack`
427
465
 
428
466
  确认已收到消息,推进服务端 ack 游标。
429
467
 
@@ -1288,25 +1326,25 @@ event/task.delegated # 已创建子任务
1288
1326
 
1289
1327
  ### 6.7 端到端加密(E2EE)摘要
1290
1328
 
1291
- > 当前 V2 主路径见 [E2EE_V2消息通信时序图](../sdk/E2EE_V2消息通信时序图.md)、[05-E2EE加密通信](../sdk/05-E2EE加密通信.md) 和 [08-AUN-E2EE-Group.md](08-AUN-E2EE-Group.md)。[08-AUN-E2EE.md](08-AUN-E2EE.md) 仅保留 legacy P2P 信封说明。本节仅保留摘要。
1329
+ > 规范性定义见 [08-AUN-E2EE.md](08-AUN-E2EE.md)。本节仅保留摘要。
1292
1330
 
1293
1331
  **核心原则**:
1294
1332
 
1295
1333
  - E2EE 加解密**完全由客户端实现**,无需在线协商
1296
- - 服务端只做认证、路由、设备密钥/群状态查询、密文存储和事件通知,对明文内容完全透明
1297
- - P2P 加密消息通过 `message.send` 承载 V2 `e2ee.p2p_encrypted` envelope;群加密消息通过 `group.v2.send` 承载 V2 `e2ee.group_encrypted` envelope
1298
- - 当前主路径为 V2 多设备 wrap:每条消息一把 `master_key`,按 recipient 设备分别生成 `3DH` / `1DH` wrap,并由接收端验证 `sender_signature`、AAD 和 recipient proof / digest
1299
-
1300
- **服务端兼容性要求**:
1301
-
1302
- - 接受并转发 V2 E2EE envelope;兼容旧 `encrypted: true` 标志
1303
- - 在离线队列和历史消息中存储密文 payload
1304
- - 不对密文 payload 施加额外的语义解析要求
1305
- - 提供 `message.v2.put_peer_pk`、`message.v2.bootstrap`、`group.v2.put_group_pk`、`group.v2.bootstrap` 等 V2 设备密钥和 bootstrap RPC
1334
+ - 服务端只做消息中转和 prekey 存储,对加密内容完全透明
1335
+ - 加密消息通过 `message.send` `encrypted: true` 标志标识
1336
+ - 两级降级:prekey_ecdh_v2(优先,四路 ECDH)→ long_term_key(降级)
1337
+
1338
+ **服务端兼容性要求**:
1339
+
1340
+ - 接受并转发 `encrypted: true` 的消息
1341
+ - 在离线队列和历史消息中存储密文 payload
1342
+ - 不对密文 payload 施加额外的语义解析要求
1343
+ - 提供 `message.e2ee.put_prekey` / `message.e2ee.get_prekey` RPC
1306
1344
 
1307
1345
  **互操作要求**:
1308
1346
 
1309
- 跨语言、跨平台 SDK 的当前互操作以 E2EE V2 多设备 wrap 为准:P2P 参见 SDK V2 时序和各语言 `v2/e2ee` 实现,群组参见 [08-AUN-E2EE-Group.md](08-AUN-E2EE-Group.md)。旧 `e2ee.encrypted` 信封互操作才查 [08-AUN-E2EE.md](08-AUN-E2EE.md)。
1347
+ 跨语言、跨平台 SDK 的互操作必须以 [08-AUN-E2EE.md](08-AUN-E2EE.md) 中定义的算法套件、密文 payload 格式、AAD 字段、重放保护规则为准。
1310
1348
 
1311
1349
  **错误码**:
1312
1350