@agentunion/fastaun 0.5.34 → 0.5.35

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 (78) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/_packed_docs/CHANGELOG.md +30 -1
  3. package/_packed_docs/INDEX.md +11 -11
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +9 -9
  5. package/_packed_docs/agent.md/SCHEMA.md +1 -1
  6. package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +2 -2
  7. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +17 -17
  8. package/_packed_docs/protocol/echo-probe-gateway.md +99 -0
  9. package/_packed_docs/protocol/echo-probe-sdk.md +91 -0
  10. package/_packed_docs/sdk/0.5.35-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +189 -0
  11. package/_packed_docs/sdk/0.5.35-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +204 -0
  12. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +2 -2
  13. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +32 -6
  14. package/_packed_docs/sdk/09-group-rpc-manual.md +7 -7
  15. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +13 -13
  16. package/_packed_docs/sdk/AUN/344/272/213/344/273/266/345/217/221/345/270/203/350/256/242/351/230/205/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +73 -3
  17. package/_packed_docs/sdk/AUN/344/272/213/344/273/266/345/217/221/345/270/203/350/256/242/351/230/205/345/274/200/345/217/221/347/247/273/346/244/215/346/214/207/345/215/227.md +50 -8
  18. package/_packed_docs/sdk/INDEX.md +18 -18
  19. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +1 -1
  20. package/_packed_docs/sdk/Ownership2.1-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +5 -5
  21. package/_packed_docs/sdk/Ownership2.1-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +5 -5
  22. package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +9 -9
  23. package/_packed_docs//346/266/210/346/201/257/345/274/271/346/200/247/350/241/245/346/213/211/351/200/232/351/201/223/345/256/236/346/226/275/350/256/241/345/210/222.md +400 -0
  24. package/_packed_docs//346/266/210/346/201/257/345/274/271/346/200/247/350/241/245/346/213/211/351/200/232/351/201/223/346/226/271/346/241/210.md +485 -0
  25. package/dist/auth.js +31 -43
  26. package/dist/auth.js.map +1 -1
  27. package/dist/client/delivery.d.ts +7 -1
  28. package/dist/client/delivery.js +137 -16
  29. package/dist/client/delivery.js.map +1 -1
  30. package/dist/client/lifecycle.js +10 -1
  31. package/dist/client/lifecycle.js.map +1 -1
  32. package/dist/client/rpc-pipeline.d.ts +2 -0
  33. package/dist/client/rpc-pipeline.js +65 -7
  34. package/dist/client/rpc-pipeline.js.map +1 -1
  35. package/dist/client/sync-channels.d.ts +2 -1
  36. package/dist/client/sync-channels.js +33 -14
  37. package/dist/client/sync-channels.js.map +1 -1
  38. package/dist/client/v2-e2ee.d.ts +1 -0
  39. package/dist/client/v2-e2ee.js +19 -5
  40. package/dist/client/v2-e2ee.js.map +1 -1
  41. package/dist/client.d.ts +15 -0
  42. package/dist/client.js +310 -49
  43. package/dist/client.js.map +1 -1
  44. package/dist/events.d.ts +1 -0
  45. package/dist/events.js +5 -0
  46. package/dist/events.js.map +1 -1
  47. package/dist/index.d.ts +2 -0
  48. package/dist/index.js +1 -0
  49. package/dist/index.js.map +1 -1
  50. package/dist/mail-client.d.ts +101 -0
  51. package/dist/mail-client.js +369 -0
  52. package/dist/mail-client.js.map +1 -0
  53. package/dist/pubsub/binding.js +9 -2
  54. package/dist/pubsub/binding.js.map +1 -1
  55. package/dist/pubsub/channel.d.ts +11 -3
  56. package/dist/pubsub/channel.js +88 -7
  57. package/dist/pubsub/channel.js.map +1 -1
  58. package/dist/pubsub/client.d.ts +8 -2
  59. package/dist/pubsub/client.js +59 -17
  60. package/dist/pubsub/client.js.map +1 -1
  61. package/dist/pubsub/index.d.ts +2 -2
  62. package/dist/pubsub/index.js +1 -1
  63. package/dist/pubsub/index.js.map +1 -1
  64. package/dist/pubsub/service.d.ts +6 -0
  65. package/dist/pubsub/service.js +100 -9
  66. package/dist/pubsub/service.js.map +1 -1
  67. package/dist/pubsub/types.d.ts +11 -1
  68. package/dist/tools/cross-sdk-agent.d.ts +1 -1
  69. package/dist/transport.d.ts +4 -0
  70. package/dist/transport.js +39 -2
  71. package/dist/transport.js.map +1 -1
  72. package/dist/udp-standby.d.ts +89 -0
  73. package/dist/udp-standby.js +487 -0
  74. package/dist/udp-standby.js.map +1 -0
  75. package/dist/version.d.ts +1 -1
  76. package/dist/version.js +1 -1
  77. package/package.json +5 -1
  78. package/scripts/patch-imapflow-external.mjs +69 -0
