@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.
- package/CHANGELOG.md +177 -67
- 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
- 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
- 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
- package/_packed_docs/CHANGELOG.md +177 -67
- package/_packed_docs/INDEX.md +124 -65
- package/_packed_docs/KITE_DOCS_GUIDE.md +65 -30
- package/_packed_docs/agent.md/SCHEMA.md +83 -58
- package/_packed_docs/agent.md/examples/codeagent-claudecode.md +1 -1
- package/_packed_docs/agent.md/examples/openclaw-lobster.md +1 -1
- package/_packed_docs/agent.md/examples/signed-openclaw-lobster.md +1 -1
- 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
- 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
- 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
- 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
- package/_packed_docs/cli/CLI/346/211/213/345/206/214.md +6 -4
- package/_packed_docs/group-message-rpc-alignment-gaps.md +741 -0
- package/_packed_docs/message-online-push-alignment.md +572 -0
- package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +58 -20
- package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +219 -247
- package/_packed_docs/protocol/12-Stream-/345/255/220/345/215/217/350/256/256.md +14 -14
- package/_packed_docs/protocol/13-Agent/350/241/214/344/270/272/350/247/204/350/214/203.md +3 -3
- 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
- package/_packed_docs/protocol/README.md +1 -0
- package/_packed_docs/protocol/aun-docs-guide.md +7 -4
- package/_packed_docs/protocol/index.md +25 -19
- package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +63 -18
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +22 -0
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +93 -84
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +6 -2
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +73 -34
- package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +3 -3
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +21 -9
- package/_packed_docs/sdk/09-group-rpc-manual.md +319 -285
- package/_packed_docs/sdk/09-message-rpc-manual.md +144 -103
- package/_packed_docs/sdk/09-payload-reference.md +1 -1
- package/_packed_docs/sdk/09-storage-rpc-manual.md +14 -3
- package/_packed_docs/sdk/09-stream-rpc-manual.md +8 -8
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +34 -23
- package/_packed_docs/sdk/CHANGELOG-0.5.6.md +73 -0
- package/_packed_docs/sdk/INDEX.md +45 -37
- package/_packed_docs/sdk/README.md +11 -9
- package/_packed_docs//345/217/221/345/270/203/346/212/245/345/221/212-0.5.6.md +260 -0
- 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
- 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
- package/dist/agent-md-schema.d.ts +14 -0
- package/dist/agent-md-schema.d.ts.map +1 -0
- package/dist/agent-md-schema.js +103 -0
- package/dist/agent-md-schema.js.map +1 -0
- package/dist/agent-md.d.ts +11 -5
- package/dist/agent-md.d.ts.map +1 -1
- package/dist/agent-md.js +295 -53
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +7 -7
- package/dist/aid-store.d.ts.map +1 -1
- package/dist/aid-store.js +46 -18
- package/dist/aid-store.js.map +1 -1
- package/dist/aid.d.ts +2 -0
- package/dist/aid.d.ts.map +1 -1
- package/dist/aid.js +29 -7
- package/dist/aid.js.map +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +31 -11
- package/dist/auth.js.map +1 -1
- package/dist/bundle.js +29920 -19719
- package/dist/cert-utils.d.ts +1 -0
- package/dist/cert-utils.d.ts.map +1 -1
- package/dist/cert-utils.js +36 -16
- package/dist/cert-utils.js.map +1 -1
- package/dist/client/delivery.d.ts +106 -11
- package/dist/client/delivery.d.ts.map +1 -1
- package/dist/client/delivery.js +1925 -374
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/group-state.d.ts.map +1 -1
- package/dist/client/group-state.js +27 -24
- package/dist/client/group-state.js.map +1 -1
- package/dist/client/lifecycle.d.ts +15 -1
- package/dist/client/lifecycle.d.ts.map +1 -1
- package/dist/client/lifecycle.js +477 -132
- package/dist/client/lifecycle.js.map +1 -1
- package/dist/client/mention-mode.d.ts +7 -0
- package/dist/client/mention-mode.d.ts.map +1 -0
- package/dist/client/mention-mode.js +184 -0
- package/dist/client/mention-mode.js.map +1 -0
- package/dist/client/peers.d.ts +1 -1
- package/dist/client/peers.d.ts.map +1 -1
- package/dist/client/peers.js +26 -3
- package/dist/client/peers.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +34 -2
- package/dist/client/rpc-pipeline.d.ts.map +1 -1
- package/dist/client/rpc-pipeline.js +503 -99
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/runtime.d.ts +1 -3
- package/dist/client/runtime.d.ts.map +1 -1
- package/dist/client/runtime.js +4 -7
- package/dist/client/runtime.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +43 -2
- package/dist/client/v2-e2ee.d.ts.map +1 -1
- package/dist/client/v2-e2ee.js +931 -138
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +46 -15
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +785 -304
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +4 -1
- package/dist/errors.js.map +1 -1
- package/dist/facades.d.ts +26 -1
- package/dist/facades.d.ts.map +1 -1
- package/dist/facades.js +142 -60
- package/dist/facades.js.map +1 -1
- package/dist/group-id.d.ts.map +1 -1
- package/dist/group-id.js +2 -17
- package/dist/group-id.js.map +1 -1
- package/dist/group-index.d.ts +6 -1
- package/dist/group-index.d.ts.map +1 -1
- package/dist/group-index.js +44 -25
- package/dist/group-index.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/keystore/index.d.ts +10 -5
- package/dist/keystore/index.d.ts.map +1 -1
- package/dist/keystore/indexeddb-identity-store.d.ts +0 -12
- package/dist/keystore/indexeddb-identity-store.d.ts.map +1 -1
- package/dist/keystore/indexeddb-identity-store.js +0 -60
- package/dist/keystore/indexeddb-identity-store.js.map +1 -1
- package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
- package/dist/keystore/indexeddb-shared.js +9 -5
- package/dist/keystore/indexeddb-shared.js.map +1 -1
- package/dist/keystore/indexeddb-token-store.d.ts +3 -1
- package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
- package/dist/keystore/indexeddb-token-store.js +47 -2
- package/dist/keystore/indexeddb-token-store.js.map +1 -1
- package/dist/register-flow.d.ts.map +1 -1
- package/dist/register-flow.js +28 -3
- package/dist/register-flow.js.map +1 -1
- package/dist/seq-tracker.d.ts +28 -8
- package/dist/seq-tracker.d.ts.map +1 -1
- package/dist/seq-tracker.js +224 -61
- package/dist/seq-tracker.js.map +1 -1
- package/dist/storage/vfs.d.ts +1 -0
- package/dist/storage/vfs.d.ts.map +1 -1
- package/dist/storage/vfs.js +26 -2
- package/dist/storage/vfs.js.map +1 -1
- package/dist/tools/cross-sdk-agent.js +399 -11
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +12 -0
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +226 -102
- package/dist/transport.js.map +1 -1
- package/dist/v2/session/session.d.ts.map +1 -1
- package/dist/v2/session/session.js +3 -4
- package/dist/v2/session/session.js.map +1 -1
- package/dist/v2/state/commitment.d.ts.map +1 -1
- package/dist/v2/state/commitment.js +1 -2
- package/dist/v2/state/commitment.js.map +1 -1
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +8 -14
- package/dist/validators.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +11 -9
- package/dist/group-resources.d.ts +0 -98
- package/dist/group-resources.d.ts.map +0 -1
- package/dist/group-resources.js +0 -635
- package/dist/group-resources.js.map +0 -1
- package/dist/keystore/indexeddb.d.ts +0 -179
- package/dist/keystore/indexeddb.d.ts.map +0 -1
- package/dist/keystore/indexeddb.js +0 -2031
- package/dist/keystore/indexeddb.js.map +0 -1
- package/dist/namespaces/auth.d.ts +0 -98
- package/dist/namespaces/auth.d.ts.map +0 -1
- package/dist/namespaces/auth.js +0 -992
- package/dist/namespaces/auth.js.map +0 -1
- package/dist/namespaces/custody.d.ts +0 -51
- package/dist/namespaces/custody.d.ts.map +0 -1
- package/dist/namespaces/custody.js +0 -302
- package/dist/namespaces/custody.js.map +0 -1
- package/dist/namespaces/meta.d.ts +0 -109
- package/dist/namespaces/meta.d.ts.map +0 -1
- package/dist/namespaces/meta.js +0 -549
- 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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
>
|
|
1329
|
+
> 规范性定义见 [08-AUN-E2EE.md](08-AUN-E2EE.md)。本节仅保留摘要。
|
|
1292
1330
|
|
|
1293
1331
|
**核心原则**:
|
|
1294
1332
|
|
|
1295
1333
|
- E2EE 加解密**完全由客户端实现**,无需在线协商
|
|
1296
|
-
-
|
|
1297
|
-
-
|
|
1298
|
-
-
|
|
1299
|
-
|
|
1300
|
-
**服务端兼容性要求**:
|
|
1301
|
-
|
|
1302
|
-
- 接受并转发
|
|
1303
|
-
- 在离线队列和历史消息中存储密文 payload
|
|
1304
|
-
- 不对密文 payload 施加额外的语义解析要求
|
|
1305
|
-
- 提供 `message.
|
|
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
|
|
1347
|
+
跨语言、跨平台 SDK 的互操作必须以 [08-AUN-E2EE.md](08-AUN-E2EE.md) 中定义的算法套件、密文 payload 格式、AAD 字段、重放保护规则为准。
|
|
1310
1348
|
|
|
1311
1349
|
**错误码**:
|
|
1312
1350
|
|