@agentunion/fastaun-browser 0.5.6 → 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 (102) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/_packed_docs/CHANGELOG.md +74 -0
  3. package/_packed_docs/INDEX.md +12 -11
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +4 -3
  5. package/_packed_docs/agent.md/SCHEMA.md +83 -58
  6. package/_packed_docs/agent.md/examples/codeagent-claudecode.md +1 -1
  7. package/_packed_docs/agent.md/examples/openclaw-lobster.md +1 -1
  8. package/_packed_docs/agent.md/examples/signed-openclaw-lobster.md +1 -1
  9. 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 +3 -3
  10. 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 +7 -0
  11. 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 +422 -331
  12. package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +598 -398
  13. package/_packed_docs/group-message-rpc-alignment-gaps.md +741 -0
  14. package/_packed_docs/message-online-push-alignment.md +572 -0
  15. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1 -1
  16. package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +10 -10
  17. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +2 -2
  18. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +30 -10
  19. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +23 -20
  20. package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +3 -3
  21. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +5 -6
  22. package/_packed_docs/sdk/09-group-rpc-manual.md +11 -7
  23. package/_packed_docs/sdk/09-message-rpc-manual.md +5 -3
  24. package/_packed_docs/sdk/09-storage-rpc-manual.md +14 -3
  25. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +19 -17
  26. package/_packed_docs/sdk/CHANGELOG-0.5.6.md +73 -0
  27. package/_packed_docs/sdk/INDEX.md +22 -22
  28. package/_packed_docs/sdk/README.md +6 -5
  29. package/_packed_docs//345/217/221/345/270/203/346/212/245/345/221/212-0.5.6.md +260 -0
  30. package/dist/agent-md-schema.d.ts +14 -0
  31. package/dist/agent-md-schema.d.ts.map +1 -0
  32. package/dist/agent-md-schema.js +103 -0
  33. package/dist/agent-md-schema.js.map +1 -0
  34. package/dist/agent-md.d.ts +7 -0
  35. package/dist/agent-md.d.ts.map +1 -1
  36. package/dist/agent-md.js +229 -10
  37. package/dist/agent-md.js.map +1 -1
  38. package/dist/aid-store.d.ts +5 -1
  39. package/dist/aid-store.d.ts.map +1 -1
  40. package/dist/aid-store.js +26 -4
  41. package/dist/aid-store.js.map +1 -1
  42. package/dist/aid.d.ts +2 -0
  43. package/dist/aid.d.ts.map +1 -1
  44. package/dist/aid.js +29 -7
  45. package/dist/aid.js.map +1 -1
  46. package/dist/auth.d.ts.map +1 -1
  47. package/dist/auth.js +1 -0
  48. package/dist/auth.js.map +1 -1
  49. package/dist/bundle.js +29895 -21968
  50. package/dist/cert-utils.d.ts +1 -0
  51. package/dist/cert-utils.d.ts.map +1 -1
  52. package/dist/cert-utils.js +36 -16
  53. package/dist/cert-utils.js.map +1 -1
  54. package/dist/client/delivery.d.ts +33 -5
  55. package/dist/client/delivery.d.ts.map +1 -1
  56. package/dist/client/delivery.js +597 -220
  57. package/dist/client/delivery.js.map +1 -1
  58. package/dist/client/lifecycle.d.ts +15 -1
  59. package/dist/client/lifecycle.d.ts.map +1 -1
  60. package/dist/client/lifecycle.js +452 -151
  61. package/dist/client/lifecycle.js.map +1 -1
  62. package/dist/client/rpc-pipeline.d.ts +17 -6
  63. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  64. package/dist/client/rpc-pipeline.js +284 -115
  65. package/dist/client/rpc-pipeline.js.map +1 -1
  66. package/dist/client/runtime.d.ts +1 -3
  67. package/dist/client/runtime.d.ts.map +1 -1
  68. package/dist/client/runtime.js +4 -7
  69. package/dist/client/runtime.js.map +1 -1
  70. package/dist/client/v2-e2ee.d.ts +20 -2
  71. package/dist/client/v2-e2ee.d.ts.map +1 -1
  72. package/dist/client/v2-e2ee.js +398 -114
  73. package/dist/client/v2-e2ee.js.map +1 -1
  74. package/dist/client.d.ts +22 -10
  75. package/dist/client.d.ts.map +1 -1
  76. package/dist/client.js +396 -104
  77. package/dist/client.js.map +1 -1
  78. package/dist/facades.d.ts +23 -1
  79. package/dist/facades.d.ts.map +1 -1
  80. package/dist/facades.js +51 -7
  81. package/dist/facades.js.map +1 -1
  82. package/dist/group-id.d.ts.map +1 -1
  83. package/dist/group-id.js +2 -17
  84. package/dist/group-id.js.map +1 -1
  85. package/dist/index.d.ts +4 -3
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +2 -1
  88. package/dist/index.js.map +1 -1
  89. package/dist/seq-tracker.d.ts.map +1 -1
  90. package/dist/seq-tracker.js +8 -2
  91. package/dist/seq-tracker.js.map +1 -1
  92. package/dist/tools/cross-sdk-agent.js +23 -1
  93. package/dist/tools/cross-sdk-agent.js.map +1 -1
  94. package/dist/transport.d.ts.map +1 -1
  95. package/dist/transport.js +8 -1
  96. package/dist/transport.js.map +1 -1
  97. package/dist/validators.d.ts.map +1 -1
  98. package/dist/validators.js +8 -14
  99. package/dist/validators.js.map +1 -1
  100. package/dist/version.d.ts +1 -1
  101. package/dist/version.js +1 -1
  102. package/package.json +6 -5
