@agentunion/fastaun-browser 0.4.9 → 0.4.11

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 (57) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/_packed_docs/CHANGELOG.md +46 -0
  3. package/_packed_docs/INDEX.md +31 -14
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +20 -14
  5. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +244 -16
  6. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +114 -28
  7. package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +7 -4
  8. package/_packed_docs/sdk/09-group-rpc-manual.md +238 -2
  9. package/_packed_docs/sdk/09-proxy-rpc-manual.md +231 -0
  10. package/_packed_docs/sdk/09-storage-rpc-manual.md +354 -22
  11. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +15 -11
  12. package/_packed_docs/sdk/INDEX.md +14 -8
  13. package/_packed_docs/sdk/Notify/351/200/232/347/237/245/346/226/271/346/241/210.md +214 -0
  14. package/_packed_docs/sdk/README.md +8 -6
  15. package/dist/bundle.js +1611 -48
  16. package/dist/client/delivery.d.ts +8 -1
  17. package/dist/client/delivery.d.ts.map +1 -1
  18. package/dist/client/delivery.js +241 -15
  19. package/dist/client/delivery.js.map +1 -1
  20. package/dist/client/group-state.js +2 -2
  21. package/dist/client/group-state.js.map +1 -1
  22. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  23. package/dist/client/rpc-pipeline.js +29 -4
  24. package/dist/client/rpc-pipeline.js.map +1 -1
  25. package/dist/client/v2-e2ee.d.ts.map +1 -1
  26. package/dist/client/v2-e2ee.js +16 -2
  27. package/dist/client/v2-e2ee.js.map +1 -1
  28. package/dist/client.d.ts +22 -0
  29. package/dist/client.d.ts.map +1 -1
  30. package/dist/client.js +131 -14
  31. package/dist/client.js.map +1 -1
  32. package/dist/index.d.ts +2 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +2 -0
  35. package/dist/index.js.map +1 -1
  36. package/dist/service-proxy.d.ts +219 -0
  37. package/dist/service-proxy.d.ts.map +1 -0
  38. package/dist/service-proxy.js +1321 -0
  39. package/dist/service-proxy.js.map +1 -0
  40. package/dist/transport.d.ts +2 -0
  41. package/dist/transport.d.ts.map +1 -1
  42. package/dist/transport.js +34 -0
  43. package/dist/transport.js.map +1 -1
  44. package/dist/v2/e2ee/encrypt-p2p.js +1 -1
  45. package/dist/v2/e2ee/encrypt-p2p.js.map +1 -1
  46. package/dist/v2/session/keystore.d.ts.map +1 -1
  47. package/dist/v2/session/keystore.js +8 -9
  48. package/dist/v2/session/keystore.js.map +1 -1
  49. package/dist/v2/session/session.d.ts +4 -2
  50. package/dist/v2/session/session.d.ts.map +1 -1
  51. package/dist/v2/session/session.js +20 -4
  52. package/dist/v2/session/session.js.map +1 -1
  53. package/dist/version.d.ts +1 -1
  54. package/dist/version.d.ts.map +1 -1
  55. package/dist/version.js +1 -1
  56. package/dist/version.js.map +1 -1
  57. package/package.json +1 -1
@@ -6,11 +6,12 @@
6
6
 
