@agentunion/fastaun-browser 0.5.2 → 0.5.4
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 +74 -0
- package/_packed_docs/CHANGELOG.md +74 -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 +303 -28
- 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 +126 -42
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +6 -4
- package/_packed_docs/sdk/INDEX.md +17 -17
- package/dist/bundle.js +1084 -296
- 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 +9 -0
- package/dist/facades.d.ts.map +1 -1
- package/dist/facades.js +314 -77
- 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
|
@@ -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
|
|
|
@@ -329,7 +329,7 @@ headers = client.get_protected_headers()
|
|
|
329
329
|
| Collab | `client.collab` | `client.collab` | `client.Collab()` | 版本化文档、标签、`gc` / `reflog` / `revert` |
|
|
330
330
|
| 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 编排 |
|
|
331
331
|
|
|
332
|
-
群文件系统路径统一使用 `group_aid:/...`,成员数据区使用 `group_aid:/memberdata/{member_ref}/...`。SDK 不拼接真实 storage 路径,`memberdata` 到成员 `group_data/{group_aid}` 的映射只在服务端完成。群自有区写入允许当前 `group_aid`
|
|
332
|
+
群文件系统路径统一使用 `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` 当本地路径。
|
|
333
333
|
|
|
334
334
|
### 消息与群组门面
|
|
335
335
|
|
|
@@ -347,16 +347,291 @@ headers = client.get_protected_headers()
|
|
|
347
347
|
- `getInfo()` — 查询群组信息(扁平化格式,提升常用字段到顶层),**推荐外部使用**
|
|
348
348
|
- `info()` — 查询群组详细信息(带权限控制,非成员只能看公开群,成员能看 seq/epoch 等运行时状态)
|
|
349
349
|
|
|
350
|
-
**群设置便利方法**:`GroupFacade`
|
|
350
|
+
**群设置便利方法**:`GroupFacade` 提供向后兼容的便利方法:
|
|
351
|
+
|
|
352
|
+
- `getAnnouncement()` / `updateAnnouncement()` — 群公告
|
|
353
|
+
- `getRules()` / `updateRules()` — 群规则
|
|
354
|
+
- `getJoinRequirements()` / `updateJoinRequirements()` — 入群要求
|
|
355
|
+
- `getSettingWithIndex()` / `updateSettingWithIndex()` — 通用文档型 indexed setting(Python 为 `get_setting_with_index()` / `update_setting_with_index()`,Go 为 `GetSettingWithIndex()` / `UpdateSettingWithIndex()`)
|
|
356
|
+
|
|
357
|
+
读取方法优先返回 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 提交。
|
|
358
|
+
|
|
359
|
+
`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`。
|
|
360
|
+
|
|
361
|
+
这些 `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>}/...`。
|
|
362
|
+
|
|
363
|
+
**Group Index 高级同步方法**:`group.index` 是 SDK 内部签名 manifest,用于记录群公告、群规则、入群要求及附件稳定引用的版本。SDK 观察 `_meta.group_indexes` 后只记录远端 etag;etag 不一致只表示本地与观察到的远端版本不同,可能是远端更新,也可能是本地有未提交修改。应用层需要显式选择 pull 远端或 push 本地。
|
|
364
|
+
|
|
365
|
+
| 语义 | Python | TS/JS | Go | 说明 |
|
|
366
|
+
|------|--------|-------|----|------|
|
|
367
|
+
| 检查 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` |
|
|
368
|
+
| 显式 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 写入本地缓存 |
|
|
369
|
+
| 显式 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 |
|
|
370
|
+
|
|
371
|
+
`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`。
|
|
372
|
+
|
|
373
|
+
#### checkGroupIndex — 检查 index 同步状态
|
|
374
|
+
|
|
375
|
+
**本地判断**,不发网络请求。基于 SDK 观察到的 `_meta.group_indexes` 远端 etag 与本地缓存 etag 对比,返回同步状态。
|
|
376
|
+
|
|
377
|
+
**参数**:
|
|
378
|
+
|
|
379
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
380
|
+
|------|------|:----:|------|
|
|
381
|
+
| `group_id` | string | ✅ | 群组标识(支持 `group_aid` 格式) |
|
|
382
|
+
|
|
383
|
+
**返回值**:
|
|
384
|
+
|
|
385
|
+
```python
|
|
386
|
+
{
|
|
387
|
+
"group_id": "g-team.agentid.pub",
|
|
388
|
+
"group_aid": "g-team.agentid.pub",
|
|
389
|
+
"local_found": true, # 本地是否有缓存 etag
|
|
390
|
+
"remote_found": true, # 是否观察到远端 _meta.group_indexes
|
|
391
|
+
"local_etag": "\"sha256:...\"", # 本地缓存 etag(带引号)
|
|
392
|
+
"remote_etag": "\"sha256:...\"", # 远端 etag(带引号)
|
|
393
|
+
"in_sync": false, # local_etag == remote_etag
|
|
394
|
+
"needs_update": true, # remote_found && !in_sync(建议 pull)
|
|
395
|
+
"last_modified": 1780000000000, # 远端 last_modified(若有)
|
|
396
|
+
"schema": "aun.group.index.v1", # 远端 schema(若有)
|
|
397
|
+
"status": "stale" # "fresh" / "stale" / "unknown"
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
**使用场景**:
|
|
402
|
+
|
|
403
|
+
- 群列表展示同步状态图标(如"本地有未同步修改"或"远端有更新")
|
|
404
|
+
- 判断是否需要调用 `getGroupIndex` pull 远端
|
|
405
|
+
|
|
406
|
+
**示例**:
|
|
407
|
+
|
|
408
|
+
```python
|
|
409
|
+
# Python
|
|
410
|
+
status = await client.group.check_group_index(group_id="g-team.agentid.pub")
|
|
411
|
+
if status["needs_update"]:
|
|
412
|
+
print("远端有更新,建议 pull")
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
// TypeScript/JavaScript
|
|
417
|
+
const status = await client.group.checkGroupIndex({ group_id: 'g-team.agentid.pub' });
|
|
418
|
+
if (status.needs_update) {
|
|
419
|
+
console.log('远端有更新,建议 pull');
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
```go
|
|
424
|
+
// Go
|
|
425
|
+
status, err := client.Group().CheckGroupIndex(ctx, map[string]any{
|
|
426
|
+
"group_id": "g-team.agentid.pub",
|
|
427
|
+
})
|
|
428
|
+
if status["needs_update"].(bool) {
|
|
429
|
+
fmt.Println("远端有更新,建议 pull")
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
---
|
|
434
|
+
|
|
435
|
+
#### getGroupIndex — 拉取远端 index 并更新本地缓存
|
|
436
|
+
|
|
437
|
+
调用 `group.get_settings(keys=["group.index"])` 摘取远端签名 manifest,验签后按 entry etag 只拉取变化的 indexed settings,更新本地缓存。
|
|
438
|
+
|
|
439
|
+
**参数**:
|
|
440
|
+
|
|
441
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
442
|
+
|------|------|:----:|------|
|
|
443
|
+
| `group_id` | string | ✅ | 群组标识(支持 `group_aid` 格式) |
|
|
444
|
+
|
|
445
|
+
**返回值**:
|
|
446
|
+
|
|
447
|
+
```python
|
|
448
|
+
{
|
|
449
|
+
"group_id": "g-team.agentid.pub",
|
|
450
|
+
"group_aid": "g-team.agentid.pub",
|
|
451
|
+
"group_index": { # 完整的 group.index 值
|
|
452
|
+
"body": "...", # 签名 JSONL 原文
|
|
453
|
+
"meta": {...}, # 解析出的 meta 行
|
|
454
|
+
"entries": [...] # 解析出的 entries 行数组
|
|
455
|
+
},
|
|
456
|
+
"meta": { # 从 meta 行提取的关键字段
|
|
457
|
+
"etag": "\"sha256:...\"",
|
|
458
|
+
"last_modified": 1780000000000,
|
|
459
|
+
"schema": "aun.group.index.v1"
|
|
460
|
+
},
|
|
461
|
+
"entries": [...], # 同 group_index.entries
|
|
462
|
+
"settings": { # 水合后的 indexed settings 值
|
|
463
|
+
"rules.content": "...",
|
|
464
|
+
"announcement.content": "...",
|
|
465
|
+
...
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
**行为**:
|
|
351
471
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
472
|
+
1. 调用 `group.get_settings(keys=["group.index"])` 获取远端 manifest
|
|
473
|
+
2. 解析 JSONL,验证 `signed_by` / `body_hash` / `etag` / 签名(当前仅支持 **ECDSA-P256-SHA256**)
|
|
474
|
+
3. 按 entry etag 对比本地缓存,只拉取变化的 settings(如 `rules.content`、`announcement.content` 等)
|
|
475
|
+
4. 持久化 `index.jsonl` 和 `group-index-cache.json`(或 IndexedDB)
|
|
476
|
+
5. 调用 `client.mark_group_index_fresh(group_aid, etag)` 标记本地与远端同步
|
|
355
477
|
|
|
356
|
-
|
|
478
|
+
**错误处理**:
|
|
479
|
+
|
|
480
|
+
- **签名验证失败**:抛异常,不更新本地缓存
|
|
481
|
+
- **不支持的 `sig_alg`**:当前四语言 SDK 仅支持 `ECDSA-P256-SHA256`,其他算法(Ed25519/RSA)会被拒绝
|
|
482
|
+
- **网络错误**:透传底层 RPC 错误
|
|
483
|
+
|
|
484
|
+
**使用场景**:
|
|
485
|
+
|
|
486
|
+
- 群成员首次进群后拉取群公告、群规则
|
|
487
|
+
- `checkGroupIndex` 发现远端有更新时主动 pull
|
|
488
|
+
- 冲突解决:放弃本地修改,以远端为准
|
|
489
|
+
|
|
490
|
+
**示例**:
|
|
491
|
+
|
|
492
|
+
```python
|
|
493
|
+
# Python
|
|
494
|
+
result = await client.group.get_group_index(group_id="g-team.agentid.pub")
|
|
495
|
+
print(f"拉取成功,etag: {result['meta']['etag']}")
|
|
496
|
+
print(f"群公告: {result['settings'].get('announcement.content')}")
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
```typescript
|
|
500
|
+
// TypeScript/JavaScript
|
|
501
|
+
const result = await client.group.getGroupIndex({ group_id: 'g-team.agentid.pub' });
|
|
502
|
+
console.log(`拉取成功,etag: ${result.meta.etag}`);
|
|
503
|
+
console.log(`群公告: ${result.settings['announcement.content']}`);
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
```go
|
|
507
|
+
// Go
|
|
508
|
+
result, err := client.Group().GetGroupIndex(ctx, map[string]any{
|
|
509
|
+
"group_id": "g-team.agentid.pub",
|
|
510
|
+
})
|
|
511
|
+
if err != nil {
|
|
512
|
+
log.Fatal(err)
|
|
513
|
+
}
|
|
514
|
+
fmt.Printf("拉取成功,etag: %s\n", result["meta"].(map[string]any)["etag"])
|
|
515
|
+
```
|
|
357
516
|
|
|
358
517
|
---
|
|
359
518
|
|
|
519
|
+
#### updateGroupIndex — 推送本地 indexed settings 修改
|
|
520
|
+
|
|
521
|
+
在远端基线上合并本地 indexed settings 修改,生成签名 `group.index` 后通过 CAS(Compare-And-Swap)机制提交。支持自动重试 etag 冲突。
|
|
522
|
+
|
|
523
|
+
**参数**:
|
|
524
|
+
|
|
525
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
526
|
+
|------|------|:----:|------|
|
|
527
|
+
| `group_id` | string | ✅ | 群组标识(支持 `group_aid` 格式) |
|
|
528
|
+
| `settings` | object | ✅ | 要更新的 indexed settings(key-value 对象) |
|
|
529
|
+
| `signer` | AID | ❌ | 签名者身份(默认 `client.current_aid`) |
|
|
530
|
+
| `last_modified` | int | ❌ | 时间戳毫秒(默认 `Date.now()` / `time.time()*1000`) |
|
|
531
|
+
| `max_attempts` | int | ❌ | CAS 冲突最大重试次数(默认 2) |
|
|
532
|
+
|
|
533
|
+
**支持的 indexed settings keys**:
|
|
534
|
+
|
|
535
|
+
- `rules.content` / `rules.attachments` — 群规则及附件
|
|
536
|
+
- `announcement.content` / `announcement.attachments` — 群公告及附件
|
|
537
|
+
- `join.mode` / `join.question` / `join.auto_approve_patterns` / `join.max_pending` / `join.attachments` — 入群要求及附件
|
|
538
|
+
- `{keyName}.content` / `{keyName}.attachments` — 通用文档型 indexed setting;`keyName` 需满足 `^[A-Za-z][A-Za-z0-9_-]{0,63}$`,且不能使用 `join` 等保留前缀
|
|
539
|
+
|
|
540
|
+
**返回值**:
|
|
541
|
+
|
|
542
|
+
```python
|
|
543
|
+
{
|
|
544
|
+
"group_id": "g-team.agentid.pub",
|
|
545
|
+
"group_aid": "g-team.agentid.pub",
|
|
546
|
+
"updated_keys": ["announcement.content", "group.index"],
|
|
547
|
+
"_meta": {
|
|
548
|
+
"group_indexes": {
|
|
549
|
+
"g-team.agentid.pub": {
|
|
550
|
+
"etag": "\"sha256:...\"", # 推送成功后的新 etag
|
|
551
|
+
"last_modified": 1780000000000,
|
|
552
|
+
"schema": "aun.group.index.v1"
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
**行为**:
|
|
560
|
+
|
|
561
|
+
1. 调用 `group.get_settings(keys=["group.index"])` 获取当前远端 etag(作为 CAS 基线)
|
|
562
|
+
2. 解析远端 `group.index` 的 entries,保留不在 `settings` 中的条目
|
|
563
|
+
3. 为 `settings` 中每个 key 计算新的 entry(包含 `etag: "sha256:<value的sha256>"`)
|
|
564
|
+
4. 合并远端保留条目和新 entries,生成新的 canonical JSONL
|
|
565
|
+
5. 用 `signer` 签名生成完整的 `group.index`(包含 meta 行的 `signature` 字段)
|
|
566
|
+
6. 调用 `group.set_settings(settings={...修改的key..., "group.index": {...}}, expected_index_etag=<远端etag>)`
|
|
567
|
+
7. **CAS 冲突自动重试**:若返回 "etag conflict" 错误,回到步骤 1 重新拉取基线(最多 `max_attempts` 次)
|
|
568
|
+
8. 推送成功后调用 `client.mark_group_index_fresh()` 和 `client.cache_group_index_settings()` 更新本地缓存
|
|
569
|
+
|
|
570
|
+
**错误处理**:
|
|
571
|
+
|
|
572
|
+
- **CAS 冲突重试耗尽**:抛出最后一次的 "etag conflict" 异常
|
|
573
|
+
- **非 CAS 错误**:立即抛出(如权限不足、签名失败)
|
|
574
|
+
- **`signer` 与 RPC `actor` 不一致**:服务端会拒绝(`signed_by` 必须等于 `actor_aid`)
|
|
575
|
+
|
|
576
|
+
**使用场景**:
|
|
577
|
+
|
|
578
|
+
- 群主/管理员修改群公告、群规则后推送
|
|
579
|
+
- 冲突解决:本地修改优先,覆盖远端(若冲突次数超限需人工介入)
|
|
580
|
+
|
|
581
|
+
**示例**:
|
|
582
|
+
|
|
583
|
+
```python
|
|
584
|
+
# Python - 更新群公告
|
|
585
|
+
result = await client.group.update_group_index(
|
|
586
|
+
group_id="g-team.agentid.pub",
|
|
587
|
+
settings={
|
|
588
|
+
"announcement.content": "新公告内容",
|
|
589
|
+
"announcement.attachments": []
|
|
590
|
+
}
|
|
591
|
+
)
|
|
592
|
+
print(f"推送成功,新 etag: {result['_meta']['group_indexes']['g-team.agentid.pub']['etag']}")
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
```typescript
|
|
596
|
+
// TypeScript/JavaScript - 更新群规则
|
|
597
|
+
const result = await client.group.updateGroupIndex({
|
|
598
|
+
group_id: 'g-team.agentid.pub',
|
|
599
|
+
settings: {
|
|
600
|
+
'rules.content': '1. 禁止广告\n2. 尊重他人',
|
|
601
|
+
'rules.attachments': []
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
console.log(`推送成功,新 etag: ${result._meta.group_indexes['g-team.agentid.pub'].etag}`);
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
```go
|
|
608
|
+
// Go - 更新入群要求
|
|
609
|
+
result, err := client.Group().UpdateGroupIndex(ctx, map[string]any{
|
|
610
|
+
"group_id": "g-team.agentid.pub",
|
|
611
|
+
"settings": map[string]any{
|
|
612
|
+
"join.mode": "approval",
|
|
613
|
+
"join.question": "你是如何知道本群的?",
|
|
614
|
+
"join.attachments": []any{
|
|
615
|
+
map[string]any{"type": "group.fs", "path": "/.group/attachments/join/guide.pdf"},
|
|
616
|
+
},
|
|
617
|
+
},
|
|
618
|
+
})
|
|
619
|
+
if err != nil {
|
|
620
|
+
log.Fatal(err)
|
|
621
|
+
}
|
|
622
|
+
fmt.Printf("推送成功\n")
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
**注意事项**:
|
|
626
|
+
|
|
627
|
+
1. **权限要求**:写入 indexed settings 需要 admin 及以上权限
|
|
628
|
+
2. **签名算法限制**:当前版本仅支持 ECDSA-P256-SHA256,使用其他算法的 AID 无法签名
|
|
629
|
+
3. **CAS 冲突策略**:默认重试 2 次,高并发场景建议增加 `max_attempts`
|
|
630
|
+
4. **`signer` 必须是当前连接身份**:服务端强制校验 `signed_by == actor_aid`,传入其他 AID 会被拒绝
|
|
631
|
+
5. **附件字段只存引用**:`updateRules` / `updateAnnouncement` / `updateSettingWithIndex` 传入 `content` 和 `attachments` 元数据引用;`updateJoinRequirements` 传入结构化入群字段和 `attachments` 元数据引用。附件实体先上传到群自有区,推荐路径为 `group_aid:/.group/attachments/{rules|announcement|join|<keyName>}/...`。群自有区默认允许 `owner/admin` 写入,`member` 默认不可写;`.group/` 不允许授予 `role:member` 写权限。
|
|
632
|
+
|
|
633
|
+
---
|
|
634
|
+
|
|
360
635
|
## ServiceProxyClient
|
|
361
636
|
|
|
362
637
|
Service Proxy 用于 provider 通过 AUN 身份暴露本地 HTTP / WebSocket 服务。当前公开封装在 Python SDK 的 `ServiceProxyClient` 中;其它语言可以按 [09-proxy-rpc-manual.md](09-proxy-rpc-manual.md) 直接实现同等控制面和隧道消息。
|
|
@@ -418,16 +693,16 @@ sub.unsubscribe()
|
|
|
418
693
|
|
|
419
694
|
| 事件 | 说明 |
|
|
420
695
|
|------|------|
|
|
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 对象变更事件透传 |
|
|
696
|
+
| `state_change` | 状态变化,`state` 为九态公开值 |
|
|
697
|
+
| `connection.error` | 连接或重连错误 |
|
|
698
|
+
| `token.refreshed` | token 刷新完成 |
|
|
699
|
+
| `token.refresh_exhausted` | refresh_token 缺失、过期或刷新链耗尽,SDK 已清理本地 token 并等待重新登录 |
|
|
700
|
+
| `message.received` | 收到 P2P 消息 |
|
|
701
|
+
| `message.ack` | 消息 ack |
|
|
702
|
+
| `message.undecryptable` | P2P E2EE 解密失败 |
|
|
703
|
+
| `group.changed` | 群组事件 |
|
|
704
|
+
| `group.message_undecryptable` | 群 E2EE 解密失败 |
|
|
705
|
+
| `storage.object_changed` | Storage 对象变更事件透传 |
|
|
431
706
|
|
|
432
707
|
---
|
|
433
708
|
|
|
@@ -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
|
|