@@ -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 行为一致性
@@ -459,7 +459,7 @@ Tail 不推进 ACK、retention/visibility floor、Pull activity 或 catch-up 状
459
459
 
460
460
  History 不触发实时消息事件。客户端解密 History 结果时不得修改实时 A/T/H 或发送 ACK。
461
461
 
462
- **客户端发布语义**:单个通过校验的 Tail/Forward 响应页内必须按 seq 升序处理,并折叠页内重复 seq。A/T/H 只描述扫描进度,不是应用发布账本;Tail 与 Forward 窗口可重叠,排队超时旁路也可能使多个响应并发完成,因此跨 Push/Tail/Forward 页不承诺唯一或有序。全局去重与排序属于应用层业务仓库职责,不是 SDK 交付契约;应用层必须在 namespace 内按 `message_id` 幂等,并按 `seq` 排序。SDK 进程内折叠只能作为 best-effort 优化。History 不进入实时发布流。
462
+ **客户端发布语义**:单个通过校验的 Tail/Forward 响应页内必须按 seq 升序处理,并折叠页内重复 seq。A/T/H 只描述扫描进度,不是应用发布账本;Tail 与 Forward 窗口可重叠,因此跨 Push/Tail/Forward 页不承诺唯一或有序。全局去重与排序属于应用层业务仓库职责,不是 SDK 交付契约;应用层必须在 namespace 内按 `message_id` 幂等,并按 `seq` 排序。SDK 进程内折叠只能作为 best-effort 优化。History 不进入实时发布流。
463
463
 
464
464
  #### `message.ack`
465
465
 
@@ -122,9 +122,9 @@ sequenceDiagram
122
122
  "capabilities": {
123
123
  "e2ee": true,
124
124
  "group_e2ee": true,
125
- "supported_p2p_e2ee": ["e2ee_v2"],
126
- "supported_group_e2ee": ["group_e2ee_v2"],
127
- "inline_realtime_payload_v1": true
125
+ "supported_p2p_e2ee": ["e2ee_v2"],
126
+ "supported_group_e2ee": ["group_e2ee_v2"],
127
+ "inline_realtime_payload_v1": true
128
128
  }
129
129
  }
