@agentunion/fastaun 0.5.0 → 0.5.1

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 (91) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/_packed_docs/CHANGELOG-validators.md +144 -0
  3. package/_packed_docs/CHANGELOG.md +59 -0
  4. package/_packed_docs/INDEX.md +198 -181
  5. package/_packed_docs/KITE_DOCS_GUIDE.md +26 -23
  6. package/_packed_docs/cli/AUN-CLI/350/256/276/350/256/241/346/226/207/346/241/243.md +4 -3
  7. package/_packed_docs/cli/CLI/346/211/213/345/206/214.md +331 -0
  8. package/_packed_docs/protocol/08-AUN-E2EE-Group.md +296 -902
  9. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +64 -114
  10. package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +7 -1
  11. package/_packed_docs/protocol/16-/347/263/273/347/273/237/347/233/256/345/275/225/344/277/235/346/212/244/346/226/271/346/241/210.md +177 -0
  12. package/_packed_docs/protocol/README.md +2 -1
  13. package/_packed_docs/protocol/index.md +8 -3
  14. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +4 -252
  15. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +483 -457
  16. package/_packed_docs/sdk/09-collab-rpc-manual.md +581 -550
  17. package/_packed_docs/sdk/09-group-rpc-manual.md +248 -335
  18. package/_packed_docs/sdk/09-storage-rpc-manual.md +56 -19
  19. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +13 -13
  20. package/_packed_docs/sdk/INDEX.md +14 -14
  21. package/dist/client/delivery.d.ts +9 -0
  22. package/dist/client/delivery.js +157 -24
  23. package/dist/client/delivery.js.map +1 -1
  24. package/dist/client/group-state.js +33 -11
  25. package/dist/client/group-state.js.map +1 -1
  26. package/dist/client/rpc-pipeline.d.ts +1 -0
  27. package/dist/client/rpc-pipeline.js +127 -40
  28. package/dist/client/rpc-pipeline.js.map +1 -1
  29. package/dist/client/v2-e2ee.d.ts +6 -1
  30. package/dist/client/v2-e2ee.js +199 -130
  31. package/dist/client/v2-e2ee.js.map +1 -1
  32. package/dist/client.d.ts +8 -1
  33. package/dist/client.js +227 -31
  34. package/dist/client.js.map +1 -1
  35. package/dist/collab/client.d.ts +8 -0
  36. package/dist/collab/client.js +12 -0
  37. package/dist/collab/client.js.map +1 -1
  38. package/dist/errors.d.ts +0 -20
  39. package/dist/errors.js +8 -53
  40. package/dist/errors.js.map +1 -1
  41. package/dist/facades.d.ts +9 -4
  42. package/dist/facades.js +137 -15
  43. package/dist/facades.js.map +1 -1
  44. package/dist/group-fs.d.ts +26 -9
  45. package/dist/group-fs.js +84 -84
  46. package/dist/group-fs.js.map +1 -1
  47. package/dist/group-id.d.ts +9 -12
  48. package/dist/group-id.js +41 -63
  49. package/dist/group-id.js.map +1 -1
  50. package/dist/index.d.ts +4 -2
  51. package/dist/index.js +4 -1
  52. package/dist/index.js.map +1 -1
  53. package/dist/keystore/aid-db.d.ts +1 -0
  54. package/dist/keystore/aid-db.js +43 -43
  55. package/dist/keystore/aid-db.js.map +1 -1
  56. package/dist/keystore/local-identity-store.d.ts +16 -0
  57. package/dist/keystore/local-identity-store.js +190 -18
  58. package/dist/keystore/local-identity-store.js.map +1 -1
  59. package/dist/keystore/local-token-store.d.ts +0 -13
  60. package/dist/keystore/local-token-store.js +0 -105
  61. package/dist/keystore/local-token-store.js.map +1 -1
  62. package/dist/logger.d.ts +2 -0
  63. package/dist/logger.js +4 -0
  64. package/dist/logger.js.map +1 -1
  65. package/dist/storage/lowlevel.d.ts +9 -1
  66. package/dist/storage/lowlevel.js +12 -1
  67. package/dist/storage/lowlevel.js.map +1 -1
  68. package/dist/storage/types.d.ts +0 -1
  69. package/dist/storage/types.js.map +1 -1
  70. package/dist/storage/vfs.d.ts +22 -3
  71. package/dist/storage/vfs.js +89 -54
  72. package/dist/storage/vfs.js.map +1 -1
  73. package/dist/tools/cross-sdk-agent.js +336 -49
  74. package/dist/tools/cross-sdk-agent.js.map +1 -1
  75. package/dist/transport.d.ts +2 -0
  76. package/dist/transport.js +52 -3
  77. package/dist/transport.js.map +1 -1
  78. package/dist/types.d.ts +38 -0
  79. package/dist/v2/session/session.d.ts +2 -0
  80. package/dist/v2/session/session.js +53 -23
  81. package/dist/v2/session/session.js.map +1 -1
  82. package/dist/v2/state/commitment.d.ts +1 -1
  83. package/dist/v2/state/commitment.js +4 -2
  84. package/dist/v2/state/commitment.js.map +1 -1
  85. package/dist/validators.d.ts +32 -0
  86. package/dist/validators.js +128 -0
  87. package/dist/validators.js.map +1 -0
  88. package/dist/version.d.ts +1 -1
  89. package/dist/version.js +1 -1
  90. package/package.json +1 -1
  91. package/_packed_docs/collab-gateway-boundary-test-report.md +0 -164
@@ -59,7 +59,7 @@
59
59
 
60
60
  ### POSIX VFS 方法
61
61
 
62
- > 在对象存储之上提供 Linux 文件系统语义(`ls/find/df/stat/mkdir/rm/mv/cp/mount`)。节点类型:`file` / `dir` / `symlink` / `mount`。
62
+ > 在对象存储之上提供 Linux 文件系统语义(`ls/find/df/stat/lstat/mkdir/touch/rm/mv/cp/mount`)。节点类型:`file` / `dir` / `symlink` / `mount`。
63
63
 
64
64
  | 方法 | 说明 |
65
65
  |------|------|