7
7
  - [AIDStore](#aidstore)
8
8
  - [AID](#aid)
9
- - [AUNClient](#aunclient)
10
- - [事件](#事件)
11
- - [E2EE 高级 API](#e2ee-高级-api)
12
- - [RPC 方法参考](#rpc-方法参考)
13
- - [Stream 使用指南](#stream-使用指南)
9
+ - [AUNClient](#aunclient)
10
+ - [事件](#事件)
11
+ - [ServiceProxyClient](#serviceproxyclient)
12
+ - [E2EE 高级 API](#e2ee-高级-api)
13
+ - [RPC 方法参考](#rpc-方法参考)
14
+ - [Stream 使用指南](#stream-使用指南)
14
15
 
15
16
  > **多语言命名约定**:Python 使用 `snake_case`(如 `aun_path`、`download_agent_md`),TS/JS 使用 `camelCase`(如 `aunPath`、`downloadAgentMd`),Go 使用 `PascalCase` 公开方法(如 `Load`、`Register`)。本手册表格中各列对应各语言的实际命名。
16
17
 
@@ -238,16 +239,51 @@ result = await client.call("message.send", {
238
239
  })
239
240
  ```
240
241
 
241
- 常用 meta RPC 直接透传:
242
-
243
- ```python
244
- await client.call("meta.ping", {})
245
- await client.call("meta.status", {})
246
- await client.call("meta.trust_roots", {})
247
- ```
248
-
249
- ### protected_headers
250
-
242
+ 常用 meta RPC 直接透传:
243
+
244
+ ```python
245
+ await client.call("meta.ping", {})
246
+ await client.call("meta.status", {})
247
+ await client.call("meta.trust_roots", {})
248
+ ```
249
+
250
+ ### Notify
251
+
252
+ `notify()` 发送轻量在线通知,底层是 JSON-RPC Notification,无 `id`,不进入离线存储、seq、pull 或 ack。
253
+
254
+ | Python | TS/JS | Go | 说明 |
255
+ |--------|-------|----|------|
256
+ | `notify(method, params=None, *, to=None, group_id=None, device_id=None, slot_id=None, ttl_ms=None)` | `notify(method, params?, options?)` | `Notify(ctx, method, params, NotifyOptions{...})` | 发送在线轻量通知 |
257
+
258
+ 常见用法:
259
+
260
+ ```python
261
+ await client.notify("notification/client.activity", {"state": "idle"})
262
+ await client.notify("event/app.typing", {"thread_id": "t1"}, to="bob.agentid.pub", ttl_ms=5000)
263
+ await client.notify("event/app.presence", {"state": "active"}, group_id="group.agentid.pub/123")
264
+ ```
265
+
266
+ 路由选项:
267
+
268
+ | 选项 | 说明 |
269
+ |------|------|
270
+ | `to` / `To` | 目标 AID;可同域或跨域 |
271
+ | `group_id` / `groupId` / `GroupID` | 目标群;与 `to` 互斥 |
272
+ | `device_id` / `deviceId` / `DeviceID` | 限定目标 AID 的在线设备;必须配合 `to` |
273
+ | `slot_id` / `slotId` / `SlotID` | 限定目标设备的在线 slot;必须配合 `device_id` |
274
+ | `ttl_ms` / `ttlMs` / `TTLMS` | `0..60000`,只控制在线投递过期,不表示离线缓存 |
275
+
276
+ 约束:
277
+
278
+ - 未指定 `to` / `group_id` 时,`method` 必须以 `notification/` 开头,表示直发 Gateway 的协议级通知。
279
+ - 指定 `to` 或 `group_id` 时,`method` 必须是 `event/app.*`,接收端通过 `client.on("app.xxx", handler)` 订阅。
280
+ - 跨域 AID notify 已支持 federation 在线转发,但仍是 best-effort;目标离线或 federation 不可用时丢弃。
281
+ - 可靠、敏感或需要审计的业务事件应继续使用 `message.send` / `group.send`。
282
+
283
+ 详细语义见 [Notify通知方案.md](Notify通知方案.md)。
284
+
285
+ ### protected_headers
286
+
251
287
  ```python
252
288
  client = AUNClient(aid)
253
289
  client.set_protected_headers({"sdk": "python", "trace": "abc"})
@@ -275,13 +311,62 @@ headers = client.get_protected_headers()
275
311
  - 上传要求目标 AID 已在本地加载且私钥有效;SDK 会对正文签名,并通过 `AuthFlow` 获取或复用该 AID 的 access_token。
276
312
  - SDK 发起 GET 时只发送 `Accept: text/markdown`,不主动发送 `If-None-Match` / `If-Modified-Since`。如果服务端异常返回 304,本地有内容则复用;无内容时再发一次无条件 GET。
277
313
  - `Accept: text/markdown` 与 agent.md 的 YAML frontmatter + Markdown 格式兼容;agent.md 仍是 Markdown 媒体类型上的结构化约定。
278
-
279
- ---
280
-
281
- ## 事件
282
-
283
- ```python
284
- sub = client.on("message.received", handler)
314
+
315
+ ---
316
+
317
+ ## ServiceProxyClient
318
+
319
+ Service Proxy 用于 provider 通过 AUN 身份暴露本地 HTTP / WebSocket 服务。当前公开封装在 Python SDK 的 `ServiceProxyClient` 中;其它语言可以按 [09-proxy-rpc-manual.md](09-proxy-rpc-manual.md) 直接实现同等控制面和隧道消息。
320
+
321
+ ```python
322
+ from aun_core.service_proxy import ServiceProxyClient
323
+
324
+ proxy_client = ServiceProxyClient(
325
+ provider_aid="alice.agentid.pub",
326
+ aun_client=client,
327
+ )
328
+ proxy_client.register_service(
329
+ "fileshare",
330
+ "http://127.0.0.1:8080",
331
+ visibility="public",
332
+ )
333
+ await proxy_client.serve_forever()
334
+ ```
335
+
336
+ proxy-server 连接地址不能由应用外部传入或配置。`ServiceProxyClient` 会先读取 provider AID 本地 SQLite metadata 中 1 小时 TTL 的 `service_proxy_discovery` 缓存;缓存缺失或过期时,按协议查询 `https://{provider_aid}/.well-known/aun-proxy`,失败后回退 `https://proxy.{issuer}/.well-known/aun-proxy`,并使用返回的 `ws_url` 建立隧道。
337
+
338
+ 关键 API:
339
+
340
+ | Python | 说明 |
341
+ |--------|------|
342
+ | `register_service(service_name, endpoint, service_type="http", visibility="private", metadata=None)` | 注册本地 embedded endpoint |
343
+ | `unregister_service(service_name)` | 注销本地服务 |
344
+ | `list_service_summaries()` | 获取可上报到 Gateway 和 proxy-server 的服务摘要 |
345
+ | `register_services_with_gateway()` | 显式调用 Gateway `proxy.register_services` |
346
+ | `unregister_services_from_gateway(service_names=None)` | 显式调用 Gateway `proxy.unregister_services` |
347
+ | `list_gateway_services()` | 显式调用 Gateway `proxy.list_services` |
348
+ | `register_services_with_proxy_server(ws)` | 通过已认证 proxy-server 隧道发送 `register_services` |
349
+ | `discover_proxy_server(force_refresh=False)` | 通过缓存 / well-known 发现 proxy-server |
350
+ | `connect_once()` | 建立一次 proxy-server 隧道并完成认证、数据面注册和可选心跳 |
351
+ | `serve_once()` | 处理有限数量的 proxy-server 转发请求 |
352
+ | `serve_forever(connection_mode="persistent")` | 持续提供 Service Proxy 服务;支持 persistent / on_demand |
353
+
354
+ 自动注册顺序:
355
+
356
+ - `connect_once()`、`serve_once()`、`serve_forever()` 在存在 `aun_client.call()` 时,会先向 Gateway 调用 `proxy.register_services`。
357
+ - 建立 proxy-server 隧道前,SDK 必须通过缓存 / `/.well-known/aun-proxy` 发现得到 `ws_url`;不得由应用传入或配置 proxy-server 地址。
358
+ - proxy-server 隧道使用 `Authorization: Bearer <access_token>` 鉴权;SDK 优先复用 cached token,缺失或过期时通过 `aun_client.authenticate()` 向 Gateway 完成登录刷新。
359
+ - 每次 proxy-server 隧道认证成功后,都会立即向 proxy-server 发送 `register_services` 隧道消息。
360
+ - 服务列表与连接绑定;断开 Gateway 长连接或 proxy-server 隧道后,相应注册立即失效。
361
+
362
+ 详细控制面 RPC、隧道消息和路由语义见 [09-proxy-rpc-manual.md](09-proxy-rpc-manual.md)。
363
+
364
+ ---
365
+
366
+ ## 事件
367
+
368
+ ```python
369
+ sub = client.on("message.received", handler)
285
370
  client.off("message.received", handler)
286
371
  sub.unsubscribe()
287
372
  ```
@@ -324,12 +409,13 @@ sub.unsubscribe()
324
409
  | 领域 | 手册 | 关键方法 |
325
410
  |------|------|----------|
326
411
  | 消息 | [09-message-rpc-manual.md](09-message-rpc-manual.md) | `message.send` / `message.pull` / `message.ack` / `message.thought.*` |
327
- | 群组 | [09-group-rpc-manual.md](09-group-rpc-manual.md) | `group.create` / `group.invite` / `group.send` / `group.v2.*` |
328
- | 存储 | [09-storage-rpc-manual.md](09-storage-rpc-manual.md) | `storage.upload` / `storage.download` / `storage.share` |
329
- | 元信息 | [09-meta-rpc-manual.md](09-meta-rpc-manual.md) | `meta.ping` / `meta.status` / `meta.trust_roots` |
330
- | Stream | [09-stream-rpc-manual.md](09-stream-rpc-manual.md) | `stream.create` / `stream.close` / `stream.list_active` |
331
-
332
- ---
412
+ | 群组 | [09-group-rpc-manual.md](09-group-rpc-manual.md) | `group.create` / `group.send` / `group.v2.*` / `group.resources.*` |
413
+ | 存储 | [09-storage-rpc-manual.md](09-storage-rpc-manual.md) | `storage.put_object` / `storage.create_upload_session` / `storage.create_folder` / `storage.create_share_link` |
414
+ | 元信息 | [09-meta-rpc-manual.md](09-meta-rpc-manual.md) | `meta.ping` / `meta.status` / `meta.trust_roots` |
415
+ | Stream | [09-stream-rpc-manual.md](09-stream-rpc-manual.md) | `stream.create` / `stream.close` / `stream.list_active` |
416
+ | Service Proxy | [09-proxy-rpc-manual.md](09-proxy-rpc-manual.md) | `proxy.register_services` / `proxy.unregister_services` / `proxy.list_services` |
417
+
418
+ ---
333
419
 
334
420
  ## Stream 使用指南
335
421
 
@@ -13,13 +13,13 @@ AUNError
13
13
  │ └── IdentityConflictError
14
14
  ├── PermissionError
15
15
  ├── ValidationError
16
+ │ └── ClientSignatureError
16
17
  ├── NotFoundError
17
18
  ├── RateLimitError
18
19
  ├── VersionConflictError
19
20
  ├── StateError
20
21
  ├── SerializationError
21
22
  ├── SessionError
22
- ├── ClientSignatureError
23
23
  ├── GroupError
24
24
  └── E2EEError
25
25
  ```
@@ -55,17 +55,20 @@ except AUNError as e:
55
55
  | 4040 / 404 | 资源不存在 | `NotFoundError` |
56
56
  | 4290 / 429 | 请求限流 | `RateLimitError` |
57
57
  | -32001 / -32003 | 认证失败 | `AuthError` |
58
- | -32004 | 权限不足 | `PermissionError` |
58
+ | -32004 | RPC handler 超时(消息以 `"rpc handler timeout"` 开头) | `TimeoutError` |
59
+ | -32004 | 权限不足(其他情况) | `PermissionError` |
59
60
  | -32008 | 资源不存在 | `NotFoundError` |
60
61
  | -32009 | 版本冲突 | `VersionConflictError` |
61
62
  | -32010 / -32011 / -32013 | 会话错误 | `SessionError` |
63
+ | -32051 | 客户端签名验证失败 | `ClientSignatureError`(继承自 `ValidationError`) |
62
64
  | -32029 | 请求限流 | `RateLimitError` |
63
65
  | -32600 / -32601 / -32602 | JSON-RPC 参数错误 | `ValidationError` |
64
66
  | -32040 ~ -32044 | E2EE 群组错误 | `E2EEError` 子类 |
65
67
  | 4090 | 身份冲突 | `IdentityConflictError` |
66
68
  | -32050 | 证书已吊销 | `CertificateRevokedError` |
67
- | -32051 | 客户端签名验证失败 | `ClientSignatureError` |
68
- | -33001 ~ -33009 | 群组错误 | `GroupError` 子类 |
69
+ | -33001 | 群组不存在 | `GroupNotFoundError` |
70
+ | -33002 / -33003 | 群组状态错误 | `GroupStateError` |
71
+ | -33004 ~ -33009 | 其他群组错误 | `GroupError` 子类 |
69
72
 
70
73
  ---
71
74
 
@@ -67,6 +67,7 @@
67
67
  | 方法 | 说明 |
68
68
  |------|------|
69
69
  | [group.send](#groupsend) | 发送群消息 |
70
+ | [group.recall](#grouprecall) | 撤回群消息 |
70
71
  | [group.thought.put](#groupthoughtput) | 写入某个群上下文的思考内容 |
71
72
  | [group.thought.get](#groupthoughtget) | 获取某个群上下文的思考内容 |
72
73
  | [group.pull](#grouppull) | 增量拉取消息 |
@@ -120,6 +121,16 @@
120
121
  | 方法 | 说明 |
121
122
  |------|------|
122
123
  | [group.resources.put](#groupresourcesput) | 分享资源 |
124
+ | [group.resources.create_folder](#groupresourcescreate_folder) | 创建资源目录 |
125
+ | [group.resources.list_children](#groupresourceslist_children) | 列出目录子节点 |
126
+ | [group.resources.rename](#groupresourcesrename) | 重命名资源节点 |
127
+ | [group.resources.move](#groupresourcesmove) | 移动资源节点 |
128
+ | [group.resources.mount_object](#groupresourcesmount_object) | 挂载 storage 对象为资源 |
129
+ | [group.resources.request_mount_object](#groupresourcesrequest_mount_object) | 申请挂载 storage 对象 |
130
+ | [group.resources.unmount](#groupresourcesunmount) | 取消挂载资源 |
131
+ | [group.resources.resolve_path](#groupresourcesresolve_path) | 按路径解析资源 |
132
+ | [group.resources.list_refs_by_storage](#groupresourceslist_refs_by_storage) | 按 storage 引用反查资源 |
133
+ | [group.resources.cleanup_by_storage_ref](#groupresourcescleanup_by_storage_ref) | 清理失效 storage 引用 |
123
134
  | [group.resources.get](#groupresourcesget) | 查看资源 |
124
135
  | [group.resources.list](#groupresourceslist) | 列出资源 |
125
136
  | [group.resources.update](#groupresourcesupdate) | 更新资源元数据 |
@@ -1173,6 +1184,59 @@ result = await client.call("group.thought.get", {
1173
1184
  {"cursor": 42}
1174
1185
  ```
1175
1186
 
1187
+ ### group.recall
1188
+
1189
+ 撤回群消息。仅**原发送者**可撤回自己的消息,受时间窗口限制(默认 **120 秒**,由群服务配置 `recall_window_seconds` 控制,`0` 表示不限制)。管理员撤回(`group_admin_recall_enabled`)默认关闭,本期不实现。
1190
+
1191
+ **双 tombstone 机制**:群消息使用 per-group 全局 `message_seq`(V1/V2 共享同一空间),直接删除原消息会让"还没拉到原消息"的客户端遇到永久 seq 空洞。因此撤回写入两条 tombstone:
1192
+
1193
+ - **原 seq 占位 tombstone**:占住被撤消息原来的 seq。V1 把原 `group_messages` 行 `message_type` 改为 `group.message_recalled` 并清空正文;V2 删除 `v2_group_messages` 密文体与所有 `v2_group_wraps`,再在 `group_messages` 插入同 `original_seq` 的明文 tombstone 顶替。服务对象是**还没读到原消息**的客户端——拉到该 seq 看到 tombstone,而非空洞。
1194
+ - **新 seq 通知 tombstone**:分配一个新的 `message_seq`,通知**已经读过原消息**(游标已越过 `original_seq`)的客户端"这条消息被撤回了"。
1195
+
1196
+ 同时写一条 `group_events`(`event_type = group.message_recalled`)用于事件流审计,并在事务提交后推送 `event/group.message_recalled`(见下)。撤回真相记录在 `group_message_recalls` 表,`(group_id, original_message_id)` 与 `(group_id, original_seq)` 双唯一键防止重复撤回。
1197
+
1198
+ **参数**:
1199
+
1200
+ | 参数 | 类型 | 必填 | 说明 |
1201
+ |------|------|------|------|
1202
+ | `group_id` | string | 是 | 群组 ID |
1203
+ | `message_ids` | string[] | 是 | 待撤回消息 ID 列表,最多 100 个(`recall_max_batch`)|
1204
+ | `reason` | string | 否 | 可选撤回理由,建议短文本(最长 255 字符)|
1205
+
1206
+ **响应**:
1207
+
1208
+ ```json
1209
+ {
1210
+ "success": true,
1211
+ "accepted": ["gm-aaa", "gm-bbb"],
1212
+ "recalled": ["gm-aaa"],
1213
+ "errors": [
1214
+ {"message_id": "gm-bbb", "error": "not_sender"}
1215
+ ]
1216
+ }
1217
+ ```
1218
+
1219
+ | 字段 | 类型 | 说明 |
1220
+ |------|------|------|
1221
+ | `success` | boolean | 整体是否受理 |
1222
+ | `accepted` | string[] | 通过时间窗口前置过滤、进入撤回事务的消息 ID |
1223
+ | `recalled` | string[] | DB 真实撤回成功的消息 ID(避免并发撤回误通知)|
1224
+ | `errors` | array | 逐条错误:`{message_id, error}` |
1225
+
1226
+ **逐条错误码**:
1227
+
1228
+ | error | 说明 |
1229
+ |-------|------|
1230
+ | `not_found` | 消息不存在 |
1231
+ | `not_sender` | 操作者不是原发送者 |
1232
+ | `already_recalled` | 消息已被撤回(命中唯一键 / 占位 tombstone 已存在)|
1233
+ | `expired` | 超过撤回时间窗口 |
1234
+ | `group_inactive` | 群组非 active 状态(suspended / dissolved)|
1235
+
1236
+ **SDK 行为**:SDK 把 pull / push 收到的撤回 tombstone(占位与通知)归一化为 `group.message_recalled` 应用事件,**不**作为普通 `group.message_created` 交付;tombstone 仍占 seq,正常推进 SeqTracker 与 ack。SDK 按 `(group_id, message_ids)` 去重,因此即使同时收到在线 push 事件、占位 tombstone、通知 tombstone,应用层也**只回调一次**。去重键**不含 `recalled_at`**:占位 tombstone、通知 tombstone 与在线 push 三条通道对同一次撤回可能携带不同来源的时间戳(push 在事务提交后重取),若纳入 `recalled_at` 会使去重失效、导致重复回调;一条消息只能被撤回一次(服务端 `group_message_recalls` 唯一键保证),`(group_id, message_ids)` 已能唯一标识一次撤回。
1237
+
1238
+ > 所有语言 SDK 统一通过 `client.call("group.recall", {...})` 调用,不提供独立的便捷方法名。
1239
+
1176
1240
  ---
1177
1241
 
1178
1242
  ## 公告与规则
@@ -1304,6 +1368,122 @@ result = await client.call("group.thought.get", {
1304
1368
 
1305
1369
  > `created` 为 `true` 表示新建,`false` 表示更新已有资源。
1306
1370
 
1371
+ ### group.resources.create_folder
1372
+
1373
+ 创建群资源目录。需要 **member 及以上**权限。
1374
+
1375
+ **参数**:
1376
+
1377
+ | 参数 | 类型 | 必填 | 说明 |
1378
+ |------|------|------|------|
1379
+ | `group_id` | string | 是 | 群组 ID |
1380
+ | `path` / `resource_path` | string | 否 | 完整目录路径 |
1381
+ | `name` | string | 否 | 目录名;未提供完整路径时使用 |
1382
+ | `parent_resource_id` / `parent_path` | string | 否 | 父目录 |
1383
+ | `title` | string | 否 | 显示标题,默认目录名 |
1384
+ | `metadata` | object | 否 | 自定义元数据 |
1385
+ | `visibility` | string | 否 | `"members_only"` / `"public"` |
1386
+ | `tags` | array | 否 | 标签 |
1387
+ | `mkdirs` | boolean | 否 | 是否递归创建父目录 |
1388
+ | `sort_order` | integer | 否 | 排序值 |
1389
+
1390
+ **响应**:`{ "group_id": "...", "resource": { ... }, "created": true }`。
1391
+
1392
+ ### group.resources.list_children
1393
+
1394
+ 列出某个资源目录下的直接子节点。
1395
+
1396
+ **参数**:
1397
+
1398
+ | 参数 | 类型 | 必填 | 说明 |
1399
+ |------|------|------|------|
1400
+ | `group_id` | string | 是 | 群组 ID |
1401
+ | `resource_id` / `path` / `resource_path` | string | 否 | 父目录;不传表示根目录 |
1402
+ | `type` / `resource_type` | string | 否 | `"folder"` / `"file"` / `"link"` |
1403
+ | `include_status` | boolean | 否 | 是否附带 storage 状态 |
1404
+ | `page` / `offset` | integer | 否 | 分页位置 |
1405
+ | `size` / `limit` | integer | 否 | 每页数量 |
1406
+ | `sort_by` | string | 否 | 排序字段,默认 `sort_order` |
1407
+ | `order` | string | 否 | `"asc"` / `"desc"` |
1408
+
1409
+ **响应**:`group_id`、`resource_id`、`path`、`items`、`total`、`count`、`page`、`size`、`offset`。
1410
+
1411
+ ### group.resources.rename
1412
+
1413
+ 重命名资源节点。需要资源创建者、storage owner、owner 或 admin 权限。
1414
+
1415
+ **参数**:`group_id`,资源选择器(`resource_id` / `resource_path` / `path`),`new_name`;可选 `title`、`expected_version`。
1416
+
1417
+ **响应**:更新后的 `resource`。
1418
+
1419
+ ### group.resources.move
1420
+
1421
+ 移动资源节点。目录不能移动到自身或自身子目录。
1422
+
1423
+ **参数**:`group_id`,资源选择器,目标父目录(`dst_parent_resource_id` / `dst_parent_path`),可选 `new_name` / `dst_name`、`expected_version`。
1424
+
1425
+ **响应**:更新后的 `resource`。
1426
+
1427
+ ### group.resources.mount_object
1428
+
1429
+ 将 `storage.*` 对象挂载为群资源。需要 **owner/admin** 权限。
1430
+
1431
+ **参数**:
1432
+
1433
+ | 参数 | 类型 | 必填 | 说明 |
1434
+ |------|------|------|------|
1435
+ | `group_id` | string | 是 | 群组 ID |
1436
+ | `storage_ref` | object | 是 | storage 引用,通常包含 `owner_aid`、`bucket`、`object_id` 或 `object_key` |
1437
+ | `path` / `resource_path` | string | 否 | 资源路径;不传时用 storage 文件名 |
1438
+ | `title` | string | 否 | 显示标题 |
1439
+ | `metadata` | object | 否 | 自定义元数据 |
1440
+ | `visibility` | string | 否 | `"members_only"` / `"public"` |
1441
+ | `tags` | array | 否 | 标签 |
1442
+ | `mkdirs` | boolean | 否 | 是否递归创建父目录,默认 `true` |
1443
+ | `conflict_policy` | string | 否 | `"reject"` / `"replace"` / `"keep_both"` |
1444
+
1445
+ **响应**:`{ "group_id": "...", "resource": { ... }, "created": true }`。
1446
+
1447
+ ### group.resources.request_mount_object
1448
+
1449
+ 成员申请挂载自己的 storage 对象,进入待审批队列。语义等同 `group.resources.request_add`,但输入按挂载对象组织。
1450
+
1451
+ **参数**:同 `group.resources.mount_object`,但不需要 owner/admin 权限。
1452
+
1453
+ **响应**:`{ "group_id": "...", "request": { ... } }`。
1454
+
1455
+ ### group.resources.unmount
1456
+
1457
+ 取消挂载资源,等价于非递归 `group.resources.delete`。
1458
+
1459
+ **参数**:`group_id`,资源选择器(`resource_id` / `resource_path` / `path`)。
1460
+
1461
+ **响应**:删除结果。
1462
+
1463
+ ### group.resources.resolve_path
1464
+
1465
+ 按路径解析资源节点。
1466
+
1467
+ **参数**:`group_id`、`path` / `resource_path`;可选 `expected_type`。
1468
+
1469
+ **响应**:`resource_id`、`resource_type`、`resource_path`、`path`、`status`、`resource`。
1470
+
1471
+ ### group.resources.list_refs_by_storage
1472
+
1473
+ 按 storage 引用反查群资源。传 `group_id` 时要求调用者是该群成员;不传 `group_id` 时要求调用者是 `owner_aid`。
1474
+
1475
+ **参数**:`owner_aid` 必填,`object_id` 或 `object_key` 至少一个;可选 `bucket`、`group_id`、`include_missing`、`offset`、`limit` / `size`。
1476
+
1477
+ **响应**:`items`、`total`、`count`、`limit`、`offset`,并回显 storage 选择器字段。
1478
+
1479
+ ### group.resources.cleanup_by_storage_ref
1480
+
1481
+ 清理指向已删除或失效 storage 对象的资源引用。传 `group_id` 时要求 owner/admin 或 storage owner;不传 `group_id` 时要求 storage owner。
1482
+
1483
+ **参数**:`owner_aid` 必填,`object_id` 或 `object_key` 至少一个;可选 `bucket`、`group_id`、`mode`。
1484
+
1485
+ **响应**:`affected_count` 和被影响的资源 `items`。
1486
+
1307
1487
  ### group.resources.get
1308
1488
 
1309
1489
  查看资源详情。
@@ -1431,7 +1611,7 @@ result = await client.call("group.thought.get", {
1431
1611
 
1432
1612
  ### group.resources.direct_add
1433
1613
 
1434
- Owner 直接添加资源(无需审批)。需要 **owner** 权限。
1614
+ Owner/Admin 直接添加资源(无需审批)。需要 **owner/admin** 权限。
1435
1615
 
1436
1616
  **参数**:同 `group.resources.put`(`resource_type` 不能是 `"folder"`)。
1437
1617
 
@@ -1781,7 +1961,8 @@ CAS 轮换群组 E2EE Epoch。需要 **admin 及以上**权限。
1781
1961
  {
1782
1962
  "module_id": "group",
1783
1963
  "action": "member_added",
1784
- "group_id": "g-abc123.agentid.pub"
1964
+ "group_id": "g-abc123.agentid.pub",
1965
+ "event_seq": 42
1785
1966
  }
1786
1967
  ```
1787
1968
 
@@ -1790,9 +1971,21 @@ CAS 轮换群组 E2EE Epoch。需要 **admin 及以上**权限。
1790
1971
  | `module_id` | string | 固定 `"group"` |
1791
1972
  | `action` | string | 变更类型(见下表) |
1792
1973
  | `group_id` | string | 群组 ID |
1974
+ | `event_seq` | integer | 可选,服务端分配的单调递增序号,用于 SDK 内部保序去重 |
1793
1975
  | `request_id` | string | 可选,仅资源审批相关 action |
1794
1976
  | `resource_path` | string | 可选,仅资源相关 action |
1795
1977
 
1978
+ **保序去重(SDK 内部行为)**:
1979
+
1980
+ 服务端为每条 `group.changed` 事件分配 `event_seq`(按群 `group_event:{group_id}` 命名空间单调递增)。SDK 收到事件后:
1981
+
1982
+ 1. **去重**:`event_seq` ≤ 已连续消费序号,或已处理过该序号,则丢弃
1983
+ 2. **保序**:事件入有序队列,按序号连续后才发布给应用层
1984
+ 3. **补洞**:检测到序号空洞时,自动调用 `group.pull_events` 拉取缺失事件补齐
1985
+ 4. **ack**:连续段推进后自动发送 `group.ack_events`(namespace `group_event:{group_id}`)
1986
+
1987
+ 不携带 `event_seq` 的旧格式事件直接发布,不参与保序(兼容旧服务端)。
1988
+
1796
1989
  **action 取值**:
1797
1990
 
1798
1991
  | action | 说明 |
@@ -1874,6 +2067,49 @@ SDK 收到后自动解密 `payload`,解密后的明文消息直接交付用户
1874
2067
 
1875
2068
  SDK 收到后自动调用 `group.pull` 拉取最新消息并逐条解密后交付用户回调。
1876
2069
 
2070
+ ### event/group.message_recalled
2071
+
2072
+ 群消息撤回后推送给所有在线成员(与 pull 双 tombstone 兜底互补)。在线 push 是实时通道,双 tombstone 是离线 / 未读 / push 丢失时的可靠性兜底;两者最终一致,SDK 去重保证应用层只感知一次。
2073
+
2074
+ **Payload**:
2075
+
2076
+ ```json
2077
+ {
2078
+ "module_id": "group",
2079
+ "group_id": "g-abc123.agentid.pub",
2080
+ "seq": 43,
2081
+ "message_id": "grm-uuid",
2082
+ "message_ids": ["gm-aaa"],
2083
+ "target_message_seqs": [42],
2084
+ "sender_aid": "alice.agentid.pub",
2085
+ "recalled_by": "alice.agentid.pub",
2086
+ "recalled_at": 1234567890000,
2087
+ "reason": "",
2088
+ "member_aids": ["bob.agentid.pub"]
2089
+ }
2090
+ ```
2091
+
2092
+ | 字段 | 类型 | 说明 |
2093
+ |------|------|------|
2094
+ | `module_id` | string | 固定 `"group"` |
2095
+ | `group_id` | string | 群组 ID |
2096
+ | `seq` | integer | 撤回通知 tombstone 的**新群消息 seq** |
2097
+ | `message_id` | string | 撤回通知 tombstone 自己的 message_id |
2098
+ | `message_ids` | string[] | 被撤回的**原消息 ID 列表** |
2099
+ | `target_message_seqs` | integer[] | 被撤回的原消息 seq 列表 |
2100
+ | `sender_aid` | string | 原消息发送方 |
2101
+ | `recalled_by` | string | 撤回操作者 |
2102
+ | `recalled_at` | integer | 撤回时间戳(毫秒)|
2103
+ | `reason` | string | 可选撤回理由 |
2104
+
2105
+ 跨域成员通过 federation forward 转发(`group.message_recalled` 在跨域转发白名单内),路径与 `group.message_created` 一致。
2106
+
2107
+ **订阅**:
2108
+
2109
+ ```python
2110
+ client.on("group.message_recalled", lambda ev: print("recalled:", ev["message_ids"]))
2111
+ ```
2112
+
1877
2113
  ---
1878
2114
 
1879
2115
  ## 错误码