@agentunion/fastaun-browser 0.5.18 → 0.5.19

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 (118) hide show
  1. package/CHANGELOG.md +44 -2
  2. package/_packed_docs/CHANGELOG.md +44 -2
  3. package/_packed_docs/INDEX.md +136 -14
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +42 -16
  5. package/_packed_docs/agent.md/SCHEMA.md +245 -205
  6. 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 +42 -15
  7. package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +4 -4
  8. package/_packed_docs/protocol/01-/350/272/253/344/273/275/344/270/216/345/207/255/350/257/201/345/215/217/350/256/256-auth.md +45 -134
  9. package/_packed_docs/protocol/07-/351/224/231/350/257/257/347/240/201/344/270/216/347/212/266/346/200/201/346/234/272.md +5 -20
  10. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +28 -9
  11. package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +22 -27
  12. package/_packed_docs/protocol/agent.md/SCHEMA.md +4 -0
  13. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +5 -2
  14. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +250 -283
  15. package/_packed_docs/sdk/09-group-rpc-manual.md +46 -14
  16. package/_packed_docs/sdk/09-message-rpc-manual.md +40 -2
  17. package/_packed_docs/sdk/09-storage-rpc-manual.md +74 -64
  18. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +25 -5
  19. package/_packed_docs/sdk/INDEX.md +76 -17
  20. package/dist/agent-md-schema.d.ts +3 -0
  21. package/dist/agent-md-schema.d.ts.map +1 -1
  22. package/dist/agent-md-schema.js +45 -11
  23. package/dist/agent-md-schema.js.map +1 -1
  24. package/dist/agent-md.d.ts +45 -2
  25. package/dist/agent-md.d.ts.map +1 -1
  26. package/dist/agent-md.js +519 -30
  27. package/dist/agent-md.js.map +1 -1
  28. package/dist/aid-store.d.ts +12 -46
  29. package/dist/aid-store.d.ts.map +1 -1
  30. package/dist/aid-store.js +113 -246
  31. package/dist/aid-store.js.map +1 -1
  32. package/dist/aid.d.ts.map +1 -1
  33. package/dist/aid.js +20 -7
  34. package/dist/aid.js.map +1 -1
  35. package/dist/auth.d.ts +4 -1
  36. package/dist/auth.d.ts.map +1 -1
  37. package/dist/auth.js +343 -23
  38. package/dist/auth.js.map +1 -1
  39. package/dist/bundle.js +37330 -32005
  40. package/dist/cert-utils.d.ts +1 -1
  41. package/dist/cert-utils.d.ts.map +1 -1
  42. package/dist/cert-utils.js +19 -7
  43. package/dist/cert-utils.js.map +1 -1
  44. package/dist/client/delivery.d.ts +15 -1
  45. package/dist/client/delivery.d.ts.map +1 -1
  46. package/dist/client/delivery.js +147 -51
  47. package/dist/client/delivery.js.map +1 -1
  48. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  49. package/dist/client/rpc-pipeline.js +19 -5
  50. package/dist/client/rpc-pipeline.js.map +1 -1
  51. package/dist/client/v2-e2ee.d.ts +1 -0
  52. package/dist/client/v2-e2ee.d.ts.map +1 -1
  53. package/dist/client/v2-e2ee.js +104 -30
  54. package/dist/client/v2-e2ee.js.map +1 -1
  55. package/dist/client.d.ts +100 -1
  56. package/dist/client.d.ts.map +1 -1
  57. package/dist/client.js +1204 -23
  58. package/dist/client.js.map +1 -1
  59. package/dist/errors.d.ts.map +1 -1
  60. package/dist/errors.js +3 -2
  61. package/dist/errors.js.map +1 -1
  62. package/dist/events.d.ts +4 -0
  63. package/dist/events.d.ts.map +1 -1
  64. package/dist/events.js +41 -1
  65. package/dist/events.js.map +1 -1
  66. package/dist/facades.d.ts +82 -2
  67. package/dist/facades.d.ts.map +1 -1
  68. package/dist/facades.js +98 -4
  69. package/dist/facades.js.map +1 -1
  70. package/dist/index.d.ts +7 -3
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +3 -2
  73. package/dist/index.js.map +1 -1
  74. package/dist/keystore/index.d.ts +17 -0
  75. package/dist/keystore/index.d.ts.map +1 -1
  76. package/dist/keystore/indexeddb-shared.d.ts +10 -1
  77. package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
  78. package/dist/keystore/indexeddb-shared.js +111 -0
  79. package/dist/keystore/indexeddb-shared.js.map +1 -1
  80. package/dist/keystore/indexeddb-token-store.d.ts +7 -1
  81. package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
  82. package/dist/keystore/indexeddb-token-store.js +30 -1
  83. package/dist/keystore/indexeddb-token-store.js.map +1 -1
  84. package/dist/org-protocol.d.ts +98 -0
  85. package/dist/org-protocol.d.ts.map +1 -0
  86. package/dist/org-protocol.js +1742 -0
  87. package/dist/org-protocol.js.map +1 -0
  88. package/dist/ownership-proof.d.ts +93 -5
  89. package/dist/ownership-proof.d.ts.map +1 -1
  90. package/dist/ownership-proof.js +239 -53
  91. package/dist/ownership-proof.js.map +1 -1
  92. package/dist/ownership-v21.d.ts +177 -0
  93. package/dist/ownership-v21.d.ts.map +1 -0
  94. package/dist/ownership-v21.js +609 -0
  95. package/dist/ownership-v21.js.map +1 -0
  96. package/dist/ownership-workflow.d.ts +146 -0
  97. package/dist/ownership-workflow.d.ts.map +1 -0
  98. package/dist/ownership-workflow.js +1066 -0
  99. package/dist/ownership-workflow.js.map +1 -0
  100. package/dist/seq-tracker.d.ts +1 -0
  101. package/dist/seq-tracker.d.ts.map +1 -1
  102. package/dist/seq-tracker.js +10 -2
  103. package/dist/seq-tracker.js.map +1 -1
  104. package/dist/tools/cross-sdk-agent.d.ts +82 -0
  105. package/dist/tools/cross-sdk-agent.d.ts.map +1 -1
  106. package/dist/tools/cross-sdk-agent.js +332 -7
  107. package/dist/tools/cross-sdk-agent.js.map +1 -1
  108. package/dist/transport.d.ts +6 -0
  109. package/dist/transport.d.ts.map +1 -1
  110. package/dist/transport.js +88 -2
  111. package/dist/transport.js.map +1 -1
  112. package/dist/v2/e2ee/decrypt.d.ts +1 -0
  113. package/dist/v2/e2ee/decrypt.d.ts.map +1 -1
  114. package/dist/v2/e2ee/decrypt.js +9 -1
  115. package/dist/v2/e2ee/decrypt.js.map +1 -1
  116. package/dist/version.d.ts +1 -1
  117. package/dist/version.js +1 -1
  118. package/package.json +1 -1
