@agentunion/fastaun-browser 0.5.2 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/_packed_docs/CHANGELOG.md +36 -0
- package/_packed_docs/INDEX.md +16 -10
- package/_packed_docs/KITE_DOCS_GUIDE.md +4 -2
- package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +202 -75
- package/_packed_docs/protocol/README.md +3 -2
- package/_packed_docs/protocol/aun-docs-guide.md +3 -2
- package/_packed_docs/protocol/index.md +6 -5
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +20 -1
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +289 -24
- package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +30 -6
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +28 -12
- package/_packed_docs/sdk/09-group-rpc-manual.md +102 -28
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +6 -4
- package/_packed_docs/sdk/INDEX.md +17 -17
- package/dist/bundle.js +982 -250
- package/dist/client/delivery.d.ts +4 -0
- package/dist/client/delivery.d.ts.map +1 -1
- package/dist/client/delivery.js +174 -7
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/v2-e2ee.js +2 -2
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +27 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +118 -12
- package/dist/client.js.map +1 -1
- package/dist/facades.d.ts +6 -0
- package/dist/facades.d.ts.map +1 -1
- package/dist/facades.js +213 -33
- package/dist/facades.js.map +1 -1
- package/dist/group-index.d.ts +105 -0
- package/dist/group-index.d.ts.map +1 -0
- package/dist/group-index.js +252 -0
- package/dist/group-index.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/keystore/index.d.ts +15 -0
- package/dist/keystore/index.d.ts.map +1 -1
- package/dist/keystore/indexeddb-shared.d.ts +7 -1
- package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
- package/dist/keystore/indexeddb-shared.js +63 -2
- package/dist/keystore/indexeddb-shared.js.map +1 -1
- package/dist/keystore/indexeddb-token-store.d.ts +3 -1
- package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
- package/dist/keystore/indexeddb-token-store.js +22 -1
- package/dist/keystore/indexeddb-token-store.js.map +1 -1
- package/dist/tools/cross-sdk-agent.js +3 -1
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +2 -1
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +17 -31
- package/dist/transport.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
| E2EE | [08-AUN-E2EE.md](08-AUN-E2EE.md) | Legacy P2P E2EE 信封说明;当前默认主路径见 SDK V2 多设备 wrap 文档 |
|
|
20
20
|
| E2EE-Group | [08-AUN-E2EE-Group.md](08-AUN-E2EE-Group.md) | 群组 E2EE V2:消息级密钥、逐设备密钥包裹、成员状态签名验证 |
|
|
21
21
|
| 09 | [09-安全考虑.md](09-安全考虑.md) | 威胁模型、防护措施、升级安全、验签时序 |
|
|
22
|
-
| 10 | [10-Group-子协议.md](10-Group-子协议.md) | `group.*`
|
|
22
|
+
| 10 | [10-Group-子协议.md](10-Group-子协议.md) | `group.*` 群组管理、群消息、`group.index` 签名索引、邀请码、资源共享、在线状态 |
|
|
23
23
|
| 11 | [11-Storage-子协议.md](11-Storage-子协议.md) | `storage.*` 对象存储、大文件上传下载、预签名 URL |
|
|
24
24
|
| 12 | [12-Stream-子协议.md](12-Stream-子协议.md) | `stream.*` 实时流式传输、WebSocket 推流、HTTP SSE 拉流、跨域拉流 |
|
|
25
25
|
| 15 | [15-离线推送通知协议.md](15-离线推送通知协议.md) | `push.*` 离线推送、push_notify_aid 代理、事件通知 + 背压 ack、白名单与去重 |
|
|
@@ -53,8 +53,9 @@
|
|
|
53
53
|
| 任务协作(task.*) | 06 §6.5 |
|
|
54
54
|
| 跨域消息路由 | 06 §6.7 |
|
|
55
55
|
| E2EE 端到端加密 | AUN-E2EE, 06 §6.6 |
|
|
56
|
-
| 群组管理(group.*) | 10 |
|
|
57
|
-
|
|
|
56
|
+
| 群组管理(group.*) | 10 |
|
|
57
|
+
| 群索引(group.index / _meta.group_indexes / CAS) | 10 §10.13 |
|
|
58
|
+
| 群消息收发(group.send/pull) | 10 §10.6 |
|
|
58
59
|
| 群成员权限(owner/admin/member) | 10 §10.2 |
|
|
59
60
|
| 邀请码(group.create_invite_code) | 10 §10.7 |
|
|
60
61
|
| 群文件系统(group.fs.*) | 10 §10.9 |
|
|
@@ -113,8 +114,8 @@ Gateway 模式定位与职责、Gateway 发现机制、连接时序(auth.* →
|
|
|
113
114
|
### 09-安全考虑
|
|
114
115
|
威胁模型、传输层安全、认证安全、JWT 信任模型分析、连接升级安全(降级攻击/假地址注入/信令重放)、公开 AP 同步安全、证书轮换验签时序。
|
|
115
116
|
|
|
116
|
-
### 10-Group-子协议
|
|
117
|
-
`group.*` 命名空间完整协议规范。群组生命周期(create/suspend/resume/dissolve)、成员管理(add/kick/set_role/transfer_owner)、群消息(send/pull/ack、`payload.type`
|
|
117
|
+
### 10-Group-子协议
|
|
118
|
+
`group.*` 命名空间完整协议规范。群组生命周期(create/suspend/resume/dissolve)、成员管理(add/kick/set_role/transfer_owner)、群消息(send/pull/ack、`payload.type` 负载类型)、`group.index` 签名索引与 `expected_index_etag` CAS、`_meta.group_indexes` 版本提示、入群申请与邀请码、群规则与公告、资源共享、在线状态、事件推送(group.created/changed/message_created)、错误码(-33001~-33009)。Group Service 作为独立 AID 持有者运行,Gateway/Message 不生成或改写 group index。
|
|
118
119
|
|
|
119
120
|
### 11-Storage-子协议
|
|
120
121
|
`storage.*` 命名空间完整协议规范。控制面与数据面分离(小对象内联 RPC,大对象预签名 URL HTTP 传输)、per-AID 隔离、对象键路径化、版本化 CAS 并发控制。方法:put_object / get_object / delete_object / list_objects / create_upload_session / create_download_ticket。
|
|
@@ -165,6 +165,25 @@ client.on("state_change", lambda e: print(e["state"]))
|
|
|
165
165
|
client.on("message.received", lambda e: print(e["payload"]))
|
|
166
166
|
```
|
|
167
167
|
|
|
168
|
-
RPC 方法参数见 `09-message-rpc-manual.md`、`09-group-rpc-manual.md`、`09-storage-rpc-manual.md` 等专项手册。
|
|
168
|
+
RPC 方法参数见 `09-message-rpc-manual.md`、`09-group-rpc-manual.md`、`09-storage-rpc-manual.md` 等专项手册。
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Group Index 观察模型
|
|
173
|
+
|
|
174
|
+
`group.index` 是群公告、群规则、入群要求和附件稳定引用的签名索引。它由 owner/admin 侧 SDK 生成并签名,Group 服务只校验、CAS 保存和在响应 `_meta.group_indexes` 中注入版本提示。
|
|
175
|
+
|
|
176
|
+
关键语义:
|
|
177
|
+
|
|
178
|
+
- `_meta.group_indexes` 只包含 `etag`、`last_modified`、`schema`,不包含 index 正文。
|
|
179
|
+
- SDK 观察到远端 `etag` 变化后只记录观察到的远端版本;etag 不一致只表示本地与远端不同步,不表示远端一定更新,也不表示本地一定应被覆盖。
|
|
180
|
+
- `getAnnouncement` / `getRules` / `getJoinRequirements` 只返回 SDK 本地缓存;本地没有对应值时才读取相应 settings 初始化缓存,不会因为 etag 不一致自动 pull 远端。
|
|
181
|
+
- 应用层调用 `checkGroupIndex` 判断是否不同步;如选择远端为准,再调用 `getGroupIndex` 从服务端摘取当前 index 并同步本地缓存。
|
|
182
|
+
- owner/admin 修改 indexed settings 时调用 `updateGroupIndex`;SDK 会先读取当前 index,再基于 `expected_index_etag` CAS push 新签名版本。
|
|
183
|
+
- Gateway/Message 不生成、不校验、不更新 `group.index`,最多转发或合并 `_meta`。
|
|
184
|
+
- `getGroupIndex` pull 验签通过后必须持久化本地视图:Python / TypeScript(Node) / Go 写入 `{aun_path}/AIDs/{local_aid}/groups/{group_aid}/index.jsonl` 和同目录 `group-index-cache.json`;浏览器 JavaScript 写入 IndexedDB `group_index_cache`,字段包含 `index_jsonl`、`local_etag`、`remote_meta`、`settings`、`entry_etags`。
|
|
185
|
+
- 本地持久化目录按 `local_aid + group_aid` 隔离,不是 `{aun_path}/AIDs/{group_aid}/`;`index.jsonl` 保存签名 `group.index.body` 原文,SDK cache envelope 不再使用旧单文件 `group-index.json`。
|
|
186
|
+
|
|
187
|
+
这个模型与 `agent.md` 的版本观察机制对齐:先观察变化,再由应用层决定以本地还是远端为准,显式 pull、merge 或 push。
|
|
169
188
|
|
|
170
189
|
|
|
@@ -116,11 +116,11 @@ Python、TS/Node 与 Go 的 `AIDStore` 本地方法返回 Result 包装;浏览
|
|
|
116
116
|
| `cached` | bool | 是否命中 TTL 窗口内的本地检查记录 |
|
|
117
117
|
| `verify_status` / `verify_error` | str | 最近一次下载验签状态 |
|
|
118
118
|
|
|
119
|
-
agent.md 本地记录不写入 SQLite。Python / TypeScript / Go 使用 `{aun_path}/AIDs/{aid}/agent.md` 与 `agentmd.json`;浏览器 JavaScript 使用 IndexedDB 等价 key,存储不可用时退化为内存缓存。
|
|
120
|
-
|
|
121
|
-
`remote_etag` / `last_modified` 除了来自 `check_agent_md()` 的 HEAD,也会由连接后的内部观察器更新:SDK 会读取 RPC response / event push `_meta.agent_md_etags` 的 `requester`、`peer`、`group` 及兼容别名,并读取 V2 envelope 的 `agent_md.sender` / `agent_md.group`。`group` 记录使用群自身 `group_aid` / `group_id` 作为 AID key。
|
|
122
|
-
|
|
123
|
-
> **v0.4.2 变更**:`discoveryPort` 配置项已移除,Gateway 地址完全由 SDK 根据 AID issuer 自动发现,无需手动指定端口。
|
|
119
|
+
agent.md 本地记录不写入 SQLite。Python / TypeScript / Go 使用 `{aun_path}/AIDs/{aid}/agent.md` 与 `agentmd.json`;浏览器 JavaScript 使用 IndexedDB 等价 key,存储不可用时退化为内存缓存。
|
|
120
|
+
|
|
121
|
+
`remote_etag` / `last_modified` 除了来自 `check_agent_md()` 的 HEAD,也会由连接后的内部观察器更新:SDK 会读取 RPC response / event push `_meta.agent_md_etags` 的 `requester`、`peer`、`group` 及兼容别名,并读取 V2 envelope 的 `agent_md.sender` / `agent_md.group`。`group` 记录使用群自身 `group_aid` / `group_id` 作为 AID key。
|
|
122
|
+
|
|
123
|
+
> **v0.4.2 变更**:`discoveryPort` 配置项已移除,Gateway 地址完全由 SDK 根据 AID issuer 自动发现,无需手动指定端口。
|
|
124
124
|
|
|
125
125
|
---
|
|
126
126
|
|
|
@@ -262,7 +262,7 @@ await client.call("meta.trust_roots", {})
|
|
|
262
262
|
```python
|
|
263
263
|
await client.notify("notification/client.activity", {"state": "idle"})
|
|
264
264
|
await client.notify("event/app.typing", {"thread_id": "t1"}, to="bob.agentid.pub", ttl_ms=5000)
|
|
265
|
-
await client.notify("event/app.presence", {"state": "active"}, group_id="g-abc123.agentid.pub")
|
|
265
|
+
await client.notify("event/app.presence", {"state": "active"}, group_id="g-abc123.agentid.pub")
|
|
266
266
|
```
|
|
267
267
|
|
|
268
268
|
路由选项:
|
|
@@ -270,7 +270,7 @@ await client.notify("event/app.presence", {"state": "active"}, group_id="g-abc12
|
|
|
270
270
|
| 选项 | 说明 |
|
|
271
271
|
|------|------|
|
|
272
272
|
| `to` / `To` | 目标 AID;可同域或跨域 |
|
|
273
|
-
| `group_id` / `groupId` / `GroupID` | 目标群;兼容参数名,值使用目标态 `group_aid`,与 `to` 互斥 |
|
|
273
|
+
| `group_id` / `groupId` / `GroupID` | 目标群;兼容参数名,值使用目标态 `group_aid`,与 `to` 互斥 |
|
|
274
274
|
| `device_id` / `deviceId` / `DeviceID` | 限定目标 AID 的在线设备;必须配合 `to` |
|
|
275
275
|
| `slot_id` / `slotId` / `SlotID` | 限定目标设备的在线 slot;必须配合 `device_id` |
|
|
276
276
|
| `ttl_ms` / `ttlMs` / `TTLMS` | `0..60000`,只控制在线投递过期,不表示离线缓存 |
|
|
@@ -309,11 +309,11 @@ headers = client.get_protected_headers()
|
|
|
309
309
|
|
|
310
310
|
说明:
|
|
311
311
|
|
|
312
|
-
- agent.md 上传、下载和检查入口都在 `AIDStore`;`AUNClient` 不再暴露上传入口。
|
|
313
|
-
- 上传要求目标 AID 已在本地加载且私钥有效;SDK 会对正文签名,并通过 `AuthFlow` 获取或复用该 AID 的 access_token。
|
|
314
|
-
- SDK 发起 GET 时只发送 `Accept: text/markdown`,不主动发送 `If-None-Match` / `If-Modified-Since`。如果服务端异常返回 304,本地有内容则复用;无内容时再发一次无条件 GET。
|
|
315
|
-
- SDK 会自动从 Gateway `_meta.agent_md_etags` 和信封 `agent_md` 观察远端版本;`requester`、`peer`、`group` 是标准角色键,`receiver`、`target`、`to`、`sender`、`from` 是兼容别名。
|
|
316
|
-
- `Accept: text/markdown` 与 agent.md 的 YAML frontmatter + Markdown 格式兼容;agent.md 仍是 Markdown 媒体类型上的结构化约定。
|
|
312
|
+
- agent.md 上传、下载和检查入口都在 `AIDStore`;`AUNClient` 不再暴露上传入口。
|
|
313
|
+
- 上传要求目标 AID 已在本地加载且私钥有效;SDK 会对正文签名,并通过 `AuthFlow` 获取或复用该 AID 的 access_token。
|
|
314
|
+
- SDK 发起 GET 时只发送 `Accept: text/markdown`,不主动发送 `If-None-Match` / `If-Modified-Since`。如果服务端异常返回 304,本地有内容则复用;无内容时再发一次无条件 GET。
|
|
315
|
+
- SDK 会自动从 Gateway `_meta.agent_md_etags` 和信封 `agent_md` 观察远端版本;`requester`、`peer`、`group` 是标准角色键,`receiver`、`target`、`to`、`sender`、`from` 是兼容别名。
|
|
316
|
+
- `Accept: text/markdown` 与 agent.md 的 YAML frontmatter + Markdown 格式兼容;agent.md 仍是 Markdown 媒体类型上的结构化约定。
|
|
317
317
|
|
|
318
318
|
---
|
|
319
319
|
|
|
@@ -347,13 +347,278 @@ headers = client.get_protected_headers()
|
|
|
347
347
|
- `getInfo()` — 查询群组信息(扁平化格式,提升常用字段到顶层),**推荐外部使用**
|
|
348
348
|
- `info()` — 查询群组详细信息(带权限控制,非成员只能看公开群,成员能看 seq/epoch 等运行时状态)
|
|
349
349
|
|
|
350
|
-
**群设置便利方法**:`GroupFacade`
|
|
350
|
+
**群设置便利方法**:`GroupFacade` 提供向后兼容的便利方法:
|
|
351
351
|
|
|
352
352
|
- `getAnnouncement()` / `updateAnnouncement()` — 群公告
|
|
353
353
|
- `getRules()` / `updateRules()` — 群规则
|
|
354
354
|
- `getJoinRequirements()` / `updateJoinRequirements()` — 入群要求
|
|
355
355
|
|
|
356
|
-
|
|
356
|
+
读取方法优先返回 SDK 本地缓存;本地没有对应值时才读取相应 settings 做初始化。便利读取从服务端拿到 canonical `group_aid` 后,会同时以 canonical `group_aid` 和本次入参 `group_id` 写入 settings cache,避免 legacy/base `group_id` 下一次读取直接 cache miss。即使 `checkGroupIndex` 观察到远端 etag 与本地 etag 不一致,`getAnnouncement()` / `getRules()` / `getJoinRequirements()` 也不会自动拉取远端版本覆盖本地缓存。`updateAnnouncement()` / `updateRules()` / `updateJoinRequirements()` 属于 indexed 写入,内部会调用 `updateGroupIndex` 生成签名 `group.index` 并带 `expected_index_etag` CAS 提交。
|
|
357
|
+
|
|
358
|
+
**Group Index 高级同步方法**:`group.index` 是 SDK 内部签名 manifest,用于记录群公告、群规则、入群要求及附件稳定引用的版本。SDK 观察 `_meta.group_indexes` 后只记录远端 etag;etag 不一致只表示本地与观察到的远端版本不同,可能是远端更新,也可能是本地有未提交修改。应用层需要显式选择 pull 远端或 push 本地。
|
|
359
|
+
|
|
360
|
+
| 语义 | Python | TS/JS | Go | 说明 |
|
|
361
|
+
|------|--------|-------|----|------|
|
|
362
|
+
| 检查 index 是否不同步 | `client.group.check_group_index({...})` | `client.group.checkGroupIndex({...})` | `client.Group().CheckGroupIndex(ctx, params)` | 本地判断,不发网络请求;返回 `local_found/remote_found/local_etag/remote_etag/in_sync/needs_update/last_modified/status/cached` |
|
|
363
|
+
| 显式 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 写入本地缓存 |
|
|
364
|
+
| 显式 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 |
|
|
365
|
+
|
|
366
|
+
`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`。
|
|
367
|
+
|
|
368
|
+
#### checkGroupIndex — 检查 index 同步状态
|
|
369
|
+
|
|
370
|
+
**本地判断**,不发网络请求。基于 SDK 观察到的 `_meta.group_indexes` 远端 etag 与本地缓存 etag 对比,返回同步状态。
|
|
371
|
+
|
|
372
|
+
**参数**:
|
|
373
|
+
|
|
374
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
375
|
+
|------|------|:----:|------|
|
|
376
|
+
| `group_id` | string | ✅ | 群组标识(支持 `group_aid` 格式) |
|
|
377
|
+
|
|
378
|
+
**返回值**:
|
|
379
|
+
|
|
380
|
+
```python
|
|
381
|
+
{
|
|
382
|
+
"group_id": "g-team.agentid.pub",
|
|
383
|
+
"group_aid": "g-team.agentid.pub",
|
|
384
|
+
"local_found": true, # 本地是否有缓存 etag
|
|
385
|
+
"remote_found": true, # 是否观察到远端 _meta.group_indexes
|
|
386
|
+
"local_etag": "\"sha256:...\"", # 本地缓存 etag(带引号)
|
|
387
|
+
"remote_etag": "\"sha256:...\"", # 远端 etag(带引号)
|
|
388
|
+
"in_sync": false, # local_etag == remote_etag
|
|
389
|
+
"needs_update": true, # remote_found && !in_sync(建议 pull)
|
|
390
|
+
"last_modified": 1780000000000, # 远端 last_modified(若有)
|
|
391
|
+
"schema": "aun.group.index.v1", # 远端 schema(若有)
|
|
392
|
+
"status": "stale" # "fresh" / "stale" / "unknown"
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
**使用场景**:
|
|
397
|
+
|
|
398
|
+
- 群列表展示同步状态图标(如"本地有未同步修改"或"远端有更新")
|
|
399
|
+
- 判断是否需要调用 `getGroupIndex` pull 远端
|
|
400
|
+
|
|
401
|
+
**示例**:
|
|
402
|
+
|
|
403
|
+
```python
|
|
404
|
+
# Python
|
|
405
|
+
status = await client.group.check_group_index(group_id="g-team.agentid.pub")
|
|
406
|
+
if status["needs_update"]:
|
|
407
|
+
print("远端有更新,建议 pull")
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
// TypeScript/JavaScript
|
|
412
|
+
const status = await client.group.checkGroupIndex({ group_id: 'g-team.agentid.pub' });
|
|
413
|
+
if (status.needs_update) {
|
|
414
|
+
console.log('远端有更新,建议 pull');
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
```go
|
|
419
|
+
// Go
|
|
420
|
+
status, err := client.Group().CheckGroupIndex(ctx, map[string]any{
|
|
421
|
+
"group_id": "g-team.agentid.pub",
|
|
422
|
+
})
|
|
423
|
+
if status["needs_update"].(bool) {
|
|
424
|
+
fmt.Println("远端有更新,建议 pull")
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
#### getGroupIndex — 拉取远端 index 并更新本地缓存
|
|
431
|
+
|
|
432
|
+
调用 `group.get_settings(keys=["group.index"])` 摘取远端签名 manifest,验签后按 entry etag 只拉取变化的 indexed settings,更新本地缓存。
|
|
433
|
+
|
|
434
|
+
**参数**:
|
|
435
|
+
|
|
436
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
437
|
+
|------|------|:----:|------|
|
|
438
|
+
| `group_id` | string | ✅ | 群组标识(支持 `group_aid` 格式) |
|
|
439
|
+
|
|
440
|
+
**返回值**:
|
|
441
|
+
|
|
442
|
+
```python
|
|
443
|
+
{
|
|
444
|
+
"group_id": "g-team.agentid.pub",
|
|
445
|
+
"group_aid": "g-team.agentid.pub",
|
|
446
|
+
"group_index": { # 完整的 group.index 值
|
|
447
|
+
"body": "...", # 签名 JSONL 原文
|
|
448
|
+
"meta": {...}, # 解析出的 meta 行
|
|
449
|
+
"entries": [...] # 解析出的 entries 行数组
|
|
450
|
+
},
|
|
451
|
+
"meta": { # 从 meta 行提取的关键字段
|
|
452
|
+
"etag": "\"sha256:...\"",
|
|
453
|
+
"last_modified": 1780000000000,
|
|
454
|
+
"schema": "aun.group.index.v1"
|
|
455
|
+
},
|
|
456
|
+
"entries": [...], # 同 group_index.entries
|
|
457
|
+
"settings": { # 水合后的 indexed settings 值
|
|
458
|
+
"rules.content": "...",
|
|
459
|
+
"announcement.content": "...",
|
|
460
|
+
...
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
**行为**:
|
|
466
|
+
|
|
467
|
+
1. 调用 `group.get_settings(keys=["group.index"])` 获取远端 manifest
|
|
468
|
+
2. 解析 JSONL,验证 `signed_by` / `body_hash` / `etag` / 签名(当前仅支持 **ECDSA-P256-SHA256**)
|
|
469
|
+
3. 按 entry etag 对比本地缓存,只拉取变化的 settings(如 `rules.content`、`announcement.content` 等)
|
|
470
|
+
4. 持久化 `index.jsonl` 和 `group-index-cache.json`(或 IndexedDB)
|
|
471
|
+
5. 调用 `client.mark_group_index_fresh(group_aid, etag)` 标记本地与远端同步
|
|
472
|
+
|
|
473
|
+
**错误处理**:
|
|
474
|
+
|
|
475
|
+
- **签名验证失败**:抛异常,不更新本地缓存
|
|
476
|
+
- **不支持的 `sig_alg`**:当前四语言 SDK 仅支持 `ECDSA-P256-SHA256`,其他算法(Ed25519/RSA)会被拒绝
|
|
477
|
+
- **网络错误**:透传底层 RPC 错误
|
|
478
|
+
|
|
479
|
+
**使用场景**:
|
|
480
|
+
|
|
481
|
+
- 群成员首次进群后拉取群公告、群规则
|
|
482
|
+
- `checkGroupIndex` 发现远端有更新时主动 pull
|
|
483
|
+
- 冲突解决:放弃本地修改,以远端为准
|
|
484
|
+
|
|
485
|
+
**示例**:
|
|
486
|
+
|
|
487
|
+
```python
|
|
488
|
+
# Python
|
|
489
|
+
result = await client.group.get_group_index(group_id="g-team.agentid.pub")
|
|
490
|
+
print(f"拉取成功,etag: {result['meta']['etag']}")
|
|
491
|
+
print(f"群公告: {result['settings'].get('announcement.content')}")
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
```typescript
|
|
495
|
+
// TypeScript/JavaScript
|
|
496
|
+
const result = await client.group.getGroupIndex({ group_id: 'g-team.agentid.pub' });
|
|
497
|
+
console.log(`拉取成功,etag: ${result.meta.etag}`);
|
|
498
|
+
console.log(`群公告: ${result.settings['announcement.content']}`);
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
```go
|
|
502
|
+
// Go
|
|
503
|
+
result, err := client.Group().GetGroupIndex(ctx, map[string]any{
|
|
504
|
+
"group_id": "g-team.agentid.pub",
|
|
505
|
+
})
|
|
506
|
+
if err != nil {
|
|
507
|
+
log.Fatal(err)
|
|
508
|
+
}
|
|
509
|
+
fmt.Printf("拉取成功,etag: %s\n", result["meta"].(map[string]any)["etag"])
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
---
|
|
513
|
+
|
|
514
|
+
#### updateGroupIndex — 推送本地 indexed settings 修改
|
|
515
|
+
|
|
516
|
+
在远端基线上合并本地 indexed settings 修改,生成签名 `group.index` 后通过 CAS(Compare-And-Swap)机制提交。支持自动重试 etag 冲突。
|
|
517
|
+
|
|
518
|
+
**参数**:
|
|
519
|
+
|
|
520
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
521
|
+
|------|------|:----:|------|
|
|
522
|
+
| `group_id` | string | ✅ | 群组标识(支持 `group_aid` 格式) |
|
|
523
|
+
| `settings` | object | ✅ | 要更新的 indexed settings(key-value 对象) |
|
|
524
|
+
| `signer` | AID | ❌ | 签名者身份(默认 `client.current_aid`) |
|
|
525
|
+
| `last_modified` | int | ❌ | 时间戳毫秒(默认 `Date.now()` / `time.time()*1000`) |
|
|
526
|
+
| `max_attempts` | int | ❌ | CAS 冲突最大重试次数(默认 2) |
|
|
527
|
+
|
|
528
|
+
**支持的 indexed settings keys**:
|
|
529
|
+
|
|
530
|
+
- `rules.content` / `rules.attachments` — 群规则及附件
|
|
531
|
+
- `announcement.content` / `announcement.attachments` — 群公告及附件
|
|
532
|
+
- `join.mode` / `join.question` / `join.auto_approve_patterns` / `join.max_pending` — 入群要求
|
|
533
|
+
|
|
534
|
+
**返回值**:
|
|
535
|
+
|
|
536
|
+
```python
|
|
537
|
+
{
|
|
538
|
+
"group_id": "g-team.agentid.pub",
|
|
539
|
+
"group_aid": "g-team.agentid.pub",
|
|
540
|
+
"updated_keys": ["announcement.content", "group.index"],
|
|
541
|
+
"_meta": {
|
|
542
|
+
"group_indexes": {
|
|
543
|
+
"g-team.agentid.pub": {
|
|
544
|
+
"etag": "\"sha256:...\"", # 推送成功后的新 etag
|
|
545
|
+
"last_modified": 1780000000000,
|
|
546
|
+
"schema": "aun.group.index.v1"
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
**行为**:
|
|
554
|
+
|
|
555
|
+
1. 调用 `group.get_settings(keys=["group.index"])` 获取当前远端 etag(作为 CAS 基线)
|
|
556
|
+
2. 解析远端 `group.index` 的 entries,保留不在 `settings` 中的条目
|
|
557
|
+
3. 为 `settings` 中每个 key 计算新的 entry(包含 `etag: "sha256:<value的sha256>"`)
|
|
558
|
+
4. 合并远端保留条目和新 entries,生成新的 canonical JSONL
|
|
559
|
+
5. 用 `signer` 签名生成完整的 `group.index`(包含 meta 行的 `signature` 字段)
|
|
560
|
+
6. 调用 `group.set_settings(settings={...修改的key..., "group.index": {...}}, expected_index_etag=<远端etag>)`
|
|
561
|
+
7. **CAS 冲突自动重试**:若返回 "etag conflict" 错误,回到步骤 1 重新拉取基线(最多 `max_attempts` 次)
|
|
562
|
+
8. 推送成功后调用 `client.mark_group_index_fresh()` 和 `client.cache_group_index_settings()` 更新本地缓存
|
|
563
|
+
|
|
564
|
+
**错误处理**:
|
|
565
|
+
|
|
566
|
+
- **CAS 冲突重试耗尽**:抛出最后一次的 "etag conflict" 异常
|
|
567
|
+
- **非 CAS 错误**:立即抛出(如权限不足、签名失败)
|
|
568
|
+
- **`signer` 与 RPC `actor` 不一致**:服务端会拒绝(`signed_by` 必须等于 `actor_aid`)
|
|
569
|
+
|
|
570
|
+
**使用场景**:
|
|
571
|
+
|
|
572
|
+
- 群主/管理员修改群公告、群规则后推送
|
|
573
|
+
- 冲突解决:本地修改优先,覆盖远端(若冲突次数超限需人工介入)
|
|
574
|
+
|
|
575
|
+
**示例**:
|
|
576
|
+
|
|
577
|
+
```python
|
|
578
|
+
# Python - 更新群公告
|
|
579
|
+
result = await client.group.update_group_index(
|
|
580
|
+
group_id="g-team.agentid.pub",
|
|
581
|
+
settings={
|
|
582
|
+
"announcement.content": "新公告内容",
|
|
583
|
+
"announcement.attachments": []
|
|
584
|
+
}
|
|
585
|
+
)
|
|
586
|
+
print(f"推送成功,新 etag: {result['_meta']['group_indexes']['g-team.agentid.pub']['etag']}")
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
```typescript
|
|
590
|
+
// TypeScript/JavaScript - 更新群规则
|
|
591
|
+
const result = await client.group.updateGroupIndex({
|
|
592
|
+
group_id: 'g-team.agentid.pub',
|
|
593
|
+
settings: {
|
|
594
|
+
'rules.content': '1. 禁止广告\n2. 尊重他人',
|
|
595
|
+
'rules.attachments': []
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
console.log(`推送成功,新 etag: ${result._meta.group_indexes['g-team.agentid.pub'].etag}`);
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
```go
|
|
602
|
+
// Go - 更新入群要求
|
|
603
|
+
result, err := client.Group().UpdateGroupIndex(ctx, map[string]any{
|
|
604
|
+
"group_id": "g-team.agentid.pub",
|
|
605
|
+
"settings": map[string]any{
|
|
606
|
+
"join.mode": "approval",
|
|
607
|
+
"join.question": "你是如何知道本群的?",
|
|
608
|
+
},
|
|
609
|
+
})
|
|
610
|
+
if err != nil {
|
|
611
|
+
log.Fatal(err)
|
|
612
|
+
}
|
|
613
|
+
fmt.Printf("推送成功\n")
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
**注意事项**:
|
|
617
|
+
|
|
618
|
+
1. **权限要求**:写入 indexed settings 需要 admin 及以上权限
|
|
619
|
+
2. **签名算法限制**:当前版本仅支持 ECDSA-P256-SHA256,使用其他算法的 AID 无法签名
|
|
620
|
+
3. **CAS 冲突策略**:默认重试 2 次,高并发场景建议增加 `max_attempts`
|
|
621
|
+
4. **`signer` 必须是当前连接身份**:服务端强制校验 `signed_by == actor_aid`,传入其他 AID 会被拒绝
|
|
357
622
|
|
|
358
623
|
---
|
|
359
624
|
|
|
@@ -418,16 +683,16 @@ sub.unsubscribe()
|
|
|
418
683
|
|
|
419
684
|
| 事件 | 说明 |
|
|
420
685
|
|------|------|
|
|
421
|
-
| `state_change` | 状态变化,`state` 为九态公开值 |
|
|
422
|
-
| `connection.error` | 连接或重连错误 |
|
|
423
|
-
| `token.refreshed` | token 刷新完成 |
|
|
424
|
-
| `token.refresh_exhausted` | refresh_token 缺失、过期或刷新链耗尽,SDK 已清理本地 token 并等待重新登录 |
|
|
425
|
-
| `message.received` | 收到 P2P 消息 |
|
|
426
|
-
| `message.ack` | 消息 ack |
|
|
427
|
-
| `message.undecryptable` | P2P E2EE 解密失败 |
|
|
428
|
-
| `group.changed` | 群组事件 |
|
|
429
|
-
| `group.message_undecryptable` | 群 E2EE 解密失败 |
|
|
430
|
-
| `storage.object_changed` | Storage 对象变更事件透传 |
|
|
686
|
+
| `state_change` | 状态变化,`state` 为九态公开值 |
|
|
687
|
+
| `connection.error` | 连接或重连错误 |
|
|
688
|
+
| `token.refreshed` | token 刷新完成 |
|
|
689
|
+
| `token.refresh_exhausted` | refresh_token 缺失、过期或刷新链耗尽,SDK 已清理本地 token 并等待重新登录 |
|
|
690
|
+
| `message.received` | 收到 P2P 消息 |
|
|
691
|
+
| `message.ack` | 消息 ack |
|
|
692
|
+
| `message.undecryptable` | P2P E2EE 解密失败 |
|
|
693
|
+
| `group.changed` | 群组事件 |
|
|
694
|
+
| `group.message_undecryptable` | 群 E2EE 解密失败 |
|
|
695
|
+
| `storage.object_changed` | Storage 对象变更事件透传 |
|
|
431
696
|
|
|
432
697
|
---
|
|
433
698
|
|
|
@@ -60,11 +60,13 @@ except AUNError as e:
|
|
|
60
60
|
| -32008 | 资源不存在 | `NotFoundError` |
|
|
61
61
|
| -32009 | 版本冲突 | `VersionConflictError` |
|
|
62
62
|
| -32010 / -32011 / -32013 | 会话错误 | `SessionError` |
|
|
63
|
-
| -32051 | 客户端签名验证失败 | `ClientSignatureError`(继承自 `ValidationError`) |
|
|
64
|
-
| -32029 | 请求限流(目标/联邦维度) | `RateLimitError` |
|
|
65
|
-
| -32429 | 请求限流(Gateway 入口背压,排队超时) | `RateLimitError` |
|
|
66
|
-
| -32600 / -32601 / -32602 | JSON-RPC 参数错误 | `ValidationError` |
|
|
67
|
-
| -
|
|
63
|
+
| -32051 | 客户端签名验证失败 | `ClientSignatureError`(继承自 `ValidationError`) |
|
|
64
|
+
| -32029 | 请求限流(目标/联邦维度) | `RateLimitError` |
|
|
65
|
+
| -32429 | 请求限流(Gateway 入口背压,排队超时) | `RateLimitError` |
|
|
66
|
+
| -32600 / -32601 / -32602 | JSON-RPC 参数错误 | `ValidationError` |
|
|
67
|
+
| -32602 且消息包含 `group.index etag conflict` | group.index CAS 冲突 | 应重新 `getGroupIndex`、合并、签名后重试 |
|
|
68
|
+
| -32602 且消息包含 `group.index` 签名/schema/hash/etag 错误 | group.index 校验失败 | 检查 signer、canonical JSONL、`body_hash`、`etag` 和 `signature` |
|
|
69
|
+
| -32040 ~ -32044 | E2EE 群组错误 | `E2EEError` 子类 |
|
|
68
70
|
| 4090 | 身份冲突 | `IdentityConflictError` |
|
|
69
71
|
| -32050 | 证书已吊销 | `CertificateRevokedError` |
|
|
70
72
|
| -33001 | 群组不存在 | `GroupNotFoundError` |
|
|
@@ -154,5 +156,27 @@ if not client.can_send:
|
|
|
154
156
|
### E2EE 解密失败
|
|
155
157
|
|
|
156
158
|
P2P 或群消息解密失败通常由 prekey 不匹配、AAD 篡改、密文损坏或群 epoch 不一致引起。SDK 会发布 `message.undecryptable` / `group.message_undecryptable` 事件,应用可记录并继续处理其他消息。
|
|
157
|
-
|
|
159
|
+
|
|
160
|
+
### group.index CAS 冲突
|
|
161
|
+
|
|
162
|
+
多个 owner/admin 并发修改公告、规则或入群要求时,服务端通过 `expected_index_etag` 做 CAS。冲突时错误消息包含 `group.index etag conflict`。
|
|
163
|
+
|
|
164
|
+
处理方式:
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
try:
|
|
168
|
+
await client.group.update_group_index({
|
|
169
|
+
"group_id": group_aid,
|
|
170
|
+
"settings": {"announcement.content": "new text"},
|
|
171
|
+
})
|
|
172
|
+
except Exception as exc:
|
|
173
|
+
if "etag conflict" in str(exc):
|
|
174
|
+
latest = await client.group.get_group_index({"group_id": group_aid})
|
|
175
|
+
# 在 latest["entries"] 上合并本地修改,然后重新 update_group_index
|
|
176
|
+
raise
|
|
177
|
+
raise
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
通常优先使用 SDK 的 `updateGroupIndex` facade;它会按 `max_attempts` 自动重读当前 index 并重试。超过重试次数仍冲突时,把冲突交给应用层做合并策略。
|
|
181
|
+
|
|
158
182
|
|
|
@@ -86,9 +86,9 @@ client.set_protected_headers({"sdk": "python", "trace": "abc"})
|
|
|
86
86
|
|
|
87
87
|
---
|
|
88
88
|
|
|
89
|
-
## 6. Flow Control
|
|
90
|
-
|
|
91
|
-
SDK 内部自动管理 RPC 并发,应用层无需配置:
|
|
89
|
+
## 6. Flow Control
|
|
90
|
+
|
|
91
|
+
SDK 内部自动管理 RPC 并发,应用层无需配置:
|
|
92
92
|
|
|
93
93
|
| 机制 | 说明 |
|
|
94
94
|
|------|------|
|
|
@@ -97,15 +97,31 @@ SDK 内部自动管理 RPC 并发,应用层无需配置:
|
|
|
97
97
|
| Pull Gate | 同一 namespace/group 的 pull 操作自动序列化 |
|
|
98
98
|
| 队列超时 | 排队超过 timeout 抛 `TimeoutError` |
|
|
99
99
|
|
|
100
|
-
应用层保持普通 `await client.call(...)` 即可。
|
|
101
|
-
|
|
102
|
-
---
|
|
103
|
-
|
|
104
|
-
## 7.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
100
|
+
应用层保持普通 `await client.call(...)` 即可。
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 7. Group Index 更新流程
|
|
105
|
+
|
|
106
|
+
公告、规则、入群要求属于 indexed settings。owner/admin 修改这些内容时,不要直接裸调 `group.set_settings` 写单个 key,应使用 SDK facade 的 `updateGroupIndex` 语义。
|
|
107
|
+
|
|
108
|
+
推荐流程:
|
|
109
|
+
|
|
110
|
+
1. 调用 `checkGroupIndex` 判断本地已处理 etag 与观察到的远端 etag 是否不同步。
|
|
111
|
+
2. 如果不同步,由应用层决定以本地还是远端为准:选择远端时调用 `getGroupIndex` 显式 pull,选择本地时继续保留本地缓存并准备 push。
|
|
112
|
+
3. 在选定基线上合并本地要修改的 settings 和附件稳定引用。
|
|
113
|
+
4. 调用 `updateGroupIndex`。SDK 会读取当前服务端 index、生成新签名 index,并带 `expected_index_etag` 调用 `group.set_settings`。
|
|
114
|
+
5. 如果服务端返回 `group.index etag conflict`,说明提交基线已变化;应用层需要重新决定 pull、merge 或保留本地修改后再提交。
|
|
115
|
+
|
|
116
|
+
`getAnnouncement`、`getRules`、`getJoinRequirements` 只读 SDK 本地缓存;本地没有对应值时才读取相应 settings 初始化缓存,不会因为 etag 不一致自动 pull 远端。`updateAnnouncement`、`updateRules`、`updateJoinRequirements` 已内置 indexed 写入路径。普通不进入 index 的设置,例如 `dispatch_mode`,仍可直接使用 `setSettings` / `set_settings` / `SetSettings`。
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## 8. 测试数据保护
|
|
121
|
+
|
|
122
|
+
- 不要删除 `AIDs/` 下的私钥、证书、seed、数据库或 token 文件。
|
|
123
|
+
- 不要并行跑共享同一身份材料的集成 / E2E / 跨域测试。
|
|
124
|
+
- 需要换身份时使用新的 AID 名称,避免制造不可恢复的 key mismatch。
|
|
109
125
|
|
|
110
126
|
---
|
|
111
127
|
|