@@ -69,6 +69,7 @@
69
69
  | [storage.fs.stat](#storagefsstat) | 查节点(stat,跟随末级软链) |
70
70
  | [storage.fs.lstat](#storagefslstat) | 查节点(lstat,不跟随软链) |
71
71
  | [storage.fs.mkdir](#storagefsmkdir) | 建目录(mkdir) |
72
+ | [storage.fs.touch](#storagefstouch) | 创建空文件或更新时间戳(touch) |
72
73
  | [storage.fs.remove](#storagefsremove) | 删除文件/目录/软链(rm) |
73
74
  | [storage.fs.rename](#storagefsrename) | 同 owner 内移动/改名(mv) |
74
75
  | [storage.fs.copy](#storagefscopy) | 复制对象或软链(cp) |
@@ -91,6 +92,8 @@
91
92
  ### ACL / 权限方法
92
93
 
93
94
  > 统一权限求值顺序(硬顺序):公开位 → token → ACL(最近祖先前缀)→ 角色 → owner → 拒绝。
95
+ >
96
+ > AID storage 的 `storage.set_acl/remove_acl` 面向具体 AID,当前主要用于写/删除授权;直接读授权不通过 ACL 下发,读访问应使用 `storage.create_share_link` / `storage.get_by_share`,撤销分享使用 `storage.revoke_share_link`。`role:*` 伪主体只允许可信 group 内部门面管理,客户端不得直接对 `group_aid` 空间设置角色 ACL;群自有区 admin 写授权使用 `group.fs.set_acl/remove_acl`。
94
97
 
95
98
  | 方法 | 说明 |
96
99
  |------|------|
@@ -113,15 +116,15 @@
113
116
 
114
117
  ---
115
118
 
116
- > `object_key` 当前仅支持 ASCII 安全字符集合 `[A-Za-z0-9._/-]`,且不允许空路径段、`..`、反斜杠转义后的非法段。
117
-
118
- ## SDK 封装状态
119
-
120
- Python / Go / TypeScript / JavaScript SDK 均提供 storage low-level 与 VFS 门面。普通应用优先使用 SDK VFS:`write_bytes` / `upload_file` 会先 `check_upload`,小对象走 `put_object`,大对象走 `create_upload_session` → HTTP PUT → `complete_upload`,秒传路径用 `complete_upload(skip_blob=true)`;`read_bytes` / `download_file` 优先尝试 inline `get_object`,超限时回退 `create_download_ticket`。需要精确控制 ACL、token、软链、卷、批量操作或 URL 字段时,再直接调用本手册中的 `storage.*` RPC。
121
-
122
- ## storage.put_object
123
-
124
- 上传小对象(内容 base64 编码通过 RPC 传输)。
119
+ > `object_key` 当前仅支持 ASCII 安全字符集合 `[A-Za-z0-9._/-]`,且不允许空路径段、`..`、反斜杠转义后的非法段。
120
+
121
+ ## SDK 封装状态
122
+
123
+ Python / Go / TypeScript / JavaScript SDK 均提供 storage low-level 与 VFS 门面。普通应用优先使用 SDK VFS:`write_bytes` / `upload_file` 会先 `check_upload`,小对象走 `put_object`,大对象走 `create_upload_session` → HTTP PUT → `complete_upload`,秒传路径用 `complete_upload(skip_blob=true)`;`read_bytes` / `download_file` 优先尝试 inline `get_object`,超限时回退 `create_download_ticket`;`touch` 直接封装 `storage.fs.touch`。需要精确控制 ACL、token、软链、卷、批量操作或 URL 字段时,再直接调用本手册中的 `storage.*` RPC。
124
+
125
+ ## storage.put_object
126
+
127
+ 上传小对象(内容 base64 编码通过 RPC 传输)。
125
128
 
126
129
  ### 参数
127
130
 
@@ -885,7 +888,7 @@ share_url = result["url"] # https://alice.agentid.pub/s/Ab3xK9mZ2q
885
888
 
886
889
  ## storage.fs.list
887
890
 
888
- 列目录(POSIX `ls`)。混合返回子目录/对象/软链/可用挂载点,排序 dir < file < symlink < mount。群 owner 路径回退到 Group FS 子节点。
891
+ 列目录(POSIX `ls`)。混合返回子目录/对象/软链/可用挂载点,排序 dir < file < symlink < mount。群 owner 路径回退到 Group FS 子节点。
889
892
 
890
893
  ### 参数
891
894
 
@@ -915,7 +918,7 @@ share_url = result["url"] # https://alice.agentid.pub/s/Ab3xK9mZ2q
915
918
 
916
919
  ## storage.fs.find
917
920
 
918
- 递归查找(POSIX `find`),支持 name/type/size/mtime 过滤与分页。Group FS 与 `.collab` 注册表有回退。
921
+ 递归查找(POSIX `find`),支持 name/type/size/mtime 过滤与分页。Group FS 与 `.collab` 注册表有回退。
919
922
 
920
923
  ### 参数
921
924
 
@@ -1014,6 +1017,36 @@ fs 目录节点视图。
1014
1017
 
1015
1018
  ---
1016
1019
 
1020
+ ## storage.fs.touch
1021
+
1022
+ 创建空文件或刷新已有节点的修改时间(POSIX `touch`)。已存在的文件、目录或软链会更新时间戳;不存在时默认创建 0 字节私有文件;`no_create=true` 时不存在不创建并返回 `{touched:false, created:false}`。
1023
+
1024
+ ### 参数
1025
+
1026
+ | 参数 | 类型 | 必填 | 默认 | 说明 |
1027
+ |------|------|------|------|------|
1028
+ | `path` | string | 是 | — | 目标路径 |
1029
+ | `owner_aid` | string | 否 | 当前用户 | 所有者 AID |
1030
+ | `bucket` | string | 否 | `"default"` | 存储桶 |
1031
+ | `parents` | boolean | 否 | `false` | 创建文件时是否递归创建父目录 |
1032
+ | `no_create` / `noCreate` | boolean | 否 | `false` | 目标不存在时不创建 |
1033
+ | `mtime` | integer | 否 | 当前时间 | Unix 秒或毫秒;小于 `10000000000` 按秒解释 |
1034
+ | `follow_symlinks` / `followSymlinks` | boolean | 否 | `false` | 目标为软链时是否跟随末级软链 |
1035
+ | `content_type` | string | 否 | `"application/octet-stream"` | 新建空文件的 MIME 类型 |
1036
+ | `metadata` | object | 否 | `{}` | 新建空文件的元数据 |
1037
+ | `expire_in_seconds` | integer | 否 | `0` | 新建空文件的过期秒数 |
1038
+
1039
+ ### 响应
1040
+
1041
+ 成功返回 fs 节点视图,并额外包含:
1042
+
1043
+ | 字段 | 类型 | 说明 |
1044
+ |------|------|------|
1045
+ | `touched` | boolean | 是否实际更新或创建 |
1046
+ | `created` | boolean | 是否新建了空文件 |
1047
+
1048
+ ---
1049
+
1017
1050
  ## storage.fs.remove
1018
1051
 
1019
1052
  删除文件/目录/软链(POSIX `rm`,目录用 `recursive`)。**拒绝删除挂载点**(须先 unmount)。
@@ -1107,6 +1140,8 @@ fs 目录节点视图。
1107
1140
  | `mount` | object | 挂载视图 |
1108
1141
  | `status` | string | `active`(直接生效)/ `pending`(待批准) |
1109
1142
 
1143
+ > `status` 字段仅在直接调用 lowlevel RPC(`fs_mount`/`FSMount`/`fsMount` 原始返回)时可见。四语言 SDK 的高层 VFS 门面(`vfs.mount()`/`StorageVFS.Mount()`/`vfs.mount()`)会把响应解析为通用 NodeView,该视图不保留顶层 `status` 字段;需要判断是否 `pending` 时请改用 lowlevel 接口读取原始响应,或检查服务端在 `pending` 场景下额外写入的 `request_id`。
1144
+
1110
1145
  ---
1111
1146
 
1112
1147
  ## storage.fs.approve
@@ -1219,7 +1254,7 @@ fs 目录节点视图。
1219
1254
 
1220
1255
  ## storage.atomic_repoint
1221
1256
 
1222
- 原子重指软链 target(乐观锁 CAS)。**collab commit / tag 并发正确性的底层核心**。
1257
+ 原子重指软链 target(乐观锁 CAS)。**collab commit / tag 并发正确性的底层核心**。
1223
1258
 
1224
1259
  ### 参数
1225
1260
 
@@ -1280,14 +1315,16 @@ fs 目录节点视图。
1280
1315
 
1281
1316
  ## storage.set_acl
1282
1317
 
1283
- 授予路径前缀 ACL grant(群内部 admin 或群代理写授权)。支持 `role:<role>` 形式 grantee。ACL 对目录前缀授权,子路径默认继承(最近祖先覆盖)。
1318
+ 授予路径前缀 ACL grant。普通 AID storage `grantee_aid` 必须是具体 AID,主要用于授予写/删除权限;子路径默认继承(最近祖先覆盖)。读取不通过 AID ACL 直接授权,应用应改用 `storage.create_share_link` 并在需要时用 `storage.revoke_share_link` 撤销。
1319
+
1320
+ `role:*` 伪主体只允许可信 group 内部门面写入。客户端不能直接对 `group_aid` 空间调用 `storage.set_acl` 设置 `role:admin`;群自有区角色写授权统一使用 `group.fs.set_acl`。
1284
1321
 
1285
1322
  ### 参数
1286
1323
 
1287
1324
  | 参数 | 类型 | 必填 | 默认 | 说明 |
1288
1325
  |------|------|------|------|------|
1289
1326
  | `path` | string | 是 | — | 授权路径前缀 |
1290
- | `grantee_aid` | string | 是 | — | 被授权 AID(或 `role:<role>`) |
1327
+ | `grantee_aid` | string | 是 | — | 被授权 AID;`role:*` 仅限 group 内部调用 |
1291
1328
  | `perms` | string | 是 | — | 权限位:`r`/`w`/`rw`/`rwx`(`rwx` 含删除) |
1292
1329
  | `owner_aid` | string | 否 | 当前用户 | 所有者 AID |
1293
1330
  | `bucket` | string | 否 | `"default"` | 存储桶 |
@@ -1302,7 +1339,7 @@ ACL 视图。
1302
1339
 
1303
1340
  ## storage.remove_acl
1304
1341
 
1305
- 移除路径 ACL 授权。
1342
+ 移除路径 ACL 授权。对 AID storage,这是撤销写/删除授权的入口;读分享的撤销入口是 `storage.revoke_share_link`。群自有区角色写授权撤销使用 `group.fs.remove_acl`,不要由客户端直接调用 `storage.remove_acl` 操作 `role:*`。
1306
1343
 
1307
1344
  ### 参数
1308
1345
 
@@ -1339,7 +1376,7 @@ ACL 视图。
1339
1376
 
1340
1377
  ## storage.set_visibility
1341
1378
 
1342
- 切换对象或目录的公开/私有(软链不支持)。`allow_roles` 替换 `role:` ACL。
1379
+ 切换对象或目录的公开/私有(软链不支持)。`allow_roles` 是低层兼容字段,不能作为群自有区 admin 写授权入口;群自有区角色写授权必须使用 `group.fs.set_acl/remove_acl`。
1343
1380
 
1344
1381
  ### 参数
1345
1382
 
@@ -1349,7 +1386,7 @@ ACL 视图。
1349
1386
  | `visibility` | string | 是 | — | `public` / `private` |
1350
1387
  | `owner_aid` | string | 否 | 当前用户 | 所有者 AID |
1351
1388
  | `bucket` | string | 否 | `"default"` | 存储桶 |
1352
- | `allow_roles` | array | 否 | — | 允许的角色列表(替换 role ACL) |
1389
+ | `allow_roles` | array | 否 | — | 低层兼容字段,仅表示可见性读角色;不能用于群自有区写授权 |
1353
1390
 
1354
1391
  ### 响应
1355
1392
 
@@ -1502,4 +1539,4 @@ token 列表(hash 摘要 + 元数据)。
1502
1539
  | -32004 | 权限拒绝(requester 不是对象 owner,或非公开对象的读权限不足) |
1503
1540
  | -32008 | 对象不存在(`ErrNotFound`) |
1504
1541
  | -32009 | 版本冲突(`ErrVersionConflict`,`expected_version` 与实际版本不匹配) |
1505
- | -32000 | 通用错误(参数校验失败、配额不足、base64 解码失败、文件大小超限等) |
1542
+ | -32000 | 通用错误(参数校验失败、配额不足、base64 解码失败、文件大小超限等) |
@@ -11,10 +11,10 @@ AUN SDK 文档位于 `docs/sdk/`,索引文件 `docs/sdk/INDEX.md` 分三层:
11
11
  - `AUNClient`:认证、连接、状态机、事件和 RPC。
12
12
  - `ServiceProxyClient`:Python SDK 的 Service Proxy provider 侧客户端,负责 Gateway 控制面注册和 proxy-server 数据面隧道注册。
13
13
 
14
- 业务 RPC 的底层参数见 `09-*-rpc-manual.md`,可通过 `client.call(method, params)` 调用;Storage、Collab、Group FS、Service Proxy 等具备高层门面的能力以对应专项文档为准。`message.send`、`message.thought.put`、`group.send`、`group.thought.put` 的业务 payload 见 `09-payload-reference.md`。在线轻量通知和跨域 federation notify 方案见 `Notify通知方案.md`。
15
-
16
- Storage 的 SDK VFS、控制面/数据面分离、direct backend 上传下载、类 Linux 权限、mount/symlink 和服务端分层见 `AUN Storage架构设计.md`。
17
- Group FS 的 `group.fs.*` RPC、`client.group.fs.*` 门面、`aun group fs` CLI、`cp/mv` 语义、群自有区 `group_aid` 写身份、`parents` 语义、JS string 差异和 memberdata 服务端映射见 `09-group-rpc-manual.md`。Collab 的 `gc`、`reflog`、`revert`、`clone` 及版本化文档/标签语义见 `09-collab-rpc-manual.md`。
14
+ 业务 RPC 的底层参数见 `09-*-rpc-manual.md`,可通过 `client.call(method, params)` 调用;Storage、Collab、Group FS、Service Proxy 等具备高层门面的能力以对应专项文档为准。`message.send`、`message.thought.put`、`group.send`、`group.thought.put` 的业务 payload 见 `09-payload-reference.md`。在线轻量通知和跨域 federation notify 方案见 `Notify通知方案.md`。
15
+
16
+ Storage 的 SDK VFS、控制面/数据面分离、direct backend 上传下载、类 Linux 权限、mount/symlink 和服务端分层见 `AUN Storage架构设计.md`。
17
+ Group FS 的 `group.fs.*` RPC、`client.group.fs.*` 门面、`aun group fs` CLI、`cp/mv` 语义、群自有区角色 ACL 授权/撤销/查询与 `group_aid` 签名、`parents` 语义、JS string 差异和 memberdata 服务端映射见 `09-group-rpc-manual.md`。Collab 的 `gc`、`reflog`、`revert`、`clone` 及版本化文档/标签语义见 `09-collab-rpc-manual.md`。
18
18
 
19
19
  ## 渐进式查阅流程
20
20
 
@@ -29,10 +29,10 @@ Group FS 的 `group.fs.*` RPC、`client.group.fs.*` 门面、`aun group fs` CLI
29
29
  - 身份与认证:AIDStore / AID / 注册 / 加载 / 证书
30
30
  - 连接与状态:AUNClient / 九态状态机 / Gateway / 重连
31
31
  - E2EE:默认加密、ProtectedHeaders、P2P / Group V2
32
- - RPC 与事件:`client.call()`、`client.on()`、`client.notify()`、RPC 手册
33
- - Storage:SDK VFS、direct backend 数据面、`storage.*` low-level RPC、类 Linux 权限、mount/symlink、Storage Service 分层
34
- - 群文件系统:`group.fs.*`、`client.group.fs.*`、`aun group fs`、`cp/mv` 上传下载、群自有区 `group_aid` 写身份、`parents`、JS string 差异和 memberdata 服务端映射(见 `09-group-rpc-manual.md`)
35
- - 协作(collab):`collab.*` 版本化文档、乐观锁 CAS commit、三方合并、标签、GC、reflog、revert(见 `09-collab-rpc-manual.md`)
32
+ - RPC 与事件:`client.call()`、`client.on()`、`client.notify()`、RPC 手册
33
+ - Storage:SDK VFS、direct backend 数据面、`storage.*` low-level RPC、类 Linux 权限、mount/symlink、Storage Service 分层
34
+ - 群文件系统:`group.fs.*`、`client.group.fs.*`、`aun group fs`、`cp/mv` 上传下载、群自有区角色 ACL 与 `group_aid` 签名、`parents`、JS string 差异和 memberdata 服务端映射(见 `09-group-rpc-manual.md`)
35
+ - 协作(collab):`collab.*` 版本化文档、乐观锁 CAS commit、三方合并、标签、GC、reflog、revert(见 `09-collab-rpc-manual.md`)
36
36
  - Service Proxy:`ServiceProxyClient`、`proxy.register_services`、proxy-server `register_services`
37
37
  - agent.md:`AIDStore.upload_agent_md()`、`AIDStore.download_agent_md()`、`AIDStore.check_agent_md()`
38
38
  - 错误处理:Result、异常、错误码、重试
@@ -68,11 +68,11 @@ Group FS 的 `group.fs.*` RPC、`client.group.fs.*` 门面、`aun group fs` CLI
68
68
  | 发布、下载或检查 agent.md | [04-连接与认证](04-连接与认证.md)、[06-API手册](06-API手册.md) |
69
69
  | 状态机和重连 | [03-核心概念](03-核心概念.md)、[04-连接与认证](04-连接与认证.md) |
70
70
  | 查方法签名 | [06-API手册](06-API手册.md) |
71
- | 查消息或群组 RPC | 对应 `09-*-rpc-manual.md` |
72
- | 查群文件系统 RPC / SDK / CLI 入口、群自有区写身份、`parents` 和 JS string 差异 | [09-group-rpc-manual.md](09-group-rpc-manual.md)、[06-API手册](06-API手册.md) |
73
- | 查 Storage 架构、SDK VFS、权限模型、mount/symlink 和上传下载链路 | [AUN Storage架构设计.md](<AUN Storage架构设计.md>) |
74
- | 查 Storage RPC 参数 | [09-storage-rpc-manual.md](09-storage-rpc-manual.md) |
75
- | 查协作(版本化文档/乐观锁/标签/GC/reflog/revert)RPC | [09-collab-rpc-manual.md](09-collab-rpc-manual.md) |
71
+ | 查消息或群组 RPC | 对应 `09-*-rpc-manual.md` |
72
+ | 查群文件系统 RPC / SDK / CLI 入口、群自有区角色 ACL 授权/撤销/查询、`parents` 和 JS string 差异 | [09-group-rpc-manual.md](09-group-rpc-manual.md)、[06-API手册](06-API手册.md) |
73
+ | 查 Storage 架构、SDK VFS、权限模型、mount/symlink 和上传下载链路 | [AUN Storage架构设计.md](<AUN Storage架构设计.md>) |
74
+ | 查 Storage RPC 参数 | [09-storage-rpc-manual.md](09-storage-rpc-manual.md) |
75
+ | 查协作(版本化文档/乐观锁/标签/GC/reflog/revert)RPC | [09-collab-rpc-manual.md](09-collab-rpc-manual.md) |
76
76
  | 查 notify 在线轻量通知 / 跨域 federation | [Notify通知方案.md](Notify通知方案.md) |
77
77
  | 查 Service Proxy 服务注册和路由 | [06-API手册](06-API手册.md)、[09-proxy-rpc-manual.md](09-proxy-rpc-manual.md) |
78
78
  | 查 payload 格式 | [09-payload-reference.md](09-payload-reference.md) |
@@ -19,12 +19,12 @@
19
19
  | [08-最佳实践](08-最佳实践.md) | 幂等连接 · 多 AID · 资源清理 · 测试数据保护 |
20
20
  | [09-payload-reference](09-payload-reference.md) | message / group / thought payload 格式 |
21
21
  | [09-message-rpc-manual](09-message-rpc-manual.md) | P2P 消息 RPC |
22
- | [09-group-rpc-manual](09-group-rpc-manual.md) | 群组 RPC(含 `group.fs.*` 群文件系统 RPC、群自有区写身份和 `parents` 语义) |
23
- | [09-storage-rpc-manual](09-storage-rpc-manual.md) | 存储 RPC(对象 + POSIX VFS / 卷 / 软链 / ACL / token) |
24
- | [09-collab-rpc-manual](09-collab-rpc-manual.md) | 协作 RPC(版本化文档 / 乐观锁 / 三方合并 / 标签 / GC / reflog / revert) |
25
- | [group.fs POSIX 化详细设计](../aun-fs/group-fs/group.fs-POSIX化详细设计.md) | 下一版 `client.group.fs.*` / `aun group fs` / `group.fs.*` 群文件系统设计,含群自有区写身份、`parents` 和 JS string 语义 |
26
- | [group.fs POSIX 化分阶段实施计划](../aun-fs/group-fs/group.fs-POSIX化分阶段实施计划.md) | group.fs 重构的 IPO、Step-by-step、用例集、Checklist、验收和回滚 |
27
- | [AUN Storage架构设计](<AUN Storage架构设计.md>) | Storage SDK VFS、控制面/数据面分离、服务端分层、类 Linux 权限、mount/symlink 与关键时序 |
22
+ | [09-group-rpc-manual](09-group-rpc-manual.md) | 群组 RPC(含 `group.fs.*` 群文件系统 RPC、群自有区角色 ACL 授权/撤销/查询和 `parents` 语义) |
23
+ | [09-storage-rpc-manual](09-storage-rpc-manual.md) | 存储 RPC(对象 + POSIX VFS / 卷 / 软链 / ACL / token) |
24
+ | [09-collab-rpc-manual](09-collab-rpc-manual.md) | 协作 RPC(版本化文档 / 乐观锁 / 三方合并 / 标签 / GC / reflog / revert) |
25
+ | [group.fs POSIX 化详细设计](../aun-fs/group-fs/group.fs-POSIX化详细设计.md) | `client.group.fs.*` / `aun group fs` / `group.fs.*` 群文件系统设计,含群自有区角色 ACL、`parents` 和 JS string 语义 |
26
+ | [group.fs POSIX 化分阶段实施计划](../aun-fs/group-fs/group.fs-POSIX化分阶段实施计划.md) | group.fs 重构的 IPO、Step-by-step、用例集、Checklist、验收和回滚 |
27
+ | [AUN Storage架构设计](<AUN Storage架构设计.md>) | Storage SDK VFS、控制面/数据面分离、服务端分层、类 Linux 权限、mount/symlink 与关键时序 |
28
28
  | [09-meta-rpc-manual](09-meta-rpc-manual.md) | meta RPC 和信任根 |
29
29
  | [09-stream-rpc-manual](09-stream-rpc-manual.md) | stream RPC |
30
30
  | [09-proxy-rpc-manual](09-proxy-rpc-manual.md) | Service Proxy 控制面 RPC 和数据面隧道注册 |
@@ -62,12 +62,12 @@
62
62
 
63
63
  - `client.call()` / `client.on()` → [04-连接与认证](04-连接与认证.md)、[06-API手册](06-API手册.md)
64
64
  - `client.notify()` 在线轻量通知、跨域 federation、在线/离线边界 → [Notify通知方案](Notify通知方案.md)
65
- - Message RPC → [09-message-rpc-manual](09-message-rpc-manual.md)
66
- - Group RPC 与群文件系统 `group.fs.*` 控制面、群自有区写身份和 `parents` 语义 → [09-group-rpc-manual](09-group-rpc-manual.md)
67
- - 下一版群文件系统 `group.fs.*`、`client.group.fs.*`、`aun group fs`、`cp/mv` POSIX 化语义和 JS string 差异 → [group.fs POSIX 化详细设计](../aun-fs/group-fs/group.fs-POSIX化详细设计.md)、[分阶段实施计划](../aun-fs/group-fs/group.fs-POSIX化分阶段实施计划.md)
68
- - Storage 架构、SDK VFS、控制面/数据面分离、类 Linux 权限和 mount/symlink → [AUN Storage架构设计](<AUN Storage架构设计.md>)
69
- - Storage RPC(对象 + `storage.fs.*` / `storage.volume.*` / 软链 / ACL / token)→ [09-storage-rpc-manual](09-storage-rpc-manual.md)
70
- - Collab RPC(`collab.*` 版本化文档、乐观锁、标签、GC、reflog、revert)→ [09-collab-rpc-manual](09-collab-rpc-manual.md)
65
+ - Message RPC → [09-message-rpc-manual](09-message-rpc-manual.md)
66
+ - Group RPC 与群文件系统 `group.fs.*` 控制面、群自有区角色 ACL 授权/撤销/查询和 `parents` 语义 → [09-group-rpc-manual](09-group-rpc-manual.md)
67
+ - 下一版群文件系统 `group.fs.*`、`client.group.fs.*`、`aun group fs`、`cp/mv` POSIX 化语义和 JS string 差异 → [group.fs POSIX 化详细设计](../aun-fs/group-fs/group.fs-POSIX化详细设计.md)、[分阶段实施计划](../aun-fs/group-fs/group.fs-POSIX化分阶段实施计划.md)
68
+ - Storage 架构、SDK VFS、控制面/数据面分离、类 Linux 权限和 mount/symlink → [AUN Storage架构设计](<AUN Storage架构设计.md>)
69
+ - Storage RPC(对象 + `storage.fs.*` / `storage.volume.*` / `storage.fs.touch` / 软链 / ACL / token)→ [09-storage-rpc-manual](09-storage-rpc-manual.md)
70
+ - Collab RPC(`collab.*` 版本化文档、乐观锁、标签、GC、reflog、revert)→ [09-collab-rpc-manual](09-collab-rpc-manual.md)
71
71
  - Meta RPC → [09-meta-rpc-manual](09-meta-rpc-manual.md)
72
72
  - Stream RPC → [09-stream-rpc-manual](09-stream-rpc-manual.md)
73
73
  - Service Proxy RPC 和隧道注册 → [09-proxy-rpc-manual](09-proxy-rpc-manual.md)
@@ -118,7 +118,7 @@
118
118
 
119
119
  ### 09-*-rpc-manual
120
120
 
121
- 各业务服务的 RPC 参数、响应和错误语义。Storage、Collab、Group FS、Service Proxy 等明确提供高层门面的能力应优先使用门面;需要精确控制底层参数时再通过 `client.call()` 调用业务 RPC。
121
+ 各业务服务的 RPC 参数、响应和错误语义。Storage、Collab、Group FS、Service Proxy 等明确提供高层门面的能力应优先使用门面;需要精确控制底层参数时再通过 `client.call()` 调用业务 RPC。
122
122
 
123
123
  ### AUN Storage架构设计
124
124
 
@@ -126,7 +126,7 @@
126
126
 
127
127
  ### 09-collab-rpc-manual
128
128
 
129
- 定义协作层 `collab.*` RPC(共 21 个):文档版本线(ls-files/create/show/commit/merge/log/diff/revert/prune)、运维备份迁移(gc/reflog/clone)、目录级标签(tag.create/list/show/diff/restore/rm/prune)、群内发现(ls-remote/unregister)。collab 编排并入 storage 服务进程,以调用者身份直调 storage 原语,授权下沉到 storage ACL。涵盖乐观锁 CAS commit 撞版本响应、三方合并冲突标记、forward-only 标签回滚、垃圾扫描和审计日志语义。
129
+ 定义协作层 `collab.*` RPC(共 21 个):文档版本线(ls-files/create/show/commit/merge/log/diff/revert/prune)、运维备份迁移(gc/reflog/clone)、目录级标签(tag.create/list/show/diff/restore/rm/prune)、群内发现(ls-remote/unregister)。collab 编排并入 storage 服务进程,以调用者身份直调 storage 原语,授权下沉到 storage ACL。涵盖乐观锁 CAS commit 撞版本响应、三方合并冲突标记、forward-only 标签回滚、垃圾扫描和审计日志语义。
130
130
 
131
131
  ### 09-proxy-rpc-manual
132
132
 
@@ -59,6 +59,15 @@ export declare class MessageDeliveryEngine {
59
59
  restoreSeqTrackerState(): void;
60
60
  migrateSeqStateGroupIds(state: Record<string, number>): Record<string, number>;
61
61
  saveSeqTrackerState(): void;
62
+ persistSeq(ns: string, forceSeq?: number): void;
63
+ flushSeqTrackerPending(): void;
64
+ private dropSeqTrackerPending;
65
+ private currentSeqTrackerContext;
66
+ private seqTrackerPending;
67
+ private mergeSeqTrackerPending;
68
+ private scheduleSeqTrackerFlush;
69
+ private cancelSeqTrackerFlushTimer;
70
+ private writeSeqTrackerState;
62
71
  persistRepairedSeq(ns: string): void;
63
72
  clampAckSeq(method: string, field: string, ns: string, seq: number): number;
64
73
  clampAckParams(method: string, params: RpcParams): RpcParams;
@@ -1,9 +1,10 @@
1
1
  import { slotIsolationKey } from '../config.js';
2
2
  import { normalizeGroupId } from '../group-id.js';
3
- import { isJsonObject } from '../types.js';
3
+ import { ConnectionState, isJsonObject } from '../types.js';
4
4
  const PUSHED_SEQS_LIMIT = 50_000;
5
5
  const PENDING_ORDERED_LIMIT = 50_000;
6
6
  const GROUP_RECALL_SEEN_LIMIT = 10_000;
7
+ const SEQ_TRACKER_PERSIST_FLUSH_DELAY_MS = 200;
7
8
  const APP_MESSAGE_ENVELOPE_KEYS = [
8
9
  'module_id', 'message_type', 'type', 'kind', 'version',
9
10
  'from', 'from_aid', 'sender_aid', 'to', 'to_aid', 'group_id',
@@ -95,7 +96,8 @@ export class MessageDeliveryEngine {
95
96
  const eventPayload = payload;
96
97
  client._groupState?.handleGroupChangedV2Membership?.(eventPayload);
97
98
  if (eventPayload.action === 'dissolved') {
98
- const groupId = normalizeGroupId(String(eventPayload.group_id ?? '')) || String(eventPayload.group_id ?? '').trim();
99
+ const rawGroupId = eventPayload.group_aid ?? eventPayload.group_id ?? '';
100
+ const groupId = normalizeGroupId(String(rawGroupId)) || String(rawGroupId).trim();
99
101
  if (groupId)
100
102
  client._cleanupDissolvedGroup?.(groupId);
101
103
  }
@@ -495,7 +497,7 @@ export class MessageDeliveryEngine {
495
497
  .catch((e) => { client._clientLog.debug(`group recall auto-ack failed: group=${groupId} ${formatDeliveryError(e)}`); });
496
498
  }
497
499
  if (contig !== contigBefore)
498
- this.saveSeqTrackerState();
500
+ this.persistSeq(ns);
499
501
  }
500
502
  publishAppEvent(event, payload, source = 'direct') {
501
503
  const client = this.runtime.client;
@@ -565,6 +567,8 @@ export class MessageDeliveryEngine {
565
567
  }
566
568
  logMessageDebug(stage, source, event, message, opts = {}) {
567
569
  const client = this.runtime.client;
570
+ if (client._isMessageDebugEnabled?.() === false)
571
+ return;
568
572
  const record = {
569
573
  stage,
570
574
  source,
@@ -666,7 +670,7 @@ export class MessageDeliveryEngine {
666
670
  .catch((e) => { client._clientLog.debug(`P2P auto-ack failed: ${formatDeliveryError(e)}`); });
667
671
  }
668
672
  if (contigAfter !== contigBefore)
669
- this.saveSeqTrackerState();
673
+ this.persistSeq(ns);
670
674
  if (encryptedPush)
671
675
  return;
672
676
  }
@@ -743,7 +747,7 @@ export class MessageDeliveryEngine {
743
747
  .catch((e) => { client._clientLog.debug(`group recall auto-ack failed: group=${groupId} ${formatDeliveryError(e)}`); });
744
748
  }
745
749
  if (contigAfter !== contigBefore)
746
- this.saveSeqTrackerState();
750
+ this.persistSeq(ns);
747
751
  void published;
748
752
  return;
749
753
  }
@@ -766,7 +770,7 @@ export class MessageDeliveryEngine {
766
770
  .catch((e) => { client._clientLog.debug(`group message auto-ack failed: group=${groupId} ${formatDeliveryError(e)}`); });
767
771
  }
768
772
  if (contigAfter !== contigBefore)
769
- this.saveSeqTrackerState();
773
+ this.persistSeq(ns);
770
774
  if (encryptedPush)
771
775
  return;
772
776
  }
@@ -822,7 +826,7 @@ export class MessageDeliveryEngine {
822
826
  this.prunePushedSeqs(ns);
823
827
  if (client._seqTracker.getContiguousSeq(ns) !== afterSeq) {
824
828
  await this.drainOrderedMessages(ns, undefined, true);
825
- this.saveSeqTrackerState();
829
+ this.persistSeq(ns);
826
830
  }
827
831
  const contig = client._seqTracker.getContiguousSeq(ns);
828
832
  if (contig > 0 && contig !== afterSeq) {
@@ -836,9 +840,6 @@ export class MessageDeliveryEngine {
836
840
  finally {
837
841
  client._gapFillDone.delete(dedupKey);
838
842
  client._releasePullGate(ns, token);
839
- if (filled > 0 && client._seqTracker.getContiguousSeq(ns) > afterSeq) {
840
- void this.fillP2pGap();
841
- }
842
843
  }
843
844
  }
844
845
  async fillGroupGap(groupId) {
@@ -865,7 +866,7 @@ export class MessageDeliveryEngine {
865
866
  this.prunePushedSeqs(ns);
866
867
  if (client._seqTracker.getContiguousSeq(ns) !== afterSeq) {
867
868
  await this.drainOrderedMessages(ns, undefined, true);
868
- this.saveSeqTrackerState();
869
+ this.persistSeq(ns);
869
870
  }
870
871
  client._clientLog.debug(`group message gap fill done: group=${groupId}, after_seq=${afterSeq}, filled=${filled}`);
871
872
  }
@@ -963,7 +964,7 @@ export class MessageDeliveryEngine {
963
964
  const ackContig = client._seqTracker.getContiguousSeq(ns);
964
965
  await this.drainOrderedMessages(ns);
965
966
  if (ackContig !== pageContigBefore && !hasDissolvedEvent) {
966
- this.saveSeqTrackerState();
967
+ this.persistSeq(ns);
967
968
  }
968
969
  if (eventObjects.length > 0 && ackContig > 0 && ackContig !== pageContigBefore) {
969
970
  const ackSeq = this.clampAckSeq('group.ack_events', 'event_seq', ns, ackContig);
@@ -999,6 +1000,7 @@ export class MessageDeliveryEngine {
999
1000
  }
1000
1001
  async handleGroupChangedEventSeq(data, groupId) {
1001
1002
  const client = this.runtime.client;
1003
+ groupId = normalizeGroupId(groupId) || String(groupId ?? '').trim();
1002
1004
  let needPull = false;
1003
1005
  const rawEventSeq = data.event_seq;
1004
1006
  const eventSeq = Number(rawEventSeq);
@@ -1027,7 +1029,7 @@ export class MessageDeliveryEngine {
1027
1029
  await this.drainOrderedMessages(ns);
1028
1030
  if (ackContig > 0 && ackContig !== contigBefore) {
1029
1031
  if (data.action !== 'dissolved')
1030
- this.saveSeqTrackerState();
1032
+ this.persistSeq(ns);
1031
1033
  const ackSeq = this.clampAckSeq('group.ack_events', 'event_seq', ns, ackContig);
1032
1034
  client._transport.call('group.ack_events', {
1033
1035
  group_id: groupId,
@@ -1132,7 +1134,7 @@ export class MessageDeliveryEngine {
1132
1134
  const newContig = client._seqTracker.getContiguousSeq(ns);
1133
1135
  const needPull = pushSeq > newContig && !published;
1134
1136
  if (newContig !== contigBefore) {
1135
- client._saveSeqTrackerState();
1137
+ this.persistSeq(ns);
1136
1138
  }
1137
1139
  if (newContig > 0 && newContig !== contigBefore) {
1138
1140
  const ackSeq = this.clampAckSeq('message.v2.ack', 'up_to_seq', ns, newContig);
@@ -1204,7 +1206,7 @@ export class MessageDeliveryEngine {
1204
1206
  this.runtime.delivery.setOnlineUnreadHintDrainActive(true);
1205
1207
  try {
1206
1208
  while (client._onlineUnreadHintQueue.size > 0) {
1207
- if (client.state !== 'ready')
1209
+ if (client.state !== ConnectionState.READY)
1208
1210
  return;
1209
1211
  if (client._sessionOptions?.background_sync === false)
1210
1212
  return;
@@ -1377,23 +1379,132 @@ export class MessageDeliveryEngine {
1377
1379
  }
1378
1380
  saveSeqTrackerState() {
1379
1381
  const client = this.runtime.client;
1380
- if (!client._aid)
1382
+ const context = this.currentSeqTrackerContext();
1383
+ if (!context) {
1384
+ this.flushSeqTrackerPending();
1381
1385
  return;
1386
+ }
1382
1387
  const state = client._seqTracker.exportState();
1383
- if (Object.keys(state).length === 0)
1388
+ const pending = this.seqTrackerPending();
1389
+ if (pending.size > 0 && client._seqTrackerPendingPersistContext === context) {
1390
+ const liveNamespaces = new Set(Object.keys(state));
1391
+ for (const ns of [...pending.keys()]) {
1392
+ if (!liveNamespaces.has(ns))
1393
+ pending.delete(ns);
1394
+ }
1395
+ if (pending.size === 0)
1396
+ client._seqTrackerPendingPersistContext = null;
1397
+ }
1398
+ if (Object.keys(state).length > 0)
1399
+ this.mergeSeqTrackerPending(context, state);
1400
+ this.flushSeqTrackerPending();
1401
+ }
1402
+ persistSeq(ns, forceSeq) {
1403
+ const client = this.runtime.client;
1404
+ const context = this.currentSeqTrackerContext();
1405
+ if (!context) {
1406
+ this.flushSeqTrackerPending();
1407
+ return;
1408
+ }
1409
+ const seq = forceSeq ?? client._seqTracker.getContiguousSeq(ns);
1410
+ if (!ns || !Number.isFinite(seq) || seq <= 0)
1411
+ return;
1412
+ if (forceSeq !== undefined) {
1413
+ client._seqTracker.forceContiguousSeq(ns, seq);
1414
+ }
1415
+ this.mergeSeqTrackerPending(context, { [ns]: seq });
1416
+ this.scheduleSeqTrackerFlush();
1417
+ }
1418
+ flushSeqTrackerPending() {
1419
+ const client = this.runtime.client;
1420
+ this.cancelSeqTrackerFlushTimer();
1421
+ const context = client._seqTrackerPendingPersistContext;
1422
+ const pending = this.seqTrackerPending();
1423
+ if (!context || pending.size === 0) {
1424
+ client._seqTrackerPendingPersistContext = null;
1425
+ return;
1426
+ }
1427
+ const [aid, deviceId, slotId] = JSON.parse(context);
1428
+ const state = Object.fromEntries(pending.entries());
1429
+ pending.clear();
1430
+ client._seqTrackerPendingPersistContext = null;
1431
+ this.writeSeqTrackerState(aid, deviceId, slotId, state);
1432
+ }
1433
+ dropSeqTrackerPending(ns) {
1434
+ const client = this.runtime.client;
1435
+ const pending = this.seqTrackerPending();
1436
+ pending.delete(ns);
1437
+ if (pending.size === 0) {
1438
+ client._seqTrackerPendingPersistContext = null;
1439
+ this.cancelSeqTrackerFlushTimer();
1440
+ }
1441
+ }
1442
+ currentSeqTrackerContext() {
1443
+ const client = this.runtime.client;
1444
+ if (!client._aid)
1445
+ return null;
1446
+ return JSON.stringify([client._aid, client._deviceId, client._slotId]);
1447
+ }
1448
+ seqTrackerPending() {
1449
+ const client = this.runtime.client;
1450
+ if (!(client._seqTrackerPendingPersist instanceof Map)) {
1451
+ client._seqTrackerPendingPersist = new Map();
1452
+ }
1453
+ return client._seqTrackerPendingPersist;
1454
+ }
1455
+ mergeSeqTrackerPending(context, state) {
1456
+ const client = this.runtime.client;
1457
+ const pending = this.seqTrackerPending();
1458
+ const pendingContext = client._seqTrackerPendingPersistContext;
1459
+ if (pendingContext && pendingContext !== context && pending.size > 0) {
1460
+ this.flushSeqTrackerPending();
1461
+ }
1462
+ client._seqTrackerPendingPersistContext = context;
1463
+ for (const [ns, seq] of Object.entries(state)) {
1464
+ const seqNum = Number(seq);
1465
+ if (ns && Number.isFinite(seqNum) && seqNum > 0) {
1466
+ pending.set(ns, seqNum);
1467
+ }
1468
+ }
1469
+ }
1470
+ scheduleSeqTrackerFlush() {
1471
+ const client = this.runtime.client;
1472
+ const pending = this.seqTrackerPending();
1473
+ if (pending.size === 0)
1474
+ return;
1475
+ if (client._seqTrackerFlushTimer)
1476
+ return;
1477
+ client._seqTrackerFlushTimer = setTimeout(() => {
1478
+ client._seqTrackerFlushTimer = null;
1479
+ this.flushSeqTrackerPending();
1480
+ }, SEQ_TRACKER_PERSIST_FLUSH_DELAY_MS);
1481
+ }
1482
+ cancelSeqTrackerFlushTimer() {
1483
+ const client = this.runtime.client;
1484
+ if (client._seqTrackerFlushTimer) {
1485
+ clearTimeout(client._seqTrackerFlushTimer);
1486
+ client._seqTrackerFlushTimer = null;
1487
+ }
1488
+ }
1489
+ writeSeqTrackerState(aid, deviceId, slotId, state) {
1490
+ const client = this.runtime.client;
1491
+ if (!aid || Object.keys(state).length === 0)
1384
1492
  return;
1385
1493
  try {
1386
1494
  const saveFn = client._tokenStore.saveSeq;
1387
1495
  if (typeof saveFn === 'function') {
1388
1496
  for (const [ns, seq] of Object.entries(state)) {
1389
- saveFn.call(client._tokenStore, client._aid, client._deviceId, client._slotId, ns, seq);
1497
+ saveFn.call(client._tokenStore, aid, deviceId, slotId, ns, seq);
1390
1498
  }
1391
1499
  return;
1392
1500
  }
1393
1501
  const updater = client._tokenStore.updateInstanceState;
1394
1502
  if (typeof updater === 'function') {
1395
- updater.call(client._tokenStore, client._aid, client._deviceId, client._slotId, (metadata) => {
1396
- metadata.seq_tracker_state = state;
1503
+ updater.call(client._tokenStore, aid, deviceId, slotId, (metadata) => {
1504
+ const existing = isJsonObject(metadata.seq_tracker_state)
1505
+ ? { ...metadata.seq_tracker_state }
1506
+ : {};
1507
+ metadata.seq_tracker_state = { ...existing, ...state };
1397
1508
  return metadata;
1398
1509
  });
1399
1510
  }
@@ -1403,9 +1514,9 @@ export class MessageDeliveryEngine {
1403
1514
  client._clientLog.warn(`save SeqTracker state failed: ${error}`);
1404
1515
  client._dispatcher.publish('seq_tracker.persist_error', {
1405
1516
  phase: 'save',
1406
- aid: client._aid,
1407
- device_id: client._deviceId,
1408
- slot_id: client._slotId,
1517
+ aid,
1518
+ device_id: deviceId,
1519
+ slot_id: slotId,
1409
1520
  error: String(error),
1410
1521
  }).catch(() => { });
1411
1522
  }
@@ -1415,6 +1526,7 @@ export class MessageDeliveryEngine {
1415
1526
  if (!client._aid || !ns)
1416
1527
  return;
1417
1528
  const seq = client._seqTracker.getContiguousSeq(ns);
1529
+ this.dropSeqTrackerPending(ns);
1418
1530
  try {
1419
1531
  if (seq > 0 && typeof client._tokenStore.saveSeq === 'function') {
1420
1532
  client._tokenStore.saveSeq(client._aid, client._deviceId, client._slotId, ns, seq);
@@ -1425,6 +1537,18 @@ export class MessageDeliveryEngine {
1425
1537
  deleteSeq.call(client._tokenStore, client._aid, client._deviceId, client._slotId, ns);
1426
1538
  return;
1427
1539
  }
1540
+ const updater = client._tokenStore.updateInstanceState;
1541
+ if (seq <= 0 && typeof updater === 'function') {
1542
+ updater.call(client._tokenStore, client._aid, client._deviceId, client._slotId, (metadata) => {
1543
+ if (isJsonObject(metadata.seq_tracker_state)) {
1544
+ const next = { ...metadata.seq_tracker_state };
1545
+ delete next[ns];
1546
+ metadata.seq_tracker_state = next;
1547
+ }
1548
+ return metadata;
1549
+ });
1550
+ return;
1551
+ }
1428
1552
  if (seq > 0) {
1429
1553
  this.saveSeqTrackerState();
1430
1554
  }
@@ -1484,6 +1608,7 @@ export class MessageDeliveryEngine {
1484
1608
  const queue = client._pendingOrderedMsgs.get(ns);
1485
1609
  if (!queue || queue.size === 0)
1486
1610
  return;
1611
+ let delivered = false;
1487
1612
  while (true) {
1488
1613
  const contig = client._seqTracker.getContiguousSeq(ns);
1489
1614
  const ready = [...queue.keys()]
@@ -1512,10 +1637,14 @@ export class MessageDeliveryEngine {
1512
1637
  await this.publishOrderedQueueItem(ns, item.event, seq, item.payload, 'ordered-drain', pullResponse);
1513
1638
  this.markPublishedSeq(ns, seq);
1514
1639
  client._markOrderedSeqDelivered(ns, seq);
1640
+ delivered = true;
1515
1641
  client._clientLog.debug(`publish ordered drain delivered: ns=${ns}, seq=${seq}, event=${item.event}`);
1516
1642
  }
1517
- if (queue.size === 0)
1643
+ if (queue.size === 0) {
1518
1644
  client._pendingOrderedMsgs.delete(ns);
1645
+ if (delivered)
1646
+ this.saveSeqTrackerState();
1647
+ }
1519
1648
  }
1520
1649
  async publishOrderedMessage(event, ns, seq, payload) {
1521
1650
  const client = this.runtime.client;
@@ -1561,6 +1690,8 @@ export class MessageDeliveryEngine {
1561
1690
  client._markOrderedSeqDelivered(ns, seqNum);
1562
1691
  client._clientLog.debug(`publish ordered delivered: event=${event}, ns=${ns}, seq=${seqNum}`);
1563
1692
  await this.drainOrderedMessages(ns);
1693
+ if (!client._pendingOrderedMsgs.get(ns))
1694
+ this.saveSeqTrackerState();
1564
1695
  return true;
1565
1696
  }
1566
1697
  async publishOrderedGroupRecall(ns, seq, message) {
@@ -1580,6 +1711,8 @@ export class MessageDeliveryEngine {
1580
1711
  this.markPublishedSeq(ns, seqNum);
1581
1712
  client._markOrderedSeqDelivered?.(ns, seqNum);
1582
1713
  await this.drainOrderedMessages(ns);
1714
+ if (!client._pendingOrderedMsgs.get(ns))
1715
+ this.saveSeqTrackerState();
1583
1716
  return true;
1584
1717
  }
1585
1718
  async publishPulledMessage(event, ns, seq, payload) {