@@ -0,0 +1,485 @@
1
+ # 消息弹性补拉通道方案
2
+
3
+ > 状态:既有实现持续验收;本轮单辅助连接与 namespace 串行规则正在实施,历史通过记录不证明新规则通过;四 SDK 辅助断线、S14 超页恢复、真实 Chromium、S06/S08/S22、S07/S11/S16 夹具及 S23 隔离集成通过;64 项跨 SDK 矩阵已通过 37 项并续跑中,资源性能及完整场景尚未验收完成。日期:2026-11-08。适用:Gateway、Message/Group 服务及 Python、Go、TS/Node、JS/Browser 四 SDK。
4
+
5
+ ## 1. 目标与确定边界
6
+
7
+ 长期离线后,历史消息补拉应持续推进,同时给正常 RPC、实时通知和管理消息保留处理能力。
8
+
9
+ 采用一条主长连接和 0~N 条辅助补拉连接。辅助连接复用 `short` 生命周期,由一个 AUNClient 内部管理,业务 Facade 保持现有调用方式。
10
+
11
+ | 项目 | 确定规则 |
12
+ | --- | --- |
13
+ | 允许的辅助连接数 | Gateway 权威决定 0 或 1;承接主连接的同一 Gateway 内,`(aid, device_id)` 跨 slot、主会话合计最多 1 条,仅向双能力协商成功的 SDK 授权 |
14
+ | SDK 实际使用数 | 遵从当前策略,支持 0..N;当前四 SDK 协议测试覆盖到 2;无明显积压或本地压力大时可少于授权数 |
15
+ | 策略传递 | 所有可能触发 Pull 的事件和响应均由 Gateway 附带发送时的最新策略 |
16
+ | 恢复通知 | 暂停后没有业务流量时,Gateway 主动通知策略恢复,SDK 不轮询探测 |
17
+ | 主动回收 | Gateway 可通知后关闭辅助连接,优先回收已确认无补拉需求且无在途请求的空闲连接 |
18
+ | 状态所有者 | 一个主 client、一套游标、去重、解密状态、ACK 和应用分发 |
19
+ | 并发额度 | 主辅通道共享总 16、background 8、event 4、pull 4 |
20
+ | 顺序单位 | 同 namespace 的网络 Pull 串行,不同 namespace 可在授权与预算允许时并行复用辅助连接;各流统一提交 |
21
+ | Message 边界 | 负责既有分页、覆盖证明与 ACK,不管理 SDK 的辅助连接池 |
22
+ | 实施范围 | Gateway、Message/Group、四 SDK 及控制面板;历史证据见实施计划第 9.4 节,本轮单辅助规则待第 9.5 节验收 |
23
+
24
+ 本轮 Gateway 对同一 `(aid, device_id)` 的跨 slot、主会话合计最多授权 1 条;开关关闭时为 0,开启时上限为 1,仍可因压力降为 0 或回收。此为本轮服务端部署上限,SDK 能力按策略支持 0..N(当前四 SDK 协议测试到 2),不把 client 实现写死为 1。在承接主连接的同一 Gateway 内,不同设备分别计数;当前不跨 Gateway 全局统计连接总数,历史请求、字节与速率仍按 AID 汇总。SDK 不能自行提高当前策略授权,也不能通过新建 AUNClient、换 slot 或额外短连接扩大同步份额。
25
+
26
+ ## 2. 连接隔离解决的问题
27
+
28
+ RPC 可以有多个请求同时在途,但同一 WebSocket 下行中的大响应仍会排在后续响应之前。例如,768 KiB 页面经过 5 Mbps 下行,传输本身约需 1.26 秒。
29
+
30
+ | 瓶颈 | 专用连接的作用 | 配套措施 |
31
+ | --- | --- | --- |
32
+ | 单连接下行队列、TCP 重传 | 历史大响应与主通道分开 | 主通道保留小页和短响应 |
33
+ | 客户端总带宽、Gateway 出口 | 仍然共享 | 历史输出限速,为主通道留余量 |
34
+ | JSON 解析与解密 CPU | 仍然共享 | 有界批次、共享状态、及时让出执行时间 |
35
+ | 本地数据库与应用队列 | 仍然共享 | 字节预算、消费预留和有界排队 |
36
+ | 服务端查询与内存 | 仍然共享 | 按 AID 聚合额度与公平调度 |
37
+
38
+ 连接数量只是传输手段。服务端压力升高时,首先减少历史请求窗口和速率,再收缩连接;暂停后不能把原负载搬回主通道继续执行。
39
+
40
+ ## 3. 架构与职责
41
+
42
+ ```mermaid
43
+ flowchart LR
44
+ subgraph SDK["一个 AUNClient"]
45
+ direction TB
46
+ APP["应用 / Facade"]
47
+ COORD["同步协调器<br/>顺序 · 游标 · 解密 · ACK · 背压"]
48
+ MAIN["主通道:long × 1<br/>正常 RPC / 实时通知 / 紧急小页"]
49
+ AUX["辅助通道:short × 0~N<br/>按Gateway策略与SDK预算创建"]
50
+ APP <--> COORD
51
+ COORD <--> MAIN
52
+ COORD <--> AUX
53
+ end
54
+ GW["Gateway<br/>策略权威 · 会话绑定 · 聚合额度"]
55
+ MSG["Message / Group<br/>分页 · 覆盖证明 · 既有 ACK"]
56
+ MAIN <--> GW
57
+ AUX <--> GW
58
+ GW <--> MSG
59
+ style MAIN fill:#e8f2ff,stroke:#3975b8
60
+ style AUX fill:#eef7ee,stroke:#548a54
61
+ style COORD fill:#fff4dc,stroke:#b68a36
62
+ ```
63
+
64
+ | 组件 | 职责 |
65
+ | --- | --- |
66
+ | 主 client | 持有身份、token 来源、设备和槽位、解密状态、持久化组件及应用分发入口 |
67
+ | 同步协调器 | 观察策略,选择会话与通道,预留资源,统一校验、提交及 ACK |
68
+ | 辅助 Transport | 完成认证、发送被允许的同步 RPC、返回页面;不独立提交进度或触发应用回调 |
69
+ | Gateway | 决定策略,校验主辅绑定,限制方法和总份额,在事件/响应中注入最新策略 |
70
+ | Message / Group | 继续维护消息可见性、分页、保留期、覆盖证明与既有确认语义 |
71
+
72
+ 普通短连接与 `history_sync` 辅助连接均不注册在线接收路由,不触发或接收 unread hint;显式 Pull 正常工作。辅助通道不独立注册设备密钥,不启动另一套 SDK 补拉、token 刷新和后台维护;目录与状态查询留在主连接。
73
+
74
+ ## 4. 连接认证、绑定与成本
75
+
76
+ ### 4.1 复用 short 并声明用途
77
+
78
+ 新增通用握手能力 `elastic_connections_v1` 与补拉能力 `sync_channels_v1`。主连接均协商成功后取得首份策略,SDK 才能申请补拉辅助连接;辅助连接也需声明并协商两者。
79
+
80
+ | 建议字段 | 含义 |
81
+ | --- | --- |
82
+ | `options.kind = "short"` | 沿用短连接生命周期 |
83
+ | `options.purpose = "history_sync"` | 声明受限的同步用途 |
84
+ | `options.parent_connection_id` | 指向当前已认证主长连接 |
85
+
86
+ Gateway 验证同一 AID、device_id、slot_id 与有效主连接,原子检查本 Gateway 内同一 `(aid, device_id)` 跨 slot、主会话正在建立和已经建立的辅助连接总数,合计最多授权 1 条。SDK 遵从策略支持 0..N 条辅助连接;每条仍执行认证与双能力协商,知道父连接 ID 不能获得附属权限。
87
+
88
+ 辅助连接只允许已定义的同步方法。SDK 发起的历史 Pull,包括回退主通道的历史 Pull,均携带内部同步用途标识;Gateway 对协商后的会话统一检查,切换 socket 不改变配额。用户显式请求的实时窗口和历史浏览按各自类别受控,不能把后台任务伪装成前台任务获得额外份额。
89
+
90
+ 首版辅助连接跟随主连接所在 Gateway 实例,或经既有会话亲和路由到该实例。主连接迁移后重新绑定;不为本方案另建跨 Gateway 辅助连接注册中心。
91
+
92
+ ### 4.2 成本控制
93
+
94
+ 冷启动成本包括建连、TLS、WebSocket 升级及连接认证。复用主 client 的有效 access token 和认证材料,避免每次重新执行完整登录,但不跳过新连接的认证校验。
95
+
96
+ 一条辅助连接连续处理多页、多个会话。建连时读取主 client 最新的有效 access token,剩余有效期须超过 30 秒;不足时退主连接,不为辅助连接另行登录或刷新。Gateway 在认证时校验 token,已认证连接不会因原 token 自然到期立即断开。TLS 会话恢复可利用,方案不依赖其一定生效。
97
+
98
+ SDK 当前空闲关闭值为 25 秒,Gateway 兜底为 120 秒;四端空闲计时条件仍有差异,本轮未统一。当前 short TTL 只看客户端数据帧活动,不能用很短的 TTL 正常结束一个正在下载的页面。
99
+
100
+ 首版沿用已有认证体系。只有压测证明建连认证成为瓶颈,再评估短时附属票据。
101
+
102
+ ## 5. Gateway 动态策略
103
+
104
+ ### 5.1 一份权威快照
105
+
106
+ 策略位于 JSON-RPC 外层 `_meta.sync_policy`,属于 Gateway 到 SDK 的连接控制元数据。响应、通知统一位置,业务 `result`、`params` 和 E2EE 正文保持原格式。
107
+
108
+ ```json
109
+ {"_meta":{"sync_policy":{
110
+ "parent_connection_id":"主连接ID", "version":17,
111
+ "max_aux_channels":1, "max_history_pull_inflight":2,
112
+ "max_page_bytes":262144, "max_history_inflight_bytes":1048576,
113
+ "history_bytes_per_second":1048576
114
+ }}}
115
+ ```
116
+
117
+ 以上数值用于说明字段,不是已生效配置。
118
+
119
+ | 字段 | 作用域与语义 |
120
+ | --- | --- |
121
+ | `parent_connection_id` | 当前主会话范围;重连或迁移后旧范围失效 |
122
+ | `version` | 同一主会话内单调递增的安全整数,取值不超过 `2^53-1`;只在策略改变时增加 |
123
+ | `max_aux_channels` | 当前主会话的辅助授权数,取 0 或 1;同一 Gateway 内 `(aid, device_id)` 跨 slot、主会话合计最多 1 条 |
124
+ | `max_history_pull_inflight` | 主辅通道合计的历史 Pull 在途上限,首版 0~3,始终受全局 Pull=4 约束 |
125
+ | `max_page_bytes` | 单页响应编码后的字节上界,SDK 请求和服务端分页均需执行 |
126
+ | `max_history_inflight_bytes` | 同步响应的聚合在途字节额度,包含已预留而尚未返回的页面 |
127
+ | `history_bytes_per_second` | 历史同步的输出速率上限;0 表示暂停历史输出 |
128
+
129
+ 策略每次携带完整快照。同一版本只对应同一内容;SDK 收到重复版本不重复执行伸缩。缺少策略字段表示本次未更新,不表示解除限制。
130
+
131
+ `max_aux_channels=0` 只禁止辅助连接;如果历史 Pull 额度仍大于 0,可由主通道以小页、低优先级继续。`max_history_pull_inflight=0` 或历史速率为 0,表示暂停历史补拉,主通道也不能绕过。
132
+
133
+ 主通道正常 RPC、ACK、租约与必要的实时处理继续受各自额度约束,不因历史暂停而整体停摆。
134
+
135
+ ### 5.2 所有 Pull 触发入口都观察策略
136
+
137
+ Gateway 在对外发送前注入当时的最新策略,覆盖所有能直接或间接触发 Pull 的入口:
138
+
139
+ | 入口 | SDK 处理顺序 |
140
+ | --- | --- |
141
+ | 主连接认证成功响应 | 安装策略,再启动上线同步 |
142
+ | P2P、群及分类消息的新增/待拉取通知 | 观察策略,再更新目标水位和调度 |
143
+ | peer 目录、群目录及待同步状态响应 | 观察策略,再派生会话任务 |
144
+ | Forward、Tail、History、群状态补拉响应 | 观察策略,再处理页面和安排后续页 |
145
+ | ACK、发送结果或其它带待拉取提示的响应 | 观察策略,再处理提示 |
146
+ | 含重试或压力信息、可能导致再次 Pull 的错误响应 | 观察策略,再安排重试 |
147
+ | 主动策略变更通知 | 安装策略,恢复或收缩待执行任务 |
148
+
149
+ 此表按“是否触发 Pull”定义覆盖范围,不依赖少数固定方法名。今后新增此类入口,也必须经过同一观察器。
150
+
151
+ 响应应在唤醒等待它的 Promise/Future 之前观察策略,通知应在进入普通事件分发前观察策略;真正派发每个 Pull 前再检查最新策略,避免并发入口使用旧额度。
152
+
153
+ 只信任当前已认证 Gateway 连接的外层元数据。Gateway 覆盖或剔除业务服务、远端网关及 AID 消息中同名的非权威字段,再注入本地策略;不修改签名正文,不从消息内容提取同步授权。
154
+
155
+ ### 5.3 版本、乱序与实时执行
156
+
157
+ 不同 socket 上的事件和响应可能乱序。SDK 只接受匹配当前主连接且版本更新的策略;同版本快照幂等,旧版本忽略。
158
+
159
+ 旧版本策略不影响同一主会话内有效消息页的处理:可以忽略响应附带的旧策略,继续校验并提交合法页面。主会话代际已经改变时,旧页面才不得提交或发送 ACK。
160
+
161
+ Gateway 更新策略后立即执行新准入规则,不等待 SDK 确认。降低额度时,停止新请求;已准入请求只在有界在途预算和 deadline 内排空,必要时取消并由既有游标重试。不能因为允许一页排空,就给该通道继续派下一页。
162
+
163
+ SDK 缩容先停止向多余通道派任务,在页面边界关闭。扩容时先读取最新版本,连接建立过程中如授权已下降,Gateway 拒绝多余附属注册,SDK 不反复重连抢占。
164
+
165
+ ### 5.4 暂停与恢复闭环
166
+
167
+ 当历史额度为 0,SDK 不发送“试探恢复”的 Pull,也不运行定时策略查询。
168
+
169
+ Gateway 压力恢复后,递增策略版本,并向主连接发送新通知 `event/gateway.sync_policy_changed`,外层附完整 `_meta.sync_policy`。即使没有新消息和 RPC,也能唤醒已有积压。
170
+
171
+ 该通知走 Gateway 连接控制路径,不写入消息存储,不经过历史 Pull 队列,不等待应用消费,也不新增 AID 业务消息类别。发送失败则进入既有连接故障处理,重连时重新获得权威快照。
172
+
173
+ 服务端复用已有压力采集和状态变化机制,只对策略发生变化的会话推送,可合并短时抖动。SDK 仅为扩容冷却和普通空闲关闭安排按需计时;没有独立的策略续租或周期扫描任务。
174
+
175
+ ### 5.5 Gateway 主动回收辅助连接
176
+
177
+ Gateway 可在连接容量调整、压力升高或清理闲置资源时主动回收辅助短连接。选择顺序如下:
178
+
179
+ 1. 已确认无补拉需求、无在途请求的空闲连接,优先回收空闲时间最长者。
180
+ 2. 仍有积压但当前无在途请求的辅助连接,结合主体公平份额回收。
181
+ 3. 正在传输页面的连接,先停止派发新页,在有界期限内排空;压力需要时取消并关闭。
182
+
183
+ 没有在途请求不等于没有补拉需求:连接可能在等消费容量、密钥或服务端额度。Gateway 使用已上报需求和自身在途计数区分;未知需求不能归为第一类。
184
+
185
+ SDK 在同步请求中上报需求摘要;从有需求转为无需求、准备短时保留空闲连接时,发送一次 `gateway.sync_demand` JSON-RPC Notification。它携带主会话及 `has_pending` 状态,只辅助回收排序,不授予额度。Gateway 校验归属并以自身在途状态为准;没有周期性需求心跳。新任务到达后,SDK 在下一次请求或重新申请通道时更新需求。
186
+
187
+ 关闭前,Gateway 向主通道发送 `event/gateway.sync_channel_close` 控制通知,包含 `parent_connection_id`、`target_connection_id`、`reason`、`drain_timeout_ms`,外层附最新 `_meta.sync_policy`。向目标辅助连接也可发送同一通知,但主通道是可靠的控制接收入口,避免通知被辅助连接的大响应挡住。
188
+
189
+ | 原因 | Gateway 与 SDK 的处理 |
190
+ | --- | --- |
191
+ | `idle_reclaim` | 释放无需求的空闲资源;授权上限可不变,SDK无任务时不重建 |
192
+ | `capacity_reduced` | 先安装更严格策略,再通知并回收多余通道 |
193
+ | `pressure_reclaim` | 先收紧历史或通道额度,再回收;SDK等待有效授权恢复,不立即重连 |
194
+ | `parent_closed` | 主会话结束,附属通道及其任务代际一起失效 |
195
+
196
+ 正常回收在通知发出后以正常关闭结束辅助 WebSocket;不等待 SDK 业务 ACK。通知本身不调用主连接的整体断开处理,不关闭主 client,也不向应用报告为登录失败。主通道已不可用时,按既有故障与主会话失效流程关闭,不无限等待通知送达。
197
+
198
+ SDK 先观察策略,再按 `target_connection_id` 将该辅助通道标为退役,停止派新页并关闭其自动重连。已完整收到且主会话仍有效的页面可按既有接收契约提交;未完成页面释放预留、保留任务,从安全游标重试。排空期限到达即结束目标通道。
199
+
200
+ 重复通知、通知晚于关闭、关闭晚于新策略均应幂等。SDK 记录通知所属授权版本的辅助故障;同版本后续任务回主连接,只有新策略重新授权或主会话重新协商后,才可按需申请。策略更新与资源回收通知均不进入消息历史或应用事件流。
201
+
202
+ ## 6. 伸缩决策与公平性
203
+
204
+ ### 6.1 Gateway 决定允许多少
205
+
206
+ | 运行情况 | Gateway 可下发的决定 |
207
+ | --- | --- |
208
+ | 容量紧张,主通道资源需要保护 | 允许辅助 0,减少历史额度或暂停 |
209
+ | 有同步容量,积压会话可以持续追赶 | 允许辅助 1,给出受限请求与字节额度 |
210
+ | 服务端健康,跨流并行确有收益 | 本轮仍最多允许辅助 1;SDK 可按 0..N 策略使用多条,不同 namespace 可并行且执行聚合额度 |
211
+ | 个别 AID 占用过多 | 收缩该 AID 的同步份额,不奖励连接数多的客户端 |
212
+
213
+ Gateway 使用连接队列、查询耗时、出口流量和既有积压估计等信号。SDK 可随既有请求汇总本地待处理流数和消费情况,供 Gateway 参考;客户端自报数据不直接授予资源。判断策略不为每页执行全量消息 COUNT。
214
+
215
+ ### 6.2 SDK 在授权内按需使用
216
+
217
+ SDK 实际连接数取 Gateway 授权、本地资源许可、可并行任务需求的最小值。只有当前真实待拉的唯一流至少 4 条,或同流最新页面的 `pending_count_lower_bound>=100` 且仍需续页时,才使用辅助连接;详细口径见第 18 节。SDK 按策略支持 0..N 条辅助连接(当前四 SDK 协议测试覆盖到 2);同 namespace 网络 Pull 串行,不同 namespace 可在授权与预算允许时并行复用辅助连接。本轮 Gateway 对同一 AID+设备最多授权1条。
218
+
219
+ 本地内存紧张、解密慢或应用消费慢时,SDK 可立即少用或暂停,但不能自行解除 Gateway 的限制。扩容慢、缩容快;普通扩容可设置至少 5 秒冷却,收到更严格策略立即执行,不等待冷却结束。
220
+
221
+ Gateway 按 AID 聚合,再在设备及主会话间分配。SDK 的进程池每 AID 最多2条、总计最多8条,避免 EvolCore 同时加载大量 Agent 时每个都开满;服务端同一 AID+设备最多1条。单 client 内按 namespace 轮转,每页或固定字节量作为一轮,同 namespace 续页放回队尾。
222
+
223
+ 主通道有资源预留,历史队列在允许份额内公平推进;同一 AID 的额外连接不产生额外令牌桶。
224
+
225
+ ## 7. 顺序、实时窗口与游标
226
+
227
+ ### 7.1 同流提交,跨流并行
228
+
229
+ P2P 按 peer、群按对应 namespace 分流。同 namespace 的网络 Pull 串行,不同 namespace 可在授权与预算允许时并行复用辅助连接,仍受 Pull=4、历史 Pull 最多3及既有字节/速率预算约束;每个流由同一个协调器提交,不把同流拆成任意序号区间分发到不同连接独立确认。
230
+
231
+ 跨流可以并行,同流可以在页面边界换通道。通道不是顺序身份,设备和 slot 也不能为增加通道而变化。
232
+
233
+ 服务端序号可能稀疏,继续校验 `scan_after_seq`、`covered_through_seq`、`next_after_seq`、`head_seq`、保留边界及既有前驱关系。收到最高序号不代表已经覆盖历史,不以 `seq+1` 判断缺口。
234
+
235
+ ### 7.2 保留现有 A/T/H
236
+
237
+ | 状态 | 含义 |
238
+ | --- | --- |
239
+ | A | 连续可靠提交水位,用于安全 ACK |
240
+ | T/H | 已验证的最新窗口边界,历史缺口可以尚未补齐 |
241
+ | initial_goal | 本轮恢复时记录的积压目标 |
242
+ | latest_goal | 后续推送或响应提升的目标 |
243
+
244
+ 聊天界面可先展示最新窗口,再按服务端顺序插入历史;这不推进尚未覆盖的连续 ACK。需要顺序执行的 Agent 指令必须等待同一流的历史缺口,不能先执行最新动作再执行旧动作。
245
+
246
+ 其他独立流及协议上独立的管理消息可以及时处理,但不能跳过群成员、权限或密钥等明确依赖。
247
+
248
+ 初始记录目标 10000,安全覆盖到该目标即可报告上线积压完成;之后追赶新消息。当前每页 head 是该页查询时的上界,initial_goal 只用于 SDK 进度阶段,不冒充固定服务端快照。需要严格固定上界分页时再扩展协议。
249
+
250
+ 主通道 Push 只合并提高目标,不为每个通知新建 worker。完整 Push 正文和 Pull 页面都进入统一去重、解密及分发路径。
251
+
252
+ ## 8. 消费背压与内存
253
+
254
+ 所有连接共用总 16、background 8、event 4、pull 4。历史 Pull 最多占 3 个,为实时或前台 Pull 保留位置;辅助连接上限与请求额度互相独立。
255
+
256
+ 派发前取得会话调度资格、共享 RPC 额度和内存预留,避免持有一种资源无限等待另一种。无消费能力时不派新页。
257
+
258
+ 客户端内存计量覆盖在途响应、密文、JSON 对象、明文、预取及等待提交数据;服务端按编码字节计量,SDK 还须执行更严格的本地内存预算。不能把编码长度等同于进程 RSS。
259
+
260
+ 单页同时受条数和硬字节上限约束。超大单条不能导致空页循环:应走已约定的大对象读取方式,或返回明确可处理结果;不静默跳过消息。当前约 768 KiB 的截断目标允许首条超出,不能直接作为新的硬上限承诺。
261
+
262
+ 已有 EventService 64 条/64 MiB 限制保持其业务范围。普通消息补拉有自己的共享预算;不能每条辅助连接各分配一份预算,也不能将 EventService 的值误写为普通消息当前容量。
263
+
264
+ ## 9. 完整流程与动态收缩
265
+
266
+ ### 9.1 上线、补拉和归还通道
267
+
268
+ ```mermaid
269
+ sequenceDiagram
270
+ participant M as 主通道
271
+ participant C as 同步协调器
272
+ participant S as 辅助通道
273
+ participant G as Gateway
274
+ participant MS as Message/Group
275
+ M->>G: long认证,协商elastic_connections_v1与sync_channels_v1
276
+ G-->>M: 主会话与策略v1,允许1条辅助
277
+ M->>C: 先安装策略,再启动同步
278
+ C->>S: 满足4流或下界100门槛,按授权建立short
279
+ S->>G: 认证,绑定主会话及history_sync用途
280
+ G-->>S: 原子准入并返回最新策略
281
+ loop 有积压且具备共享额度和消费能力
282
+ C->>S: 分配某个流的下一页
283
+ S->>G: 受限Pull
284
+ G->>MS: 按已认证设备、slot及既有游标查询
285
+ MS-->>G: 消息页及覆盖证明
286
+ G-->>S: 页面加发送时的最新策略
287
+ S->>C: 先观察策略,再交付页面
288
+ C->>C: 校验、协调解密、提交并计算安全ACK
289
+ C->>M: 发ACK,或为下一页提供已提交ACK
290
+ M-->>C: Push附最新策略,先观察再合并目标
291
+ end
292
+ C->>S: 无任务,空闲期后关闭
293
+ ```
294
+
295
+ ### 9.2 从 1 收缩为 0,再恢复为 1
296
+
297
+ ```mermaid
298
+ sequenceDiagram
299
+ participant C as SDK协调器
300
+ participant S as 辅助通道
301
+ participant G as Gateway
302
+ G->>G: 压力升高,立即安装v8并收紧准入
303
+ G-->>C: Pull触发事件附v8:辅助0,历史在途0
304
+ C->>C: 停派历史页,保留待同步目标
305
+ S-->>C: 旧请求合法页面迟到,附v7
306
+ C->>C: 忽略旧策略,按原接收契约处理有效页
307
+ C->>S: 排空或取消后关闭
308
+ Note over C,G: 不轮询,不借主通道绕过历史暂停
309
+ G->>G: 容量恢复,安装v9
310
+ G-->>C: 主动策略通知:辅助1,历史在途1
311
+ C->>S: 按需重新建立
312
+ C->>S: 从安全提交游标继续
313
+ ```
314
+
315
+ 策略变化与页面处理在同一主会话调度状态下协调。派发前的最后检查和额度预留必须原子化,避免刚收到暂停策略又派出旧任务。
316
+
317
+ ## 10. ACK、加密与故障恢复
318
+
319
+ | 情况 | 处理规则 |
320
+ | --- | --- |
321
+ | 辅助连接断开 | 从安全提交游标重试,不改变主通道在线身份 |
322
+ | 超时与迟到响应 | 同一请求只提交一次;策略版本旧不等于页面无效 |
323
+ | 主连接断开、替换或迁移 | 使所有附属任务代际失效,停止派发;新主连接重新取策略 |
324
+ | 同一消息同时 Push/Pull | 沿用既有命名空间和消息身份去重,统一分发 |
325
+ | token 更新 | 主 client 统一管理,新辅助连接使用最新有效 token;已认证连接不因原 token 自然到期立即断线 |
326
+ | 过保留期或无权限 | 按既有边界报告缺口或终止相应任务,不无限重拉 |
327
+ | 历史密钥/群状态缺失 | 优先恢复依赖,保持有界等待与现有失败语义 |
328
+ | Gateway 不支持新能力 | 不建辅助连接,沿用原主通道的小页低优先级补拉 |
329
+
330
+ 共享有状态解密会话,按其必要粒度串行推进;不能由多个完整 client 各自更新同一套解密状态。身份代际检查与提交边界需要协调,避免检查通过后身份切换造成旧页写入新状态。
331
+
332
+ 下载完成、SDK 可靠接收、应用业务处理完成是不同阶段。ACK 沿用现有接收契约,由协调器计算;worker 不自行 ACK 已下载最大 seq。
333
+
334
+ 若要求进程崩溃后仍不漏业务,消息正文或可靠待处理记录应与游标先完成本地事务,再 ACK。只持久化游标不等于持久化消息;增加通道本身不提供应用副作用恰好执行一次的保证。
335
+
336
+ ## 11. volatile、控制消息与兼容
337
+
338
+ 长期离线补拉的主体是持久消息。volatile 使用独立游标与原 TTL,过期事件不在历史恢复承诺内。
339
+
340
+ 管理、续租和实时必要处理保留主通道优先路径。若现有 Pull 混合持久和 volatile 行,真正分离需要新增通用的存储范围选择,同时保留既有设备、slot 与服务注册过滤,不能只换 socket 就宣称已完全分流。
341
+
342
+ 新策略仅经认证的连接控制路径交给 SDK 消费,不发布成应用业务消息。主辅通道能力分别协商,辅助通道不能绕过主会话授权;旧端继续原行为,Gateway 总体保护始终有效。
343
+
344
+ Message 不需要维护事件名、订阅者、worker 分配等业务注册表。本方案也不改变 EventService 单实例租约的既定边界。
345
+
346
+ ## 12. 经典系统可借鉴的机制
347
+
348
+ | 系统 | 可借鉴机制 | AUN 落点 |
349
+ | --- | --- | --- |
350
+ | [Telegram Updates](https://core.telegram.org/api/updates) | 状态水位、差异恢复、缺口处理 | 合并目标,统一补齐差异 |
351
+ | [XMPP MAM](https://xmpp.org/extensions/xep-0313.html) | 稳定归档分页、历史与实时协调 | 历史顺序不依赖网络到达顺序 |
352
+ | [Kafka](https://kafka.apache.org/documentation/#intro_consumers) | 分区内顺序、分区间并行 | 同 peer/group 协调提交,跨流调度 |
353
+ | [NATS Pull Consumer](https://docs.nats.io/nats-concepts/jetstream/consumers) | 消费侧信用、共享在途限制、按字节限批 | 先预留消费能力,再取受限页面 |
354
+
355
+ 借鉴上述机制,不照搬其完整服务模型,也不把积压条数直接换算成 TCP 连接数。
356
+
357
+ ## 13. 实施顺序与验收
358
+
359
+ ### 13.1 分阶段实施
360
+
361
+ | 阶段 | 交付与通过条件 |
362
+ | --- | --- |
363
+ | P0 契约与共同向量 | 固定策略作用域、版本、0值、观察顺序、主辅绑定及新旧兼容;先补定向失败用例 |
364
+ | P1 共享预算 | 先收口四 SDK 调度、消费预留与字节上限,不增加连接也应正确 |
365
+ | P2 Gateway 授权 | 实现所有 Pull 触发入口的最新策略注入、强制准入与主动恢复通知 |
366
+ | P3 单辅助通道 | Gateway 允许1条时使用,主辅共用状态,断线恢复与顺序通过 |
367
+ | P4 动态0/1 | 验证实时降额、乱序策略、并发建连、恢复与公平性 |
368
+ | P5 Ubuntu Docker 验收 | 四 SDK 分别验收,再执行跨 SDK 生成/接收矩阵和受控性能测试 |
369
+
370
+ 各阶段按 Red→Green 推进;首个失败修复后从该项续跑。四 SDK 512 条辅助断线、S14 超页停流与显式恢复已分段通过,最终 Browser bundle 的真实 Chromium 260 条断线退主通过。S06/S08/S22定向验证、S07/S11/S16 Ubuntu私有Gateway+SDK夹具验收及S23四端隔离SDK+WebSocket协议夹具通过;其中Gateway夹具使用测试Auth/Message服务,不是完整AUN服务E2E。Gateway S10/S20/S21/S24定向race及全部TestSync race通过。64项真实跨SDK基础矩阵均已有通过attempt,原首败保留;S03持续新消息尚未执行,S17旧SDK→新Gateway的16项协议验证通过,反向仍有服务身份失败。S13四端Ubuntu隔离定向测试通过,但`network=none`不覆盖完整AUN服务E2E;Group分页补丁已部署,S15真实Chromium冷SPK恢复通过,native四端验收继续。S27配置已保存而实例未应用→同实例恢复已在Ubuntu通过;S26快速切换、S25/S28和受控性能仍待验收。逐项历史证据与边界见实施计划第9.4节;历史结果不证明本轮单辅助连接与 namespace 串行新规则,须按第9.5节重新验收,完整阶段门仍未通过。
371
+
372
+ ### 13.2 场景与验收标准
373
+
374
+ | 编号 | 场景 | 验收标准 |
375
+ | --- | --- | --- |
376
+ | S01 | 单 peer 巨大积压 | 同流提交正确,额外通道不造成独立游标或ACK |
377
+ | S02 | 多 peer、多群积压 | 跨流公平推进,续页不长期霸占调度 |
378
+ | S03 | 补拉时持续收到新消息 | A/T/H与初始目标正确,聊天回填和指令顺序语义清晰 |
379
+ | S04 | 普通RPC并发 | 固定网络/负载下P95增幅目标不超过关闭补拉基线20%,绝对值压测确定 |
380
+ | S05 | 慢消费、慢解密、慢磁盘 | 背压到请求派发,条数、字节和在途资源始终有界 |
381
+ | S06 | 各种Pull触发事件/响应 | 每个入口都先安装策略再调度,包括错误和续页 |
382
+ | S07 | Gateway授权0→1→0→1 | SDK不越权开连接或借主通道绕过历史暂停 |
383
+ | S08 | 多socket乱序、重复策略 | 旧版本不回退,新策略一次生效;合法旧页仍可处理 |
384
+ | S09 | 主连接替换、迁移与迟到页 | 旧主会话结果不提交、不ACK,新会话取得新策略 |
385
+ | S10 | 建连过程中降低额度 | 已建和建立中连接原子计数,多余通道不被接纳 |
386
+ | S11 | 暂停后无新消息和RPC | Gateway主动策略通知可恢复,SDK无轮询任务 |
387
+ | S12 | 辅助断线和请求超时 | 从安全游标重试,主连接持续可用,无重复状态推进 |
388
+ | S13 | 稀疏序号、重复页、保留期 | 不猜seq连续性,不跳确认,不无限重试 |
389
+ | S14 | 单条超大消息 | 不突破硬预算,不产生空页死循环,不静默跳过 |
390
+ | S15 | 群状态、密钥、volatile | 保留依赖、可见性、TTL和独立游标语义 |
391
+ | S16 | 多AID、多设备、多辅助连接 | 按AID聚合公平,同一主体不靠多开连接增加份额 |
392
+ | S17 | 新旧SDK/Gateway组合 | 未协商时不使用新功能,既有业务调用继续可用 |
393
+ | S18 | 四端及跨SDK | Python/Go/TS/JS分别验收;四发送端×四接收端验证互通和恢复 |
394
+ | S19 | 延迟、带宽与丢包变化 | 验证真实收益,伸缩有冷却,无建连/认证风暴 |
395
+ | S20 | 应用或远端注入同名策略 | 非Gateway外层字段不改变额度和主会话绑定 |
396
+ | S21 | 空闲无需求连接与待消费连接并存 | Gateway优先回收确认无需求的空闲连接,不能把等待消费额度视为无需求 |
397
+ | S22 | 主动回收、通知与关闭乱序 | SDK幂等退役目标通道,主client保持在线,不立即重连抢占 |
398
+ | S23 | 回收时页面完整或中途断开 | 完整合法页安全处理,未完成页不推进ACK,任务和预留正确恢复 |
399
+ | S24 | 需求状态变化与空闲回收竞态 | Gateway校验归属和自身在途计数,新需求不丢任务,无周期需求心跳 |
400
+
401
+ ## 14. 现有依据与相关文档
402
+
403
+ 下列路径用于定位实现;均以仓库当前代码为准。
404
+
405
+ | 现有入口 | 对本方案的约束 |
406
+ | --- | --- |
407
+ | `python/src/aun_core/_client/rpc_pipeline.py` 的 `run_pull_serialized` | 同namespace单飞、全局Pull调度及续页公平 |
408
+ | `python/src/aun_core/transport.py` 的 `_acquire_rpc_slot` | 主辅 Transport 共用16/8/4/4预算 |
409
+ | `python/src/aun_core/_client/delivery.py` 的 `_peer_pull_pages` | 请求前消费预留、页面证明、提交和安全ACK |
410
+ | `python/src/aun_core/seq_tracker.py` | A/T/H与最新窗口、连续历史的区分 |
411
+ | `extensions/services/gateway/go/internal/httpserver/server.go` | short共存、认证、设备/slot注入及连接级队列 |
412
+ | `extensions/services/message/go/internal/p2p/pull.go` | peer分页、覆盖证明、可携带ACK及页面字节目标 |
413
+
414
+ Python路径相对 `aun-sdk-core/`,服务端路径相对仓库根。四 SDK 实施需要共享同一份协议向量与验收语义,不共享语言代码。
415
+
416
+ - [消息同步与历史拉取改造方案](消息同步与历史拉取改造方案.md):保留既有Forward/Tail/History及发布排序语义。
417
+ - [控制消息与设备能力协商方案](控制消息与设备能力协商方案.md):保留分类、设备能力与控制消费边界。
418
+ - [风控响应与SDK协同方案](风控响应与SDK协同方案.md):同步资源策略与既有风控各自执行,取更严格的有效限制。
419
+
420
+ ## 15. 控制面板开关与配置持久化
421
+
422
+ 控制面板提供 Gateway 的“启用弹性补拉通道”布尔配置,经现有配置中心的修订、绑定及发布流程持久保存。页面重新打开、Console 或 Gateway 重启后保留设置;Gateway 在接受辅助连接前先加载有效配置。
423
+
424
+ 配置键为 `sync_channels_enabled`,布尔值,缺省为 `true`,已有显式 `false` 保持关闭。仅双能力协商成功的 SDK 可按需使用辅助通道,同一 Gateway 内 `(aid, device_id)` 跨 slot、主会话合计最多 1 条;实际允许 0/1 条仍由 Gateway 根据实时压力决定。
425
+
426
+ | 操作 | Gateway 与 SDK 的处理 |
427
+ | --- | --- |
428
+ | 开启 | Gateway 更新策略并通知双能力协商成功的在线主会话;SDK 仅在有积压、授权允许且本地资源足够时申请辅助连接 |
429
+ | 关闭 | Gateway 立即禁止新增辅助连接,发布 `max_aux_channels=0`,通知已有辅助通道排空或取消后关闭 |
430
+ | 关闭后继续补拉 | 使用主通道小页、低优先级路径;仍执行历史请求与速率额度,历史暂停时不绕过 |
431
+ | 保存失败 | 不显示保存成功,不把草稿作为有效配置 |
432
+ | 保存后尚未全部应用 | 面板区分持久配置修订和各实例生效状态,恢复配置链路后按最新有效版本收敛 |
433
+
434
+ 仅修改 `sync_channels_enabled` 时热应用,不重启 Gateway,不中断在线主连接。Launcher 比较配置摘要时仅排除这一开关;出现其它冷配置或混合改动时,继续沿用原重启流程。
435
+
436
+ 热应用等待期间保留旧实际配置版本;只有 Gateway 诊断返回的实例、目标配置版本和 `enabled` 均匹配后,Launcher 才报告新版本已实际应用。诊断缺失或版本不符时继续待应用,不提前报告成功。复用现有配置 watch 和生命周期检查,不新增定时任务。
437
+
438
+ 配置作用域、权限、发布和实例状态沿用现有控制面机制。配置修订号与主会话内的策略版本分别管理;持久化完成不冒充所有 Gateway 已应用完成,不增加 SDK 配置轮询。
439
+
440
+ 实施任务、TDD 阶段及四 SDK/Ubuntu Docker 验收见[消息弹性补拉通道实施计划](消息弹性补拉通道实施计划.md),面板保存、重启、在线切换和多 Gateway 配置范围对应 S25—S28。
441
+
442
+ ## 16. 通用弹性能力与本轮补拉用途
443
+
444
+ 能力声明分为 `elastic_connections_v1`(支持通用弹性辅助连接)与 `sync_channels_v1`(支持本轮补拉策略协议)。主辅连接均声明并协商两者,Gateway 再结合面板配置决定补拉授权;未支持或未协商时沿用原主通道路径。
445
+
446
+ `purpose=history_sync` 是当前用途。弹性连接的认证、主会话绑定、准入、关闭及回收应能供其它用途复用;每种用途另定义允许调用的方法及资源策略,本轮只实现历史补拉。`_meta.sync_policy` 及 `gateway.sync_*` 通知属于补拉用途,不能把它们作为未来所有用途的统一业务协议。
447
+
448
+ ## 17. 辅助连接断线后的主通道回退
449
+
450
+ 弹性/补拉连接断线后不自动重连。协调器也不为同一批待补任务冷却后自动补开连接;未完成页保留安全游标,退回当前主连接,使用剩余请求期限、共享资源预算及最新历史策略继续处理。
451
+
452
+ 回退不能绕过历史暂停,不能让未完成页推进 ACK,不能使主 client 下线。当前主会话或策略内记住辅助故障,重复策略快照不恢复辅助资格;收到新的 Gateway 授权策略或主会话重新协商后,才可重新按需申请。详细状态与四端断线验收见实施计划第 9.1.1 节。
453
+
454
+ 自然断线、RPC 失败及建连失败锁定实际请求或连接使用的策略版本,不读取失败回调执行时的最新版本。主动 `sync_channel_close` 通知优先锁定其附带的合法权威策略版本;缺少、非法或主会话不匹配时,使用目标连接的实际使用版本。迟到的旧失败或旧关闭通知不影响新策略资格。
455
+
456
+ ## 18. 明显积压门槛、数量提示与连接统计
457
+
458
+ ### 18.1 只为明显积压使用辅助连接
459
+
460
+ 本轮 Gateway 对同一 AID+设备跨 slot 合计最多授权一条;SDK 遵从策略支持 0..N 辅助连接(当前协议测试到 2)。同 namespace Pull 串行、不同 namespace 在授权和预算允许时并行;连接数上限不等同于 Pull 并发总数为 1。双能力协商、Gateway 授权和本地预算均满足后,SDK 再检查当前请求:
461
+
462
+ | 条件 | 判定方式 |
463
+ | --- | --- |
464
+ | 多流积压 | 当前真实待拉的唯一 peer、群消息、群事件流合计至少 4 条,且当前请求属于这些流;同一流的目录、推送、排队和在途记录合并计一次 |
465
+ | 单流积压 | 同流最新普通持久 Forward 页的 `pending_count_lower_bound>=100`,且原分页契约要求继续拉取 |
466
+ | 不满足门槛 | 留在主连接;目录、状态、History、Tail、control、sdk_control 和 volatile 请求也走主连接 |
467
+
468
+ 辅助候选仅为普通持久消息的 `message.pull`、`message.v2.pull`、`group.pull`、`group.v2.pull` 和 `group.pull_events`。首次上线之外,在线期间再次出现符合门槛的积压也可按需使用;追平后日常 Push/Pull/ACK 仍由主连接处理。不累计已经拉到的消息数,不用稀疏序号差推算待拉条数。
469
+
470
+ ### 18.2 数量提示不增加查询成本
471
+
472
+ 上述普通持久 Forward 请求带 `max_page_bytes` 时,服务端可返回 `pending_count_lower_bound`,整数上限 100。Message 要求显式 `storage_scope=persistent`;Group 当前 `all` 也只含持久来源。它只统计本次既有读取中经过可见性过滤、去重后,最终没有放入响应的候选记录;字节截页移出的记录计入下界。不新增 RPC、SQL COUNT、查询、扫描或读取条数。
473
+
474
+ Message 在无法证明正下界时省略字段;Group 可返回 0。缺省或 0 均不证明追平,续页和完成仍使用原 `has_more`、head、游标与覆盖证明。`force`、History、Tail、control、sdk_control、volatile 及 Message 的持久/临时混合来源不提供此提示。
475
+
476
+ 原 `limit+1` 读取常常只能证明还有 1 条,Group V2 原读取甚至只能证明 0;大正文因字节预算提前截页时,才可能证明至少还有 100 条。此机制不能识别所有单流大积压,也不为凑够阈值补读数据。目录既有 `pending_count_estimate` 是序号跨度上界,不参与此门槛。
477
+
478
+ ### 18.3 控制台连接统计
479
+
480
+ | Gateway 字段 | 控制台口径 |
481
+ | --- | --- |
482
+ | `traffic.daily_history_sync_connections` | UTC+8 当日成功建立的补拉连接次数,重建重新计次,失败不计;同 issuer 共享日总,跨 Gateway 按 `daily_message_scope` 与日期取最新日最大值,避免重复求和 |
483
+ | `traffic.connections.history_sync` | 当前 Gateway 在线补拉连接数,可跨 Gateway 相加 |
484
+
485
+ 概览与 Gateway 页面均显示“当日补拉连接”和“在线补拉连接”;旧 Gateway 缺字段时显示 `—`。统计复用现有分钟批量持久化,不新增定时任务。
package/dist/auth.js CHANGED
@@ -16,7 +16,7 @@ import * as http from 'node:http';
16
16
  import * as https from 'node:https';
17
17
  import * as path from 'node:path';
18
18
  import { URL, fileURLToPath } from 'node:url';
19
- import { AuthError, StateError, ValidationError, AUNError, mapRemoteError } from './errors.js';
19
+ import { AuthError, ConnectionError, StateError, ValidationError, AUNError, mapRemoteError } from './errors.js';
20
20
  import { shortRpcViaNetworkActor } from './transport.js';
21
21
  import { isJsonObject, } from './types.js';
22
22
  import { VERSION as AUN_SDK_VERSION } from './version.js';
@@ -824,7 +824,7 @@ export class AuthFlow {
824
824
  */
825
825
  async connectSession(transport, challenge, gatewayUrl, opts) {
826
826
  const tStart = Date.now();
827
- const nonce = this._extractChallengeNonce(challenge);
827
+ let nonce = this._extractChallengeNonce(challenge);
828
828
  const deviceId = String(opts?.deviceId ?? '');
829
829
  const slotId = String(opts?.slotId ?? '');
830
830
  const deliveryMode = opts?.deliveryMode ?? null;
@@ -832,6 +832,27 @@ export class AuthFlow {
832
832
  const shortTtlMs = opts?.shortTtlMs;
833
833
  const extraInfo = opts?.extraInfo;
834
834
  this.setInstanceContext({ deviceId, slotId });
835
+ let reconnectRequired = false;
836
+ const initializeSession = async (token) => {
837
+ if (reconnectRequired && typeof transport.connect === 'function') {
838
+ nonce = this._extractChallengeNonce(await transport.connect(gatewayUrl));
839
+ reconnectRequired = false;
840
+ }
841
+ try {
842
+ return await this._initializeSession(transport, nonce, token, {
843
+ deviceId, slotId, deliveryMode, connectionKind, shortTtlMs, extraInfo,
844
+ });
845
+ }
846
+ catch (err) {
847
+ if (err instanceof AuthError)
848
+ reconnectRequired = true;
849
+ else if (err instanceof ConnectionError && /\bcode=4001\b/.test(err.message)) {
850
+ reconnectRequired = true;
851
+ throw new AuthError(err.message);
852
+ }
853
+ throw err;
854
+ }
855
+ };
835
856
  this._logger.debug(`connectSession enter: gateway=${gatewayUrl}, device_id=${deviceId}, slot_id=${slotId}, has_explicit_token=${!!opts?.accessToken}, has_challenge_nonce=${!!nonce}`);
836
857
  try {
837
858
  let identity;
@@ -845,14 +866,7 @@ export class AuthFlow {
845
866
  const explicitToken = String(opts?.accessToken || '');
846
867
  if (explicitToken && identity !== null) {
847
868
  try {
848
- const hello = await this._initializeSession(transport, nonce, explicitToken, {
849
- deviceId,
850
- slotId,
851
- deliveryMode,
852
- connectionKind,
853
- shortTtlMs,
854
- extraInfo,
855
- });
869
+ const hello = await initializeSession(explicitToken);
856
870
  identity.access_token = explicitToken;
857
871
  this._persistIdentity(identity);
858
872
  this._logger.debug(`connectSession exit: elapsed=${Date.now() - tStart}ms strategy=explicit_token aid=${String(identity.aid ?? '')}`);
@@ -868,29 +882,15 @@ export class AuthFlow {
868
882
  if (identity === null) {
869
883
  const authContext = await this.ensureAuthenticated(gatewayUrl);
870
884
  const token = String(authContext.token);
871
- const hello = await this._initializeSession(transport, nonce, token, {
872
- deviceId,
873
- slotId,
874
- deliveryMode,
875
- connectionKind,
876
- shortTtlMs,
877
- extraInfo,
878
- });
885
+ const hello = await initializeSession(token);
879
886
  this._logger.debug(`connectSession exit: elapsed=${Date.now() - tStart}ms strategy=ensure_authenticated aid=${String(authContext.identity?.aid ?? '')}`);
880
887
  return { ...authContext, hello };
881
888
  }
882
889
  // 策略 2:缓存的 access_token
883
890
  const cachedToken = AuthFlow._getCachedAccessToken(identity);
884
- if (cachedToken) {
891
+ if (cachedToken && cachedToken !== explicitToken) {
885
892
  try {
886
- const hello = await this._initializeSession(transport, nonce, cachedToken, {
887
- deviceId,
888
- slotId,
889
- deliveryMode,
890
- connectionKind,
891
- shortTtlMs,
892
- extraInfo,
893
- });
893
+ const hello = await initializeSession(cachedToken);
894
894
  this._logger.debug(`connectSession exit: elapsed=${Date.now() - tStart}ms strategy=cached_token aid=${String(identity.aid ?? '')}`);
895
895
  return { token: cachedToken, identity, hello };
896
896
  }
@@ -907,14 +907,7 @@ export class AuthFlow {
907
907
  identity = await this.refreshCachedTokens(gatewayUrl, identity);
908
908
  const newCachedToken = AuthFlow._getCachedAccessToken(identity);
909
909
  if (newCachedToken) {
910
- const hello = await this._initializeSession(transport, nonce, newCachedToken, {
911
- deviceId,
912
- slotId,
913
- deliveryMode,
914
- connectionKind,
915
- shortTtlMs,
916
- extraInfo,
917
- });
910
+ const hello = await initializeSession(newCachedToken);
918
911
  this._logger.debug(`connectSession exit: elapsed=${Date.now() - tStart}ms strategy=refresh_token aid=${String(identity.aid ?? '')}`);
919
912
  return { token: newCachedToken, identity, hello };
920
913
  }
@@ -934,14 +927,7 @@ export class AuthFlow {
934
927
  if (!token) {
935
928
  throw new AuthError('authenticate did not return access_token');
936
929
  }
937
- const hello = await this._initializeSession(transport, nonce, token, {
938
- deviceId,
939
- slotId,
940
- deliveryMode,
941
- connectionKind,
942
- shortTtlMs,
943
- extraInfo,
944
- });
930
+ const hello = await initializeSession(token);
945
931
  identity = this.loadIdentity(identity.aid);
946
932
  this._logger.debug(`connectSession exit: elapsed=${Date.now() - tStart}ms strategy=full_reauth aid=${String(identity?.aid ?? '')}`);
947
933
  return { token, identity, hello };
@@ -1566,6 +1552,8 @@ export class AuthFlow {
1566
1552
  sync_channels_v1: true,
1567
1553
  ...(this._requestAdmission && this._verifySsl && this._riskCapabilityEnabled?.() ? { risk_test_v1: true } : {}),
1568
1554
  };
1555
+ if (connectionKind !== 'long' || typeof extraInfoRaw?._sync_parent === 'string' || capabilities.udp_standby_v1 !== true)
1556
+ delete capabilities.udp_standby_v1;
1569
1557
  const request = {
1570
1558
  nonce,
1571
1559
  auth: { method: 'kite_token', token },