130
130
  ```
@@ -134,8 +134,8 @@ sequenceDiagram
134
134
  | `e2ee` | 支持 P2P E2EE V2 加密通信 |
135
135
  | `group_e2ee` | 支持群组 E2EE V2 加密通信 |
136
136
  | `supported_p2p_e2ee` | 明确支持的 P2P 加密版本列表 |
137
- | `supported_group_e2ee` | 明确支持的群组加密版本列表 |
138
- | `inline_realtime_payload_v1` | **支持实时推送内联消息载荷**(0.5.6+ 新增) |
137
+ | `supported_group_e2ee` | 明确支持的群组加密版本列表 |
138
+ | `inline_realtime_payload_v1` | **支持实时推送内联消息载荷**(0.5.6+ 新增) |
139
139
 
140
140
  ### inline_realtime_payload_v1 详解
141
141
 
@@ -152,11 +152,11 @@ sequenceDiagram
152
152
 
153
153
  ### 服务端能力公告
154
154
 
155
- `hello-ok.result.capabilities` 返回服务端支持的特性,客户端可根据此信息调整行为。服务端和客户端的 capabilities **不是交集协商**,而是各自独立声明。
156
-
157
- ---
158
-
159
- ## 消息格式
155
+ `hello-ok.result.capabilities` 返回服务端支持的特性,客户端可根据此信息调整行为。服务端和客户端的 capabilities **不是交集协商**,而是各自独立声明。
156
+
157
+ ---
158
+
159
+ ## 消息格式
160
160
 
161
161
  所有消息遵循 JSON-RPC 2.0 格式,通过以下规则区分类型:
162
162
 
@@ -73,9 +73,9 @@ Tail 和 Forward 的水位由通过校验的服务端原始页决定。单条解
73
73
 
74
74
  冷启动缺少发送方 IK 时,SDK 会将同发送端请求 single-flight 合并,最多同步等待 3 秒执行 bootstrap;成功后立即重试当前消息解密,失败或超时才转入 pending 后台重试。这个有界阻塞只影响当前加密消息的首次处理,不改变“永久坏密文不阻塞 A/T/H”的规则。
75
75
 
76
- SDK 有三个 Pull GateP2P Message 共用一个,所有 Group Message 共用一个,所有 Group Event 共用一个。每个 Gate 单飞;相同请求 key 折叠,不同 key 排队。Message Tail/History 进入前台 FIFO,Message Forward/Gap Fill 与 Group Event Pull 进入后台 FIFO;排队超过 3 秒时请求旁路对应 Gate 执行。Group Event 保持独立的 Forward Cursor,不进入 A/T/H 模型,也不与 Group Message 竞争同一 Gate。
76
+ 每个 `AUNClient` 只有一个客户端级 Pull GateP2P MessageGroup MessageGroup Event Pull 都进入该 Gate;Gate 始终 single-inflight,相同请求 key 折叠并共享同一结果,不同 key 排队,前台 FIFO 优先于后台 FIFO。Message Tail/History 属于前台,Message Forward/Gap Fill 与 Group Event Pull 属于后台。Group Event 仍使用独立的 Forward Cursor,但不使用独立 Gate;不同 `AUNClient` 实例之间不共享 Gate。应用层直接正常 `await client.call(...)` 即可,无需自行实现重复并发控制。
77
77
 
78
- A/T/H 是服务端消息空间的扫描水位,不是应用层发布账本。SDK 只保证单个通过校验的 Tail/Forward 响应页内按 seq 升序、同 seq 最多发布一次;History 只解密返回,不进入实时发布流。Tail 与 Forward 的窗口可能重叠,Pull Gate 超时旁路也允许多个响应并发完成,因此跨 Push/Tail/Forward 页不保证唯一或有序。全局去重与排序属于应用业务仓库职责,不是 SDK 交付契约;应用必须在 namespace 内按 `message_id` 幂等,并按 `seq` 排序存储或展示。SDK 的进程内去重只能作为 best-effort 重叠折叠,不能替代应用持久化幂等。
78
+ A/T/H 是服务端消息空间的扫描水位,不是应用层发布账本。SDK 只保证单个通过校验的 Tail/Forward 响应页内按 seq 升序、同 seq 最多发布一次;History 只解密返回,不进入实时发布流。Tail 与 Forward 的窗口可能重叠,因此跨 Push/Tail/Forward 页不保证唯一或有序。全局去重与排序属于应用业务仓库职责,不是 SDK 交付契约;应用必须在 namespace 内按 `message_id` 幂等,并按 `seq` 排序存储或展示。SDK 的进程内去重只能作为 best-effort 重叠折叠,不能替代应用持久化幂等。
79
79
 
80
80
  ---
81
81