@@ -62,57 +62,19 @@ store := aun.NewAIDStore(aunPath, encryptionSeed, aun.AIDStoreOptions{
62
62
 
63
63
  | Python | TS/JS | Go | 说明 |
64
64
  |--------|-------|----|------|
65
- | `load(aid)` | `load(aid)` | `Load(aid)` | 从本地加载 AID |
66
- | `register(aid)` | `register(aid)` | `Register(ctx, aid)` | 注册并落盘证书和私钥 |
67
- | `register({aid, type, owner?, ...})` | `register({aid, type, owner?, ...})` | `RegisterWithOptions(ctx, params)` | 新 AID 的类型/owner 注册编排 |
68
- | `bind_aid(params)` | `bindAid(params)` | `BindAID(ctx, params)` | 已有证书的 legacy/unbound 关系绑定 |
69
- | `get_aid_binding_status(aid)` | `getAidBindingStatus(aid)` | `GetAIDBindingStatus(ctx, aid)` | 查询绑定状态 |
70
- | `resume_aid_binding(operation_id)` | `resumeAidBinding(operation_id)` | `ResumeAIDBinding(ctx, operationID, aid...)` | 查询 operation 并恢复激活 |
71
- | `change_owner(params)` | `changeOwner(params)` | `ChangeOwner(ctx, params)` | 三方换 owner 编排 |
72
- | `list()` | `list()` | `List()` | 列出本地 AID |
65
+ | `load(aid)` | `load(aid)` | `Load(aid)` | 从本地加载 AID |
66
+ | `register(aid)` | `register(aid)` | `Register(ctx, aid)` | 注册并落盘证书和私钥 |
67
+ | `list()` | `list()` | `List()` | 列出本地 AID |
73
68
  | `exists(aid)` | `exists(aid)` | `Exists(ctx, aid)` | 远端存在性检查 |
74
69
  | `resolve(aid, opts=None)` | `resolve(aid, opts?)` | `Resolve(ctx, aid, opts...)` | 拉证书并缓存;默认下载 agent.md,可用 `skip_agent_md` / `skipAgentMd` 跳过 |
75
- | `upload_agent_md(aid, content=None)` | `uploadAgentMd(aid, content?)` | `UploadAgentMD(ctx, aid, content...)` | 发布本地 AID 的 agent.md;签名后使用该 AID 的 access_token 上传 |
70
+ | `upload_agent_md(aid, content=None)` | `uploadAgentMd(aid, content?)` | `UploadAgentMD(ctx, aid, content...)` | 普通主体以本身份签名/token 发布;Group/Org 仅当前 owner 本人尾签并携带 V2 owner 授权上传,见协议 §2.5 |
76
71
  | `download_agent_md(aid, timeout_s=None)` | `downloadAgentMd(aid, timeoutMs=30000)` | `DownloadAgentMD(ctx, aid)` | 下载 agent.md,返回 `DownloadAgentMdResult` / `AgentMDInfo` |
77
72
  | `check_agent_md(aid, ttl_days=1)` | `checkAgentMd(aid, ttlDays=1)` | `CheckAgentMD(ctx, aid, maxUnsyncedDays...)` | 通过 HEAD 和本地记录检查一致性 |
78
73
  | `diagnose(aid)` | `diagnose(aid)` | `Diagnose(ctx, aid)` | 本地 + 远端诊断 |
79
74
  | `renew_cert(aid)` / `rekey(aid)` | `renewCert(aid)` / `rekey(aid)` | `RenewCert(ctx, aid)` / `Rekey(ctx, aid)` | 证书运维 |
80
75
  | `change_seed(old, new)` | `changeSeed(old, new)` | `ChangeSeed(old, new)` | 本地密钥保护种子迁移 |
81
76
 
82
- Python、TS/Node 与 Go 的 `AIDStore` 本地方法返回 Result 包装;浏览器 JS 因 IndexedDB / WebCrypto 约束返回 `Promise<Result>`。联网方法在 TS/JS 中均返回 `Promise<Result>`;Go 形态为 `Result[T]`,字段为 `Ok` / `Data` / `Error`。
83
-
84
- 新身份使用 `register({ aid, type, owner? })`:SDK 先签发 AID 证书,再通过 `auth.aid_register_*` 完成关系登记;不要对新身份先普通注册后调用 `bindAid`。`bind_aid` / `bindAid` / `BindAID` 用于已有证书且关系为 `legacy_unbound` 或 `unbound` 的 AID 绑定或重绑,不创建新证书。
85
-
86
- ### Owner 关系注册与绑定
87
-
88
- SDK 同时保留两条明确的高层路径:
89
-
90
- | 场景 | 高层入口 | 服务端流程 | 是否签发证书 |
91
- |------|----------|------------|:------------:|
92
- | 新 AID | `register({aid, type, owner?, agent_md?, idempotency_key?})` / `RegisterWithOptions` | `auth.aid_register_prepare` → `submit` → 发布带 proof 的 `agent.md` → `activate` | 是 |
93
- | 已有 legacy/unbound AID | `bind_aid` / `bindAid` / `BindAID` | `auth.aid_bind_prepare` → `submit` → 发布带 proof 的 `agent.md` → `activate` | 否 |
94
-
95
- `register(aid)` / `Register(ctx, aid)` 保持兼容语义:只创建或恢复证书并上传普通 `agent.md`,不会创建 owner 关系。新建非 `human` AID 必须在新注册入口提供 owner;`human` 不得提供 owner;只有 `agent` 可以使用 self-owner(`owner == aid`)。外部 owner 需要 subject 和 owner 两份签名,self-owner 只需要 subject 签名。
96
-
97
- Auth 关系类型可接受 `org`,但当前 CA/Name Service 只实现 `signing` 与 `group_identity` 证书用途;`org_identity` 尚未实现,SDK 不应把组织身份证书映射或组织专用 agent.md 签名描述为可用能力。
98
-
99
- 新注册和绑定完成 `prepare` 后,SDK 将服务端返回的 `proof_id`、`fact_hash`、`statement_hash` 及签名封套写入 `ownership_proof`,并用 `owner_operation_id` 上传 `agent.md`;普通 `uploadAgentMd` 不能改变已激活关系。operation 有效期为 7 天,证书在 pending/失败/过期期间仍可登录和通信。
100
-
101
- 状态可通过 `get_aid_binding_status` / `auth.aid_register_status` / `auth.owner_relation_status` 查询,通过 `resume_aid_binding` 或 `aid_operation_get` 在进程重启后恢复。常见状态为 `legacy_unbound`、`pending`、`proof_pending`、`agent_md_pending`、`active`、`not_applicable`、`unbound`、`unusable`、`expired`。
102
-
103
- ### Ownership proof 约定
104
-
105
- 四端 SDK 使用同一组 UTF-8 canonical bytes(对象键按字典序、无空格):
106
-
107
- ```text
108
- fact = canonical_json({"subject_aid": subject, "owner_aid": owner})
109
- statement = canonical_json({"proof_id": proof_id, "subject_aid": subject, "owner_aid": owner})
110
- signing_bytes = UTF-8("AUN-AID-OWNER-RELATION-V1;") || statement
111
- ```
112
-
113
- AID 先规范化为 ASCII 小写;`proof_id` 必须是 UUID v4;`fact_hash` 是 `SHA-256(fact)`,`statement_hash` 是 `SHA-256(signing_bytes)`,均为小写十六进制。签名封套只有 `cert_fingerprint`(`sha256:<64 hex>`)和严格 Base64 的 `signature`。`owner_signature` 对外部 owner 必需,对 self-owner 必须省略。
114
-
115
- `change_owner` 由当前 owner 发起,`expected_relation_version` 用于 CAS;提交 `old_owner_signature`、subject signature 和 new owner signature 后,subject 发布新 proof 并激活。成功后 `relation_version` 递增,旧 owner 的 Storage ACL 撤销但数据保留,新 owner 的 ACL 重新创建。
77
+ Python、TS/Node 与 Go 的 `AIDStore` 本地方法返回 Result 包装;浏览器 JS 因 IndexedDB / WebCrypto 约束返回 `Promise<Result>`。联网方法在 TS/JS 中均返回 `Promise<Result>`;Go 形态为 `Result[T]`,字段为 `Ok` / `Data` / `Error`。
116
78
 
117
79
  ### Result 类型
118
80
 
@@ -190,8 +152,8 @@ AID 由 `AIDStore.load()` 返回,应用层不直接构造。
190
152
  | `is_private_key_valid()` | `isPrivateKeyValid()` | `IsPrivateKeyValid()` | 私钥是否可用 |
191
153
  | `sign(data)` | `sign(data)` | `Sign(data)` | 签名 bytes |
192
154
  | `verify(data, signature)` | `verify(data, signature)` | `Verify(data, signature)` | 验签 |
193
- | `sign_agent_md(content)` | `signAgentMd(content)` | `SignAgentMd(content)` | agent.md 签名 |
194
- | `verify_agent_md(content)` | `verifyAgentMd(content)` | `VerifyAgentMd(content)` | agent.md 验签 |
155
+ | `sign_agent_md(content)` | `signAgentMd(content)` | `SignAgentMd(content)` | agent.md 签名 |
156
+ | `verify_agent_md(content)` | `verifyAgentMd(content)` | `VerifyAgentMd(content)` | agent.md 验签 |
195
157
 
196
158
  ---
197
159
 
@@ -231,20 +193,20 @@ client := aun.NewAUNClient(aid)
231
193
 
232
194
  | Python | TS/JS | Go | 说明 |
233
195
  |--------|-------|----|------|
234
- | `load_identity(aid)` | `loadIdentity(aid)` | `LoadIdentity(aid)` | 在 `no_identity` / `closed` 状态加载身份 |
235
- | `state` | `state` | `ConnectionState()` | 九态公开状态 |
236
- | `gateway_url` | `gatewayUrl` | `GetGatewayURL()` | 当前连接的 Gateway URL(只读,自动发现,连接前为空) |
237
- | `get_access_token()` | `getAccessToken()` | `GetAccessToken()` | 同步读取后台刷新任务维护的当前 access token;不发起认证、刷新或网络请求;无 token 时分别返回 `None` / `null` / 空字符串 |
238
- | `current_aid` | `currentAid` | `CurrentAID()` | 当前 AID 对象 |
196
+ | `load_identity(aid)` | `loadIdentity(aid)` | `LoadIdentity(aid)` | 在 `no_identity` / `closed` 状态加载身份 |
197
+ | `state` | `state` | `ConnectionState()` | 九态公开状态 |
198
+ | `gateway_url` | `gatewayUrl` | `GetGatewayURL()` | 当前连接的 Gateway URL(只读,自动发现,连接前为空) |
199
+ | `get_access_token()` | `getAccessToken()` | `GetAccessToken()` | 同步读取后台刷新任务维护的当前 access token;不发起认证、刷新或网络请求;无 token 时分别返回 `None` / `null` / 空字符串 |
200
+ | `current_aid` | `currentAid` | `CurrentAID()` | 当前 AID 对象 |
239
201
  | `aid` | `aid` | `AID()` | 当前 AID 字符串 |
240
202
  | `has_identity` | `hasIdentity` | `HasIdentity()` | 是否已加载身份 |
241
203
  | `can_sign` | `canSign` | `CanSign()` | 是否可签名 |
242
204
  | `can_connect` | `canConnect` | `CanConnect()` | 是否可连接 |
243
205
  | `can_send` / `is_ready` | `canSend` / `isReady` | `CanSend()` / `IsReady()` | 是否可发送 |
244
- | `is_online` | `isOnline` | `IsOnline()` | 是否处于在线或重连相关状态 |
245
- | `is_closed` | `isClosed` | `IsClosed()` | 是否已关闭 |
246
-
247
- `get_access_token()` / `getAccessToken()` / `GetAccessToken()` 面向需要自行发起 HTTP 数据面请求的应用。返回值是调用时刻的缓存快照,不是可长期持有的凭据:应在每次需要 AID Bearer 的请求前重新读取,不能持续复用 `authenticate()` 首次返回的字符串。getter 本身不会主动刷新,也不独立检查 token 是否临近过期;其有效性依赖客户端后台刷新任务正常运行。返回空值时应先完成身份加载和认证/连接,且不得发送空的 `Authorization` 头。Storage VFS 和 Group FS 等 SDK 门面会自行编排其数据面请求,普通应用优先使用门面。Bearer、资源域 ticket、跨 issuer 下载和 401/403 重试边界见 [Storage RPC 手册:数据面 HTTP 下载流程](09-storage-rpc-manual.md#数据面-http-下载流程)。
206
+ | `is_online` | `isOnline` | `IsOnline()` | 是否处于在线或重连相关状态 |
207
+ | `is_closed` | `isClosed` | `IsClosed()` | 是否已关闭 |
208
+
209
+ `get_access_token()` / `getAccessToken()` / `GetAccessToken()` 面向需要自行发起 HTTP 数据面请求的应用。返回值是调用时刻的缓存快照,不是可长期持有的凭据:应在每次需要 AID Bearer 的请求前重新读取,不能持续复用 `authenticate()` 首次返回的字符串。getter 本身不会主动刷新,也不独立检查 token 是否临近过期;其有效性依赖客户端后台刷新任务正常运行。返回空值时应先完成身份加载和认证/连接,且不得发送空的 `Authorization` 头。Storage VFS 和 Group FS 等 SDK 门面会自行编排其数据面请求,普通应用优先使用门面。Bearer、资源域 ticket、跨 issuer 下载和 401/403 重试边界见 [Storage RPC 手册:数据面 HTTP 下载流程](09-storage-rpc-manual.md#数据面-http-下载流程)。
248
210
 
249
211
  状态值:
250
212
 
@@ -258,35 +220,35 @@ retry_backoff / reconnecting / connection_failed / closed
258
220
  Python:
259
221
 
260
222
  ```python
261
- auth = await client.authenticate()
262
- await client.connect({"auto_reconnect": True})
223
+ auth = await client.authenticate()
224
+ await client.connect({"auto_reconnect": True})
263
225
  await client.disconnect()
264
226
  await client.close()
265
227
  ```
266
228
 
267
229
  说明:
268
230
 
269
- - `authenticate()` 只取 token,不建立业务会话。
270
- - `connect()` 可从 `standby` 自动认证并进入 `ready`。
271
- - `disconnect()` 断开当前传输连接,对象仍可重新连接。
272
- - `close()` 关闭连接和后台任务;之后只能重新加载身份再复用。
231
+ - `authenticate()` 只取 token,不建立业务会话。
232
+ - `connect()` 可从 `standby` 自动认证并进入 `ready`。
233
+ - `disconnect()` 断开当前传输连接,对象仍可重新连接。
234
+ - `close()` 关闭连接和后台任务;之后只能重新加载身份再复用。
273
235
 
274
236
  四个 SDK 的公开构造入口均已对齐为“无参或 AID 对象”。调试、TLS、根证书、device_id、slot_id 等配置由 `AIDStore` 传递到 AID,再由 `AUNClient` 继承;连接级选项只传给 `connect()`。
275
237
 
276
238
  ### RPC
277
239
 
278
240
  ```python
279
- result = await client.call("message.send", {
280
- "to": "bob.agentid.pub",
281
- "device_id": "bob-phone",
282
- "ttl": 300,
283
- "payload": {"type": "text", "text": "hello"},
284
- })
285
- ```
286
-
287
- `message.send` 可选传入目标 `device_id`,非空时只投递该设备;`to_device_id` 仅为兼容别名,规范字段优先。目标无效时发送失败且不会退化为全设备投递。这里的 `device_id` 是接收目标,`message.pull` / `message.ack` 的同名字段则表示当前消费设备。
288
-
289
- `ttl` 单位为秒:缺省或 `<=0` 保持持久化;正数钳制到 1–3600 秒,只存于消息服务内存且不分配持久 `seq`。四个 SDK 自动维护 volatile 游标、去重及 ACK;返回和事件中的 `storage`、`volatile_cursor`、`created_at`、`expires_at` 可用于识别此类消息。
241
+ result = await client.call("message.send", {
242
+ "to": "bob.agentid.pub",
243
+ "device_id": "bob-phone",
244
+ "ttl": 300,
245
+ "payload": {"type": "text", "text": "hello"},
246
+ })
247
+ ```
248
+
249
+ `message.send` 可选传入目标 `device_id`,非空时只投递该设备;`to_device_id` 仅为兼容别名,规范字段优先。目标无效时发送失败且不会退化为全设备投递。这里的 `device_id` 是接收目标,`message.pull` / `message.ack` 的同名字段则表示当前消费设备。
250
+
251
+ `ttl` 单位为秒:缺省或 `<=0` 保持持久化;正数钳制到 1–3600 秒,只存于消息服务内存且不分配持久 `seq`。四个 SDK 自动维护 volatile 游标、去重及 ACK;返回和事件中的 `storage`、`volatile_cursor`、`created_at`、`expires_at` 可用于识别此类消息。
290
252
 
291
253
  常用 meta RPC 直接透传:
292
254
 
@@ -351,14 +313,14 @@ headers = client.get_protected_headers()
351
313
  | Python | TS/JS | Go | 说明 |
352
314
  |--------|-------|----|------|
353
315
  | `AIDStore.upload_agent_md(aid, content=None)` | `store.uploadAgentMd(aid, content?)` | `store.UploadAgentMD(ctx, aid, content...)` | 发布指定本地 AID 的 agent.md |
354
- | `AIDStore.download_agent_md(aid)` | `store.downloadAgentMd(aid)` | `store.DownloadAgentMD(ctx, aid)` | 下载并验签 |
355
- | `AIDStore.check_agent_md(aid)` | `store.checkAgentMd(aid)` | `store.CheckAgentMD(ctx, aid, maxUnsyncedDays...)` | 检查一致性 |
356
-
357
- 说明:
358
-
359
- - agent.md 上传、下载和检查入口都在 `AIDStore`;`AUNClient` 不再暴露上传入口。
360
- - 上传要求目标 AID 已在本地加载且私钥有效;SDK 会对正文签名,并通过 `AuthFlow` 获取或复用该 AID 的 access_token。
361
- - SDK 发起 GET 时只发送 `Accept: text/markdown`,不主动发送 `If-None-Match` / `If-Modified-Since`。如果服务端异常返回 304,本地有内容则复用;无内容时再发一次无条件 GET。
316
+ | `AIDStore.download_agent_md(aid)` | `store.downloadAgentMd(aid)` | `store.DownloadAgentMD(ctx, aid)` | 下载并验签 |
317
+ | `AIDStore.check_agent_md(aid)` | `store.checkAgentMd(aid)` | `store.CheckAgentMD(ctx, aid, maxUnsyncedDays...)` | 检查一致性 |
318
+
319
+ 说明:
320
+
321
+ - agent.md 上传、下载和检查入口都在 `AIDStore`;`AUNClient` 不再暴露上传入口。
322
+ - 上传要求目标 AID 已在本地加载且私钥有效;SDK 会对正文签名,并通过 `AuthFlow` 获取或复用该 AID 的 access_token。
323
+ - SDK 发起 GET 时只发送 `Accept: text/markdown`,不主动发送 `If-None-Match` / `If-Modified-Since`。如果服务端异常返回 304,本地有内容则复用;无内容时再发一次无条件 GET。
362
324
  - SDK 会自动从 Gateway `_meta.agent_md_etags` 和信封 `agent_md` 观察远端版本;`requester`、`peer`、`group` 是标准角色键,`receiver`、`target`、`to`、`sender`、`from` 是兼容别名。
363
325
  - `Accept: text/markdown` 与 agent.md 的 YAML frontmatter + Markdown 格式兼容;agent.md 仍是 Markdown 媒体类型上的结构化约定。
364
326
 
@@ -372,39 +334,41 @@ headers = client.get_protected_headers()
372
334
 
373
335
  | 能力 | Python | TS/JS | Go | 说明 |
374
336
  |------|--------|-------|----|------|
375
- | Storage VFS | `client.storage` | `client.storage` | `client.Storage()` | 类 POSIX 文件操作;上传自动选择 inline / session / 秒传并统一 Content-Type,下载自动选择 inline / ticket;支持 `touch`、`find`、`du`、`df`、ACL/token/软链/挂载门面 |
337
+ | Storage VFS | `client.storage` | `client.storage` | `client.Storage()` | 类 POSIX 文件操作;上传自动选择 inline / session / 秒传并统一 Content-Type,下载自动选择 inline / ticket;支持 `touch`、`find`、`du`、`df`、ACL/token/软链/挂载门面 |
376
338
  | Collab | `client.collab` | `client.collab` | `client.Collab()` | 版本化文档、标签、`gc` / `reflog` / `revert` |
377
339
  | Group FS | `client.group.fs` | `client.group.fs` | `client.Group().FS()` | POSIX 风格群文件系统;`ls/find/stat/lstat/mkdir/rm/cp/mv/df/mount/umount`,以及 `set_acl/remove_acl/get_acl/list_acl` 角色 ACL 门面,上传下载数据面由 SDK 编排 |
378
340
 
379
- 四个 SDK 的 Storage VFS 与 Group FS 上传门面在未显式指定 `content_type` / `contentType` 时,先按实际字节识别 PNG、JPEG、GIF、BMP、WebP、PDF、gzip、ZIP、MP4、JSON、HTML 和 UTF-8 文本,再回退文件扩展名及 `application/octet-stream`。session 上传会把同一 MIME 值用于 create session、HTTP PUT 的 `Content-Type` 和 complete;session 响应中的其它 header 会保留,Content-Type 会替换为该 MIME。OSS 预签名 URL 会校验签名头,应用绕过 VFS 时缺少或改写该请求头可能直接收到 OSS 403。
380
-
381
- 群文件系统路径统一使用 `group_aid:/...`,成员数据区使用 `group_aid:/memberdata/{member_ref}/...`。SDK 不拼接真实 storage 路径,`memberdata` 到成员 `group_data/{group_aid}` 的映射只在服务端完成。群自有区写入允许当前 `group_aid` 证书签名;成员角色中 `owner/admin` 默认可写群自有区,`member` 默认不可写。`owner/admin` 可通过 `group.fs.set_acl` 对特定业务目录授予 `role:member` 的 `rw` 权限,用于后续群协作场景;`rw` 只允许创建/写入,不授予删除、移动、重命名权限。`rwd` 是 storage 内部权限位,SDK/RPC 对外按 POSIX 视图显示为 `rwx`,不给删除/移动/重命名权限时只使用 `rw`。`.group/` 是系统控制目录,默认给 `owner/admin` 写权限,用于群公告、群规则、入群要求附件,不能向 `role:member` 授权。老群如果缺少 `.group/` 默认 ACL,group 服务会在该群首次被 RPC 访问时 best-effort 触发 namespace/ACL lazy repair;ACL 同步全部成功后才记录本进程已检查,失败会在后续访问继续重试。JS 浏览器版上传中 `string` 默认表示文本内容,Node 本地路径需显式 `sourceType: "path"`、`localPath: true` 或 `local:` 前缀;Python/TS/Go 默认把 `string` 当本地路径。
341
+ 四个 SDK 的 Storage VFS 与 Group FS 上传门面在未显式指定 `content_type` / `contentType` 时,先按实际字节识别 PNG、JPEG、GIF、BMP、WebP、PDF、gzip、ZIP、MP4、JSON、HTML 和 UTF-8 文本,再回退文件扩展名及 `application/octet-stream`。session 上传会把同一 MIME 值用于 create session、HTTP PUT 的 `Content-Type` 和 complete;session 响应中的其它 header 会保留,Content-Type 会替换为该 MIME。OSS 预签名 URL 会校验签名头,应用绕过 VFS 时缺少或改写该请求头可能直接收到 OSS 403。
342
+
343
+ 群文件系统路径统一使用 `group_aid:/...`,成员数据区使用 `group_aid:/memberdata/{member_ref}/...`。SDK 不拼接真实 storage 路径,`memberdata` 到成员 `group_data/{group_aid}` 的映射只在服务端完成。群自有区写入允许当前 `group_aid` 证书签名;成员角色中 `owner/admin` 默认可写群自有区,`member` 默认不可写。`owner/admin` 可通过 `group.fs.set_acl` 对特定业务目录授予 `role:member` 的 `rw` 权限,用于后续群协作场景;`rw` 只允许创建/写入,不授予删除、移动、重命名权限。`rwd` 是 storage 内部权限位,SDK/RPC 对外按 POSIX 视图显示为 `rwx`,不给删除/移动/重命名权限时只使用 `rw`。`.group/` 是系统控制目录,默认给 `owner/admin` 写权限,用于群公告、群规则、入群要求附件,不能向 `role:member` 授权。老群如果缺少 `.group/` 默认 ACL,group 服务会在该群首次被 RPC 访问时 best-effort 触发 namespace/ACL lazy repair;ACL 同步全部成功后才记录本进程已检查,失败会在后续访问继续重试。JS 浏览器版上传中 `string` 默认表示文本内容,Node 本地路径需显式 `sourceType: "path"`、`localPath: true` 或 `local:` 前缀;Python/TS/Go 默认把 `string` 当本地路径。
382
344
 
383
345
  ### 消息与群组门面
384
346
 
385
347
  | 能力 | Python | TS/JS | Go | 说明 |
386
348
  |------|--------|-------|----|------|
387
- | Message | `client.message` | `client.message` | `client.Message()` | 消息便利方法:`send()`、`pull()`、`history()`、`ack()`、`recall()`、`queryOnline()` |
349
+ | Message | `client.message` | `client.message` | `client.Message()` | 消息便利方法:`send()`、`pull()`、`history()`、`ack()`、`read()`、`read_state/readState/ReadState`、`recall()`、`queryOnline()` |
388
350
  | Message Thought | `client.message.thought` | `client.message.thought` | `client.Message().Thought()` | P2P 思考内容:`put()`、`get()`(不持久化、不分配 seq、强制 E2EE) |
389
- | Group | `client.group` | `client.group` | `client.Group()` | 群组便利方法:`create()`、`send()`、`pull()`、`history()`、`ack()`、群管理方法、群查询方法、群设置便利方法 |
390
- | Group Thought | `client.group.thought` | `client.group.thought` | `client.Group().Thought()` | 群思考内容:`put()`、`get()`(不持久化、不分配 seq、强制 E2EE) |
391
- | Offline Push | `client.push` | — | — | Python SDK:登记/注销当前安装的 `push_notify_aid`,Push Server 处理批次后 ACK |
392
- | Stream | `client.stream` | `client.stream` | — | 流式数据:`createStream()`、`sendChunk()`、`endStream()`、`subscribeStream()` |
393
-
394
- `message.history` 和 `group.history` 使用 `before_seq` 排他上界向左翻页,下一页直接使用响应的 `next_before_seq`。History 只读,不推进实时 A/T/H、不 ACK、不触发实时消息事件。实时 Push 的最新页同步完全由 SDK 内部自动执行,再按 A/T/H 决定是否补一页 Forward;没有需要应用调用的 Tail 方法或参数。
395
-
396
- 消息回调只提供页级语义:单个 Tail/Forward 响应页内按 seq 升序、同 seq 最多一次;跨 Push/Tail/Forward 页可能重复或乱序。A/T/H 不能用于判断某条消息是否已经回调。全局去重与排序不是 SDK 业务逻辑保证;应用仓库应以 `(namespace, message_id)` 做唯一键,并按 `seq` 排序。SDK 的进程内折叠仅是 best-effort 优化。
397
-
398
- **群成员管理与状态方法**:
399
-
400
- | 语义 | Python | TS/JS | Go |
401
- |------|--------|-------|----|
402
- | 屏蔽/取消屏蔽 | `block()` / `unblock()` | `block()` / `unblock()` | `Block()` / `Unblock()` |
403
- | 获取屏蔽列表 | `get_blocklist()` | `getBlocklist()` | `GetBlocklist()` |
404
- | 上报自己的群内状态 | `report_status()` | `reportStatus()` | `ReportStatus()` |
405
- | 获取所有成员状态 | `get_member_statuses()` | `getMemberStatuses()` | `GetMemberStatuses()` |
406
-
407
- 屏蔽与禁言相互独立且可以同时设置。屏蔽成员仍可向群发送消息,但不接收群消息;禁言成员不能向群发送消息,但仍可接收。群主转让是两阶段流程:旧群主先生成授权并调用 `group.transfer_owner`,新群主 SDK 收到 `owner_transfer_rekey_pending` 后生成新群密钥并调用 `group.complete_transfer`。四个 SDK 会在新群主在线或重连恢复时自动尝试第二阶段,也保留 `completeTransfer`(Python 为 `complete_transfer`,Go 为 `CompleteTransfer`)供显式恢复;发起调用返回 `pending_rekey`,不表示转让已经完成。
351
+ | Group | `client.group` | `client.group` | `client.Group()` | 群组便利方法:`create()`、`send()`、`pull()`、`history()`、`ack()`、`read()`、`read_state/readState/ReadState`、群管理方法、群查询方法、群设置便利方法 |
352
+ | Group Thought | `client.group.thought` | `client.group.thought` | `client.Group().Thought()` | 群思考内容:`put()`、`get()`(不持久化、不分配 seq、强制 E2EE) |
353
+ | Offline Push | `client.push` | — | — | Python SDK:登记/注销当前安装的 `push_notify_aid`,Push Server 处理批次后 ACK |
354
+ | Stream | `client.stream` | `client.stream` | — | 流式数据:`createStream()`、`sendChunk()`、`endStream()`、`subscribeStream()` |
355
+
356
+ 业务已读与投递 ACK 独立。Python 使用 `read/read_state`,TS/JS 使用 `read/readState`,Go 使用 `Read/ReadState`;四个已读门面保留显式 `None/null/nil` 字段交服务端校验。只有省略 `states` 时兼容顶层单条,显式空/null 批次拒绝;写入回执不是共享汇总。完整字段与事件契约见 [Message RPC](09-message-rpc-manual.md#messageread) 和 [Group RPC](09-group-rpc-manual.md#groupread)。
357
+
358
+ `message.history` 和 `group.history` 使用 `before_seq` 排他上界向左翻页,下一页直接使用响应的 `next_before_seq`。History 只读,不推进实时 A/T/H、不 ACK、不触发实时消息事件。实时 Push 的最新页同步完全由 SDK 内部自动执行,再按 A/T/H 决定是否补一页 Forward;没有需要应用调用的 Tail 方法或参数。
359
+
360
+ 消息回调只提供页级语义:单个 Tail/Forward 响应页内按 seq 升序、同 seq 最多一次;跨 Push/Tail/Forward 页可能重复或乱序。A/T/H 不能用于判断某条消息是否已经回调。全局去重与排序不是 SDK 业务逻辑保证;应用仓库应以 `(namespace, message_id)` 做唯一键,并按 `seq` 排序。SDK 的进程内折叠仅是 best-effort 优化。
361
+
362
+ **群成员管理与状态方法**:
363
+
364
+ | 语义 | Python | TS/JS | Go |
365
+ |------|--------|-------|----|
366
+ | 屏蔽/取消屏蔽 | `block()` / `unblock()` | `block()` / `unblock()` | `Block()` / `Unblock()` |
367
+ | 获取屏蔽列表 | `get_blocklist()` | `getBlocklist()` | `GetBlocklist()` |
368
+ | 上报自己的群内状态 | `report_status()` | `reportStatus()` | `ReportStatus()` |
369
+ | 获取所有成员状态 | `get_member_statuses()` | `getMemberStatuses()` | `GetMemberStatuses()` |
370
+
371
+ 屏蔽与禁言相互独立且可以同时设置。屏蔽成员仍可向群发送消息,但不接收群消息;禁言成员不能向群发送消息,但仍可接收。群主转让是两阶段流程:旧群主先生成授权并调用 `group.transfer_owner`,新群主 SDK 收到 `owner_transfer_rekey_pending` 后生成新群密钥并调用 `group.complete_transfer`。四个 SDK 会在新群主在线或重连恢复时自动尝试第二阶段,也保留 `completeTransfer`(Python 为 `complete_transfer`,Go 为 `CompleteTransfer`)供显式恢复;发起调用返回 `pending_rekey`,不表示转让已经完成。
408
372
 
409
373
  **群查询方法**:`GroupFacade` 提供三个查询方法,适用不同场景:
410
374
 
@@ -412,20 +376,20 @@ headers = client.get_protected_headers()
412
376
  - `getInfo()` — 查询群组信息(扁平化格式,提升常用字段到顶层),**推荐外部使用**
413
377
  - `info()` — 查询群组详细信息(带权限控制,非成员只能看公开群,成员能看 seq/epoch 等运行时状态)
414
378
 
415
- **群设置便利方法**:`GroupFacade` 提供向后兼容的便利方法:
416
-
417
- - `getAnnouncement()` / `updateAnnouncement()` — 群公告
418
- - `getRules()` / `updateRules()` — 群规则
419
- - `getJoinRequirements()` / `updateJoinRequirements()` — 入群要求
420
- - `getSettingWithIndex()` / `updateSettingWithIndex()` — 通用文档型 indexed setting(Python 为 `get_setting_with_index()` / `update_setting_with_index()`,Go 为 `GetSettingWithIndex()` / `UpdateSettingWithIndex()`)
421
-
422
- 读取方法优先返回 SDK 本地缓存;本地没有对应值时才读取相应 settings 做初始化。便利读取从服务端拿到 canonical `group_aid` 后,会同时以 canonical `group_aid` 和本次入参 `group_id` 写入 settings cache,避免 legacy/base `group_id` 下一次读取直接 cache miss。即使 `checkGroupIndex` 观察到远端 etag 与本地 etag 不一致,`getAnnouncement()` / `getRules()` / `getJoinRequirements()` / `getSettingWithIndex()` 也不会自动拉取远端版本覆盖本地缓存。`updateAnnouncement()` / `updateRules()` / `updateJoinRequirements()` / `updateSettingWithIndex()` 属于 indexed 写入,内部会调用 `updateGroupIndex` 生成签名 `group.index` 并带 `expected_index_etag` CAS 提交。
423
-
424
- `getSettingWithIndex()` / `updateSettingWithIndex()` 比三对预定义方法多一个 `keyName`(Python 可传 `key_name`,Go 同时接受 `keyName` / `key_name`)。SDK 会生成 `{keyName}.content` 和 `{keyName}.attachments` 两个 settings key;`keyName` 只能是受控文档名(`^[A-Za-z][A-Za-z0-9_-]{0,63}$`,且不能使用 `join` 等保留前缀)。`getRules()` / `updateRules()` 与 `getAnnouncement()` / `updateAnnouncement()` 是该通用方法在 `rules`、`announcement` 上的薄封装;`getJoinRequirements()` / `updateJoinRequirements()` 保持结构化 schema,不改成 `join.content`。
425
-
426
- 这些 `update*` 便利方法只更新群设置元数据:`updateRules()` / `updateAnnouncement()` / `updateSettingWithIndex()` 写入 `*.content` 与 `*.attachments`,`updateJoinRequirements()` 写入 `join.mode` / `join.question` / `join.auto_approve_patterns` / `join.max_pending` / `join.attachments`。`attachments` 只保存稳定引用,附件实体应先上传到 group.fs 群自有区,推荐路径为 `group_aid:/.group/attachments/{rules|announcement|join|<keyName>}/...`。
427
-
428
- **Group Index 高级同步方法**:`group.index` 是 SDK 内部签名 manifest,用于记录群公告、群规则、入群要求及附件稳定引用的版本。SDK 观察 `_meta.group_indexes` 后只记录远端 etag;etag 不一致只表示本地与观察到的远端版本不同,可能是远端更新,也可能是本地有未提交修改。应用层需要显式选择 pull 远端或 push 本地。
379
+ **群设置便利方法**:`GroupFacade` 提供向后兼容的便利方法:
380
+
381
+ - `getAnnouncement()` / `updateAnnouncement()` — 群公告
382
+ - `getRules()` / `updateRules()` — 群规则
383
+ - `getJoinRequirements()` / `updateJoinRequirements()` — 入群要求
384
+ - `getSettingWithIndex()` / `updateSettingWithIndex()` — 通用文档型 indexed setting(Python 为 `get_setting_with_index()` / `update_setting_with_index()`,Go 为 `GetSettingWithIndex()` / `UpdateSettingWithIndex()`)
385
+
386
+ 读取方法优先返回 SDK 本地缓存;本地没有对应值时才读取相应 settings 做初始化。便利读取从服务端拿到 canonical `group_aid` 后,会同时以 canonical `group_aid` 和本次入参 `group_id` 写入 settings cache,避免 legacy/base `group_id` 下一次读取直接 cache miss。即使 `checkGroupIndex` 观察到远端 etag 与本地 etag 不一致,`getAnnouncement()` / `getRules()` / `getJoinRequirements()` / `getSettingWithIndex()` 也不会自动拉取远端版本覆盖本地缓存。`updateAnnouncement()` / `updateRules()` / `updateJoinRequirements()` / `updateSettingWithIndex()` 属于 indexed 写入,内部会调用 `updateGroupIndex` 生成签名 `group.index` 并带 `expected_index_etag` CAS 提交。
387
+
388
+ `getSettingWithIndex()` / `updateSettingWithIndex()` 比三对预定义方法多一个 `keyName`(Python 可传 `key_name`,Go 同时接受 `keyName` / `key_name`)。SDK 会生成 `{keyName}.content` 和 `{keyName}.attachments` 两个 settings key;`keyName` 只能是受控文档名(`^[A-Za-z][A-Za-z0-9_-]{0,63}$`,且不能使用 `join` 等保留前缀)。`getRules()` / `updateRules()` 与 `getAnnouncement()` / `updateAnnouncement()` 是该通用方法在 `rules`、`announcement` 上的薄封装;`getJoinRequirements()` / `updateJoinRequirements()` 保持结构化 schema,不改成 `join.content`。
389
+
390
+ 这些 `update*` 便利方法只更新群设置元数据:`updateRules()` / `updateAnnouncement()` / `updateSettingWithIndex()` 写入 `*.content` 与 `*.attachments`,`updateJoinRequirements()` 写入 `join.mode` / `join.question` / `join.auto_approve_patterns` / `join.max_pending` / `join.attachments`。`attachments` 只保存稳定引用,附件实体应先上传到 group.fs 群自有区,推荐路径为 `group_aid:/.group/attachments/{rules|announcement|join|<keyName>}/...`。
391
+
392
+ **Group Index 高级同步方法**:`group.index` 是 SDK 内部签名 manifest,用于记录群公告、群规则、入群要求及附件稳定引用的版本。SDK 观察 `_meta.group_indexes` 后只记录远端 etag;etag 不一致只表示本地与观察到的远端版本不同,可能是远端更新,也可能是本地有未提交修改。应用层需要显式选择 pull 远端或 push 本地。
429
393
 
430
394
  | 语义 | Python | TS/JS | Go | 说明 |
431
395
  |------|--------|-------|----|------|
@@ -433,7 +397,7 @@ headers = client.get_protected_headers()
433
397
  | 显式 pull 远端 index | `client.group.get_group_index({...})` | `client.group.getGroupIndex({...})` | `client.Group().GetGroupIndex(ctx, params)` | 调用 `group.get_settings(keys=["group.index"])` 摘取 manifest,并按 entry etag 只拉取变化的 db settings 写入本地缓存 |
434
398
  | 显式 push 本地 indexed settings + index | `client.group.update_group_index({...})` | `client.group.updateGroupIndex({...})` | `client.Group().UpdateGroupIndex(ctx, params)` | 先读取当前 index 得到 `expected_index_etag`,在远端基线上合并本地变更,生成签名 `group.index` 后 CAS push |
435
399
 
436
- `getGroupIndex` pull 验签成功后会持久化本地视图。Python / TypeScript(Node) / Go 使用 `{aun_path}/AIDs/{local_aid}/groups/{group_aid}/index.jsonl` 保存签名 `group.index.body` 原文,并用同目录 `group-index-cache.json` 保存 `local_etag`、`remote_meta`、`settings`、`entry_etags` 等 cache envelope。浏览器 JavaScript 使用 IndexedDB `group_index_cache` store 的等价记录,按 `local_aid + group_aid` 隔离。普通便利读取可额外写入本次请求 `group_id` 的 settings cache alias;签名正文和 `getGroupIndex` 视图仍以 canonical `group_aid` 为准。不要使用 `{aun_path}/AIDs/{group_aid}/`,也不要使用旧单文件 `group-index.json`。
400
+ `getGroupIndex` pull 验签成功后会持久化本地视图。Python / TypeScript(Node) / Go 使用 `{aun_path}/AIDs/{local_aid}/groups/{group_aid}/index.jsonl` 保存签名 `group.index.body` 原文,并用同目录 `group-index-cache.json` 保存 `local_etag`、`remote_meta`、`settings`、`entry_etags` 等 cache envelope。浏览器 JavaScript 使用 IndexedDB `group_index_cache` store 的等价记录,按 `local_aid + group_aid` 隔离。普通便利读取可额外写入本次请求 `group_id` 的 settings cache alias;签名正文和 `getGroupIndex` 视图仍以 canonical `group_aid` 为准。不要使用 `{aun_path}/AIDs/{group_aid}/`,也不要使用旧单文件 `group-index.json`。
437
401
 
438
402
  #### checkGroupIndex — 检查 index 同步状态
439
403
 
@@ -595,14 +559,14 @@ fmt.Printf("拉取成功,etag: %s\n", result["meta"].(map[string]any)["etag"])
595
559
  | `last_modified` | int | ❌ | 时间戳毫秒(默认 `Date.now()` / `time.time()*1000`) |
596
560
  | `max_attempts` | int | ❌ | CAS 冲突最大重试次数(默认 2) |
597
561
 
598
- **支持的 indexed settings keys**:
599
-
600
- - `rules.content` / `rules.attachments` — 群规则及附件
601
- - `announcement.content` / `announcement.attachments` — 群公告及附件
602
- - `join.mode` / `join.question` / `join.auto_approve_patterns` / `join.max_pending` / `join.attachments` — 入群要求及附件
603
- - `{keyName}.content` / `{keyName}.attachments` — 通用文档型 indexed setting;`keyName` 需满足 `^[A-Za-z][A-Za-z0-9_-]{0,63}$`,且不能使用 `join` 等保留前缀
604
-
605
- **返回值**:
562
+ **支持的 indexed settings keys**:
563
+
564
+ - `rules.content` / `rules.attachments` — 群规则及附件
565
+ - `announcement.content` / `announcement.attachments` — 群公告及附件
566
+ - `join.mode` / `join.question` / `join.auto_approve_patterns` / `join.max_pending` / `join.attachments` — 入群要求及附件
567
+ - `{keyName}.content` / `{keyName}.attachments` — 通用文档型 indexed setting;`keyName` 需满足 `^[A-Za-z][A-Za-z0-9_-]{0,63}$`,且不能使用 `join` 等保留前缀
568
+
569
+ **返回值**:
606
570
 
607
571
  ```python
608
572
  {
@@ -671,16 +635,16 @@ console.log(`推送成功,新 etag: ${result._meta.group_indexes['g-team.agent
671
635
 
672
636
  ```go
673
637
  // Go - 更新入群要求
674
- result, err := client.Group().UpdateGroupIndex(ctx, map[string]any{
675
- "group_id": "g-team.agentid.pub",
676
- "settings": map[string]any{
677
- "join.mode": "approval",
678
- "join.question": "你是如何知道本群的?",
679
- "join.attachments": []any{
680
- map[string]any{"type": "group.fs", "path": "/.group/attachments/join/guide.pdf"},
681
- },
682
- },
683
- })
638
+ result, err := client.Group().UpdateGroupIndex(ctx, map[string]any{
639
+ "group_id": "g-team.agentid.pub",
640
+ "settings": map[string]any{
641
+ "join.mode": "approval",
642
+ "join.question": "你是如何知道本群的?",
643
+ "join.attachments": []any{
644
+ map[string]any{"type": "group.fs", "path": "/.group/attachments/join/guide.pdf"},
645
+ },
646
+ },
647
+ })
684
648
  if err != nil {
685
649
  log.Fatal(err)
686
650
  }
@@ -692,41 +656,41 @@ fmt.Printf("推送成功\n")
692
656
  1. **权限要求**:写入 indexed settings 需要 admin 及以上权限
693
657
  2. **签名算法限制**:当前版本仅支持 ECDSA-P256-SHA256,使用其他算法的 AID 无法签名
694
658
  3. **CAS 冲突策略**:默认重试 2 次,高并发场景建议增加 `max_attempts`
695
- 4. **`signer` 必须是当前连接身份**:服务端强制校验 `signed_by == actor_aid`,传入其他 AID 会被拒绝
696
- 5. **附件字段只存引用**:`updateRules` / `updateAnnouncement` / `updateSettingWithIndex` 传入 `content` 和 `attachments` 元数据引用;`updateJoinRequirements` 传入结构化入群字段和 `attachments` 元数据引用。附件实体先上传到群自有区,推荐路径为 `group_aid:/.group/attachments/{rules|announcement|join|<keyName>}/...`。群自有区默认允许 `owner/admin` 写入,`member` 默认不可写;`.group/` 不允许授予 `role:member` 写权限。
697
-
698
- ---
699
-
700
- ### 离线推送门面(Python)
701
-
702
- 应用启动时推荐把推送代理和 opaque token 交给 `connect()`:
703
-
704
- ```python
705
- await client.connect({
706
- "push_notify_aid": "push.example.com",
707
- "push_token": token,
708
- })
709
- ```
710
-
711
- SDK 先建立 AUN 会话,再调用 `push.register` 登记当前安装。`push_notify_aid` 是 SDK 公共名称,线上 RPC 字段仍是冻结契约中的 `push_server_aid`;它不会被透传给 `auth.connect`。
712
-
713
- 运行期间更换 token 或停止推送时使用 facade:
714
-
715
- ```python
716
- await client.push.register(push_notify_aid="push.example.com", push_token=new_token)
717
- await client.push.unregister()
718
- ```
719
-
720
- - `register()` 可附带 `provider`、`app_id`;身份范围始终由 Gateway 注入的当前 `aid/device_id/slot_id` 决定,调用方不能伪造。
721
- - `unregister()` 幂等禁用当前安装,不删除历史 delivery。
722
- - `ack(batch_id, status)` 只供 Push Server 实现使用;V1 status 仅允许 `accepted`、`retryable_failure`、`permanent_failure`。
723
- - 本轮只修改 Python SDK;Go、TypeScript、JavaScript 尚无专用 Push facade。
724
-
725
- 协议与可靠性边界见 `docs/protocol/15-离线推送通知协议.md`。
726
-
727
- ---
728
-
729
- ## ServiceProxyClient
659
+ 4. **`signer` 必须是当前连接身份**:服务端强制校验 `signed_by == actor_aid`,传入其他 AID 会被拒绝
660
+ 5. **附件字段只存引用**:`updateRules` / `updateAnnouncement` / `updateSettingWithIndex` 传入 `content` 和 `attachments` 元数据引用;`updateJoinRequirements` 传入结构化入群字段和 `attachments` 元数据引用。附件实体先上传到群自有区,推荐路径为 `group_aid:/.group/attachments/{rules|announcement|join|<keyName>}/...`。群自有区默认允许 `owner/admin` 写入,`member` 默认不可写;`.group/` 不允许授予 `role:member` 写权限。
661
+
662
+ ---
663
+
664
+ ### 离线推送门面(Python)
665
+
666
+ 应用启动时推荐把推送代理和 opaque token 交给 `connect()`:
667
+
668
+ ```python
669
+ await client.connect({
670
+ "push_notify_aid": "push.example.com",
671
+ "push_token": token,
672
+ })
673
+ ```
674
+
675
+ SDK 先建立 AUN 会话,再调用 `push.register` 登记当前安装。`push_notify_aid` 是 SDK 公共名称,线上 RPC 字段仍是冻结契约中的 `push_server_aid`;它不会被透传给 `auth.connect`。
676
+
677
+ 运行期间更换 token 或停止推送时使用 facade:
678
+
679
+ ```python
680
+ await client.push.register(push_notify_aid="push.example.com", push_token=new_token)
681
+ await client.push.unregister()
682
+ ```
683
+
684
+ - `register()` 可附带 `provider`、`app_id`;身份范围始终由 Gateway 注入的当前 `aid/device_id/slot_id` 决定,调用方不能伪造。
685
+ - `unregister()` 幂等禁用当前安装,不删除历史 delivery。
686
+ - `ack(batch_id, status)` 只供 Push Server 实现使用;V1 status 仅允许 `accepted`、`retryable_failure`、`permanent_failure`。
687
+ - 本轮只修改 Python SDK;Go、TypeScript、JavaScript 尚无专用 Push facade。
688
+
689
+ 协议与可靠性边界见 `docs/protocol/15-离线推送通知协议.md`。
690
+
691
+ ---
692
+
693
+ ## ServiceProxyClient
730
694
 
731
695
  Service Proxy 用于 provider 通过 AUN 身份暴露本地 HTTP / WebSocket 服务。当前公开封装在 Python SDK 的 `ServiceProxyClient` 中;其它语言可以按 [09-proxy-rpc-manual.md](09-proxy-rpc-manual.md) 直接实现同等控制面和隧道消息。
732
696
 
@@ -791,113 +755,116 @@ sub.unsubscribe()
791
755
  | `connection.error` | 连接或重连错误 |
792
756
  | `token.refreshed` | token 刷新完成 |
793
757
  | `token.refresh_exhausted` | refresh_token 缺失、过期或刷新链耗尽,SDK 已清理本地 token 并等待重新登录 |
794
- | `message.received` | 收到 P2P 消息 |
795
- | `message.ack` | 消息 ack |
796
- | `message.undecryptable` | P2P E2EE 解密失败 |
797
- | `group.message_created` | 收到群消息 |
798
- | `group.changed` | 群组事件 |
799
- | `group.message_recalled` | 群消息撤回 |
800
- | `group.message_undecryptable` | 群 E2EE 解密失败 |
801
- | `sync.started` / `sync.progress` / `sync.stopped` | Pull 同步生命周期 |
802
- | `delivery.changed` | SDK 本地批量交付完成通知,用于合并应用层 UI 刷新 |
803
- | `push.offline_message` | Push Server 收到离线唤醒批次;普通终端无需订阅 |
804
- | `storage.object_changed` | Storage 对象变更事件透传 |
805
-
806
- `message.received` 的应用层消息保留 `sender_device_id`,表示发送方设备;需要只回复原设备时,将该值作为下一次 `message.send.device_id`。volatile 消息还保留 `storage="volatile"`、`volatile_cursor`、`created_at`、`expires_at`,不含持久 `seq`;内部 slot 不会暴露给应用。
807
-
808
- ### delivery.changed
809
-
810
- `delivery.changed` 是 Python、Go、JavaScript 和 TypeScript SDK 统一提供的本地事件,用于通知应用层一批消息已经完成交付,可以从业务仓库刷新相关会话 UI。它不替代 `message.received` / `group.message_created`:应用仍应逐条处理和持久化原消息事件,再把 `delivery.changed` 作为批量刷新信号。
811
-
812
- 该事件由 SDK 本地 dispatcher 发布,不是 Gateway 推送事件,不属于 JSON-RPC `event/...` 协议;裸 WebSocket 客户端不会收到它。
813
-
814
- **Payload**:
815
-
816
- ```json
817
- {
818
- "trigger": "pull_drained",
819
- "changes": [
820
- {"namespace": "group:team.agentid.pub", "delivered_count": 50},
821
- {"namespace": "p2p", "delivered_count": 3}
822
- ]
823
- }
824
- ```
825
-
826
- | 字段 | 类型 | 说明 |
827
- |------|------|------|
828
- | `trigger` | string | `pull_drained`、`push` 或 `inline_push` |
829
- | `changes` | array | 本次刷新涉及的 namespace,按 `namespace` 稳定排序且不重复 |
830
- | `changes[].namespace` | string | P2P 固定为 `p2p`;群消息为 `group:<canonical-group-aid>` |
831
- | `changes[].delivered_count` | integer | 本次合并窗口内已发布到应用层的原消息事件数;不是未读数、服务端页大小或最大 seq |
832
-
833
- 触发与折叠规则:
834
-
835
- - `pull_drained`:当前 `AUNClient` 上所有已排队或正在执行的 Pull 工作,包括后续分页、continuation、gap fill 和缺失 Sender IK 的延迟补齐,全部排空后最多发布一次。一次通知可以同时包含 P2P 和多个群 namespace,不会按 Pull 页或单个 namespace 分别通知。
836
- - `push` / `inline_push`:有效实时消息在对应的 `message.received` / `group.message_created` 事件处理完成后通知;同一次有序队列 drain 放行的同类实时消息折叠为一次通知。
837
- - 有效的 `push` / `inline_push` 可以原子吸收尚未通知的 Pull 变更;这些变更不会在后续 `pull_drained` 中再次通知。
838
- - 空 Pull、重复或过滤消息、非法 payload、解密失败,以及未带来有效消息交付的 Head 通知,不发布 `delivery.changed`。
839
-
840
- `delivery.changed` 只表示 UI 刷新时机,不是消息明细或持久化账本。应用仓库仍应按照原消息事件的 `(namespace, message_id)` 做幂等,并按 `seq` 排序。
841
-
842
- ### 消息来源 `source`
843
-
844
- 消息、群消息、群事件、撤回和不可解密事件的 payload 可能带有顶层 `source`,用于区分 SDK 的投递来源:
845
-
846
- | `source` | 含义 |
847
- |----------|------|
848
- | `push` | 普通实时推送 |
849
- | `pull` | Pull、Tail、Forward 或群事件补洞 |
850
- | `inline_push` | 已在连续窗口内直接消费的实时载荷 |
851
- | `pending_retry` | 缺失 Sender IK 后延迟恢复的消息 |
852
-
853
- 直接由应用或无来源内部调用产生的事件不强行添加 `source`。`source` 位于应用事件 payload 顶层,不改变业务 `payload` 内的数据结构;应用应将其视为保留字段。
854
-
855
- ### Pull 同步生命周期
856
-
857
- 四个 SDK 均通过本地 dispatcher 发布以下异步生命周期事件;它们不是 Gateway 的 `event/...` 消息,应用 handler 不会阻塞网络 reader。
858
-
859
- `sync.started` 在本次 Pull 的第一个 namespace 开始时发布一次:
860
-
861
- ```json
862
- {
863
- "run_id": 7,
864
- "source": "pull",
865
- "syncing": true,
866
- "namespaces_pending": 1,
867
- "namespace": "p2p",
868
- "received_total": 0,
869
- "started_at": 1730000000000
870
- }
871
- ```
872
-
873
- 每完成一页消息或群事件的处理、提交和有序投递,发布一次 `sync.progress`。`page_raw_count` 是本页有效原始条数,`page_pulled_count` 是本页实际投递条数;`pulled_count` / `received_total` 是本次 namespace 的累计投递数。服务端返回合法非负整数时透传 `remaining`,并同步设置 `estimated_remaining`;否则仅在协议可推导时提供估算值。
874
-
875
- ```json
876
- {
877
- "run_id": 7,
878
- "source": "pull",
879
- "namespace": "p2p",
880
- "page": 2,
881
- "page_raw_count": 50,
882
- "page_pulled_count": 48,
883
- "pulled_count": 98,
884
- "received_total": 98,
885
- "batch_size": 48,
886
- "has_more": true,
887
- "remaining": 102,
888
- "estimated_remaining": 102
889
- }
890
- ```
891
-
892
- 当所有 namespace、续页、gap fill、ordered drain 和延迟 Sender IK 任务均排空时发布一次 `sync.stopped`。正常终态使用 `reason: "pull_drained"`;异常、取消、断线或连接/身份代次失效使用 `reason: "aborted"`。`sync.stopped` 描述同步生命周期终点,`delivery.changed(trigger="pull_drained")` 只汇总实际投递变化;空 Pull 可以有前者而没有后者。
893
-
894
- ### JS/TS Worker 降级
895
-
896
- JavaScript 浏览器 SDK 的 Worker 是网络隔离增强:`new Worker()` 同步失败、CSP `worker-src` 阻止、Worker 启动后未及时 `open` 或异步崩溃时,SDK 会销毁 Worker 并回退原生 WebSocket。TypeScript Node.js SDK 在 `worker_threads` 不可用、启动超时或 Network Actor 崩溃时回退本地 transport。两种降级均保留异步 dispatcher 和 Pull 生命周期语义,不要求应用改变消息处理代码。
897
-
898
- ---
899
-
900
- ## E2EE 高级 API
758
+ | `message.received` | 收到 P2P 消息 |
759
+ | `message.read_changed` / `group.read_changed` | 同一 AID 共享已读位置提交并前进;owner 必须为当前身份、scope 匹配原事件 |
760
+ | `read_state_sync` | SDK 本地已读补偿完成,含本轮 `owner_aid`、去重 `states` 和逐项 `errors`;连接返回不表示补偿完成 |
761
+ | `message.undecryptable` | P2P E2EE 解密失败 |
762
+ | `group.message_created` | 收到群消息 |
763
+ | `group.changed` | 群组事件 |
764
+ | `group.message_recalled` | 群消息撤回 |
765
+ | `group.message_undecryptable` | 群 E2EE 解密失败 |
766
+ | `sync.started` / `sync.progress` / `sync.stopped` | Pull 同步生命周期 |
767
+ | `delivery.changed` | SDK 本地批量交付完成通知,用于合并应用层 UI 刷新 |
768
+ | `push.offline_message` | Push Server 收到离线唤醒批次;普通终端无需订阅 |
769
+ | `storage.object_changed` | Storage 对象变更事件透传 |
770
+
771
+ 服务端不产生 `message.ack` 通知;SDK 保留的透传订阅不能作为可依赖的 ACK 或对端已读事件。已读事件只向 owner AID 分发且不进入 Pull 队列;重连查询补偿不受消息 `background_sync=false` 影响。身份切换按代际隔离旧缓存与补偿完成事件,A→B→A 也不能接收旧代际结果;原在途 RPC 仍可返回。详见 [跨设备已读未读 SDK 集成指南](../跨设备已读未读SDK集成指南.md)。
772
+
773
+ `message.received` 的应用层消息保留 `sender_device_id`,表示发送方设备;需要只回复原设备时,将该值作为下一次 `message.send.device_id`。volatile 消息还保留 `storage="volatile"`、`volatile_cursor`、`created_at`、`expires_at`,不含持久 `seq`;内部 slot 不会暴露给应用。
774
+
775
+ ### delivery.changed
776
+
777
+ `delivery.changed` 是 Python、Go、JavaScript 和 TypeScript SDK 统一提供的本地事件,用于通知应用层一批消息已经完成交付,可以从业务仓库刷新相关会话 UI。它不替代 `message.received` / `group.message_created`:应用仍应逐条处理和持久化原消息事件,再把 `delivery.changed` 作为批量刷新信号。
778
+
779
+ 该事件由 SDK 本地 dispatcher 发布,不是 Gateway 推送事件,不属于 JSON-RPC `event/...` 协议;裸 WebSocket 客户端不会收到它。
780
+
781
+ **Payload**:
782
+
783
+ ```json
784
+ {
785
+ "trigger": "pull_drained",
786
+ "changes": [
787
+ {"namespace": "group:team.agentid.pub", "delivered_count": 50},
788
+ {"namespace": "p2p", "delivered_count": 3}
789
+ ]
790
+ }
791
+ ```
792
+
793
+ | 字段 | 类型 | 说明 |
794
+ |------|------|------|
795
+ | `trigger` | string | `pull_drained`、`push` 或 `inline_push` |
796
+ | `changes` | array | 本次刷新涉及的 namespace,按 `namespace` 稳定排序且不重复 |
797
+ | `changes[].namespace` | string | P2P 固定为 `p2p`;群消息为 `group:<canonical-group-aid>` |
798
+ | `changes[].delivered_count` | integer | 本次合并窗口内已发布到应用层的原消息事件数;不是未读数、服务端页大小或最大 seq |
799
+
800
+ 触发与折叠规则:
801
+
802
+ - `pull_drained`:当前 `AUNClient` 上所有已排队或正在执行的 Pull 工作,包括后续分页、continuation、gap fill 和缺失 Sender IK 的延迟补齐,全部排空后最多发布一次。一次通知可以同时包含 P2P 和多个群 namespace,不会按 Pull 页或单个 namespace 分别通知。
803
+ - `push` / `inline_push`:有效实时消息在对应的 `message.received` / `group.message_created` 事件处理完成后通知;同一次有序队列 drain 放行的同类实时消息折叠为一次通知。
804
+ - 有效的 `push` / `inline_push` 可以原子吸收尚未通知的 Pull 变更;这些变更不会在后续 `pull_drained` 中再次通知。
805
+ - 空 Pull、重复或过滤消息、非法 payload、解密失败,以及未带来有效消息交付的 Head 通知,不发布 `delivery.changed`。
806
+
807
+ `delivery.changed` 只表示 UI 刷新时机,不是消息明细或持久化账本。应用仓库仍应按照原消息事件的 `(namespace, message_id)` 做幂等,并按 `seq` 排序。
808
+
809
+ ### 消息来源 `source`
810
+
811
+ 消息、群消息、群事件、撤回和不可解密事件的 payload 可能带有顶层 `source`,用于区分 SDK 的投递来源:
812
+
813
+ | `source` | 含义 |
814
+ |----------|------|
815
+ | `push` | 普通实时推送 |
816
+ | `pull` | Pull、Tail、Forward 或群事件补洞 |
817
+ | `inline_push` | 已在连续窗口内直接消费的实时载荷 |
818
+ | `pending_retry` | 缺失 Sender IK 后延迟恢复的消息 |
819
+
820
+ 直接由应用或无来源内部调用产生的事件不强行添加 `source`。`source` 位于应用事件 payload 顶层,不改变业务 `payload` 内的数据结构;应用应将其视为保留字段。
821
+
822
+ ### Pull 同步生命周期
823
+
824
+ 四个 SDK 均通过本地 dispatcher 发布以下异步生命周期事件;它们不是 Gateway 的 `event/...` 消息,应用 handler 不会阻塞网络 reader。
825
+
826
+ `sync.started` 在本次 Pull 的第一个 namespace 开始时发布一次:
827
+
828
+ ```json
829
+ {
830
+ "run_id": 7,
831
+ "source": "pull",
832
+ "syncing": true,
833
+ "namespaces_pending": 1,
834
+ "namespace": "p2p",
835
+ "received_total": 0,
836
+ "started_at": 1730000000000
837
+ }
838
+ ```
839
+
840
+ 每完成一页消息或群事件的处理、提交和有序投递,发布一次 `sync.progress`。`page_raw_count` 是本页有效原始条数,`page_pulled_count` 是本页实际投递条数;`pulled_count` / `received_total` 是本次 namespace 的累计投递数。服务端返回合法非负整数时透传 `remaining`,并同步设置 `estimated_remaining`;否则仅在协议可推导时提供估算值。
841
+
842
+ ```json
843
+ {
844
+ "run_id": 7,
845
+ "source": "pull",
846
+ "namespace": "p2p",
847
+ "page": 2,
848
+ "page_raw_count": 50,
849
+ "page_pulled_count": 48,
850
+ "pulled_count": 98,
851
+ "received_total": 98,
852
+ "batch_size": 48,
853
+ "has_more": true,
854
+ "remaining": 102,
855
+ "estimated_remaining": 102
856
+ }
857
+ ```
858
+
859
+ 当所有 namespace、续页、gap fill、ordered drain 和延迟 Sender IK 任务均排空时发布一次 `sync.stopped`。正常终态使用 `reason: "pull_drained"`;异常、取消、断线或连接/身份代次失效使用 `reason: "aborted"`。`sync.stopped` 描述同步生命周期终点,`delivery.changed(trigger="pull_drained")` 只汇总实际投递变化;空 Pull 可以有前者而没有后者。
860
+
861
+ ### JS/TS Worker 降级
862
+
863
+ JavaScript 浏览器 SDK 的 Worker 是网络隔离增强:`new Worker()` 同步失败、CSP `worker-src` 阻止、Worker 启动后未及时 `open` 或异步崩溃时,SDK 会销毁 Worker 并回退原生 WebSocket。TypeScript Node.js SDK 在 `worker_threads` 不可用、启动超时或 Network Actor 崩溃时回退本地 transport。两种降级均保留异步 dispatcher 和 Pull 生命周期语义,不要求应用改变消息处理代码。
864
+
865
+ ---
866
+
867
+ ## E2EE 高级 API
901
868
 
902
869
  普通业务无需直接操作 E2EE manager;`message.send` / `group.send` 默认加密并自动解密收到的消息。
903
870
 
@@ -919,14 +886,14 @@ JavaScript 浏览器 SDK 的 Worker 是网络隔离增强:`new Worker()` 同
919
886
 
920
887
  | 领域 | 手册 | 关键方法 |
921
888
  |------|------|----------|
922
- | 消息 | [09-message-rpc-manual.md](09-message-rpc-manual.md) | `message.send` / `message.pull` / `message.history` / `message.ack` / `message.thought.*` |
923
- | 群组 | [09-group-rpc-manual.md](09-group-rpc-manual.md) | `group.create` / `group.send` / `group.pull` / `group.history` / `group.v2.*` / `group.fs.*` |
889
+ | 消息 | [09-message-rpc-manual.md](09-message-rpc-manual.md) | `message.send` / `message.pull` / `message.history` / `message.ack` / `message.read` / `message.read_state` / `message.thought.*` |
890
+ | 群组 | [09-group-rpc-manual.md](09-group-rpc-manual.md) | `group.create` / `group.send` / `group.pull` / `group.history` / `group.read` / `group.read_state` / `group.v2.*` / `group.fs.*` |
924
891
  | 存储 | [09-storage-rpc-manual.md](09-storage-rpc-manual.md) | `storage.put_object` / `storage.fs.*` / `storage.volume.*` / ACL / token / share link |
925
892
  | 协作 | [09-collab-rpc-manual.md](09-collab-rpc-manual.md) | `collab.ls-files` / `collab.show` / `collab.commit` / `collab.merge` / `collab.log` / `collab.diff` / `collab.clone` / `collab.prune` / `collab.gc` / `collab.reflog` / `collab.revert` / `collab.tag.*` / `collab.ls-remote` / `collab.unregister` |
926
893
  | 元信息 | [09-meta-rpc-manual.md](09-meta-rpc-manual.md) | `meta.ping` / `meta.status` / `meta.trust_roots` |
927
- | Stream | [09-stream-rpc-manual.md](09-stream-rpc-manual.md) | `stream.create` / `stream.close` / `stream.list_active` |
928
- | Offline Push | `docs/protocol/15-离线推送通知协议.md` | `push.register` / `push.unregister` / `push.ack` / `push.offline_message` |
929
- | Service Proxy | [09-proxy-rpc-manual.md](09-proxy-rpc-manual.md) | `proxy.register_services` / `proxy.unregister_services` / `proxy.list_services` |
894
+ | Stream | [09-stream-rpc-manual.md](09-stream-rpc-manual.md) | `stream.create` / `stream.close` / `stream.list_active` |
895
+ | Offline Push | `docs/protocol/15-离线推送通知协议.md` | `push.register` / `push.unregister` / `push.ack` / `push.offline_message` |
896
+ | Service Proxy | [09-proxy-rpc-manual.md](09-proxy-rpc-manual.md) | `proxy.register_services` / `proxy.unregister_services` / `proxy.list_services` |
930
897
 
931
898
  ---
932
899