@agentunion/fastaun 0.5.13 → 0.5.15
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 +39 -0
- package/_packed_docs/CHANGELOG.md +39 -0
- package/_packed_docs/aun/345/210/206/345/270/203/345/274/217/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +85 -4
- package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +1 -1
- package/dist/client/delivery.d.ts +1 -0
- package/dist/client/delivery.js +81 -55
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +3 -3
- package/dist/client/rpc-pipeline.js +34 -39
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.js +51 -16
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.js +28 -23
- package/dist/client.js.map +1 -1
- package/dist/events.d.ts +18 -8
- package/dist/events.js +154 -50
- package/dist/events.js.map +1 -1
- package/dist/group-index.js +1 -1
- package/dist/group-index.js.map +1 -1
- package/dist/logger.d.ts +9 -1
- package/dist/logger.js +29 -7
- package/dist/logger.js.map +1 -1
- package/dist/result.d.ts +1 -2
- package/dist/result.js +2 -2
- package/dist/result.js.map +1 -1
- package/dist/transport.d.ts +7 -1
- package/dist/transport.js +139 -55
- 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
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,45 @@
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## 0.5.15 — 2026-09-15
|
|
10
|
+
|
|
11
|
+
### 修复
|
|
12
|
+
|
|
13
|
+
#### 协议事件与应用事件隔离
|
|
14
|
+
- 协议事件、transport observer 和断线回调使用独立 dispatcher,不再被慢速应用 handler 阻塞。
|
|
15
|
+
- 协议事件转发到应用层时深拷贝 payload,公开错误统一转换为 `{code, message}`,避免暴露内部错误对象。
|
|
16
|
+
|
|
17
|
+
#### Worker、Pull 与 V2 连接代次
|
|
18
|
+
- Node.js Worker transport 增加连接代次和旧响应保护;Worker 启动、运行中异常或熔断时回退本地 transport,旧连接结果不会污染新连接。
|
|
19
|
+
- Transport、Pull gate、ACK 和后台同步默认超时统一放宽到约 35 秒;Forward gate key 将默认 `limit=50` 纳入归一化,V2 续页使用初始 cursor 构造 gate key。
|
|
20
|
+
|
|
21
|
+
### 改进
|
|
22
|
+
|
|
23
|
+
- Transport/RPC 日志补充本端 AID、对端 AID 和入站/出站方向,并为乱序响应保留原请求上下文。
|
|
24
|
+
- 统一公开 RPC 错误信息,移除错误结果中的内部 `cause` 暴露。
|
|
25
|
+
- SDK、锁文件、运行时版本和 V2 E2EE envelope `sdk_version` 统一更新为 `0.5.15`。
|
|
26
|
+
|
|
27
|
+
### 测试
|
|
28
|
+
|
|
29
|
+
- 增加协议 dispatcher、payload 隔离、错误映射、日志上下文和 transport 超时测试。
|
|
30
|
+
- 扩展 Worker/native transport、Pull gate、V2 cursor、连接代次和 Node.js conformance 测试。
|
|
31
|
+
|
|
32
|
+
## 0.5.14 — 2026-09-11
|
|
33
|
+
|
|
34
|
+
### 修复
|
|
35
|
+
|
|
36
|
+
#### Pull gate watchdog 超时隔离
|
|
37
|
+
- Pull watchdog 超时只使当前 Pull job 失败,不再通过全局失效状态影响 `message.send` 等非 Pull RPC。
|
|
38
|
+
- 保留 background Pull 的全局 single-flight gate;超时但尚未真正结束的旧 Pull 继续占用 gate,后续 Pull 等待其 settle 后再执行。
|
|
39
|
+
|
|
40
|
+
### 改进
|
|
41
|
+
|
|
42
|
+
- SDK、锁文件、运行时版本和 V2 E2EE envelope `sdk_version` 统一更新为 `0.5.14`。
|
|
43
|
+
|
|
44
|
+
### 测试
|
|
45
|
+
|
|
46
|
+
- 增加 Pull 超时隔离、非 Pull RPC 投递和全局 single-flight gate 保持的单元测试。
|
|
47
|
+
|
|
9
48
|
## 0.5.13 — 2026-09-08
|
|
10
49
|
|
|
11
50
|
### 修复
|
|
@@ -6,6 +6,45 @@
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## 0.5.15 — 2026-09-15
|
|
10
|
+
|
|
11
|
+
### 修复
|
|
12
|
+
|
|
13
|
+
#### 协议事件与应用事件隔离
|
|
14
|
+
- 协议事件、transport observer 和断线回调使用独立 dispatcher,不再被慢速应用 handler 阻塞。
|
|
15
|
+
- 协议事件转发到应用层时深拷贝 payload,公开错误统一转换为 `{code, message}`,避免暴露内部错误对象。
|
|
16
|
+
|
|
17
|
+
#### Worker、Pull 与 V2 连接代次
|
|
18
|
+
- Node.js Worker transport 增加连接代次和旧响应保护;Worker 启动、运行中异常或熔断时回退本地 transport,旧连接结果不会污染新连接。
|
|
19
|
+
- Transport、Pull gate、ACK 和后台同步默认超时统一放宽到约 35 秒;Forward gate key 将默认 `limit=50` 纳入归一化,V2 续页使用初始 cursor 构造 gate key。
|
|
20
|
+
|
|
21
|
+
### 改进
|
|
22
|
+
|
|
23
|
+
- Transport/RPC 日志补充本端 AID、对端 AID 和入站/出站方向,并为乱序响应保留原请求上下文。
|
|
24
|
+
- 统一公开 RPC 错误信息,移除错误结果中的内部 `cause` 暴露。
|
|
25
|
+
- SDK、锁文件、运行时版本和 V2 E2EE envelope `sdk_version` 统一更新为 `0.5.15`。
|
|
26
|
+
|
|
27
|
+
### 测试
|
|
28
|
+
|
|
29
|
+
- 增加协议 dispatcher、payload 隔离、错误映射、日志上下文和 transport 超时测试。
|
|
30
|
+
- 扩展 Worker/native transport、Pull gate、V2 cursor、连接代次和 Node.js conformance 测试。
|
|
31
|
+
|
|
32
|
+
## 0.5.14 — 2026-09-11
|
|
33
|
+
|
|
34
|
+
### 修复
|
|
35
|
+
|
|
36
|
+
#### Pull gate watchdog 超时隔离
|
|
37
|
+
- Pull watchdog 超时只使当前 Pull job 失败,不再通过全局失效状态影响 `message.send` 等非 Pull RPC。
|
|
38
|
+
- 保留 background Pull 的全局 single-flight gate;超时但尚未真正结束的旧 Pull 继续占用 gate,后续 Pull 等待其 settle 后再执行。
|
|
39
|
+
|
|
40
|
+
### 改进
|
|
41
|
+
|
|
42
|
+
- SDK、锁文件、运行时版本和 V2 E2EE envelope `sdk_version` 统一更新为 `0.5.14`。
|
|
43
|
+
|
|
44
|
+
### 测试
|
|
45
|
+
|
|
46
|
+
- 增加 Pull 超时隔离、非 Pull RPC 投递和全局 single-flight gate 保持的单元测试。
|
|
47
|
+
|
|
9
48
|
## 0.5.13 — 2026-09-08
|
|
10
49
|
|
|
11
50
|
### 修复
|
|
@@ -700,6 +700,56 @@ foreach ($phase in $phases) {
|
|
|
700
700
|
}
|
|
701
701
|
```
|
|
702
702
|
|
|
703
|
+
TS 与浏览器 JS federation 用例使用已准备好的 `kite-ts-tester`、`kite-js-tester`,先启动容器并接入两域共享业务网络。容器不存在或依赖未准备时停止,不要在业务测试阶段执行 `up`、`--force-recreate` 或安装依赖:
|
|
704
|
+
|
|
705
|
+
```powershell
|
|
706
|
+
cd D:\modelunion\kite\docker-deploy
|
|
707
|
+
docker compose --env-file agentid.pub/local.env --project-directory . `
|
|
708
|
+
-f docker-compose.testers.yml start ts-sdk-tester js-sdk-tester
|
|
709
|
+
docker network connect kite-agentid-federation-net kite-ts-tester 2>$null
|
|
710
|
+
docker network connect kite-agentid-federation-net kite-js-tester 2>$null
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
TS 用例必须逐文件执行,失败立即停止;从失败文件续跑时,只保留该文件及后续尚未执行的文件:
|
|
714
|
+
|
|
715
|
+
```powershell
|
|
716
|
+
$tsCases = @(
|
|
717
|
+
'federation.test.ts',
|
|
718
|
+
'federation-storage.test.ts',
|
|
719
|
+
'federation-v2-thought.test.ts',
|
|
720
|
+
'federation-notify.test.ts',
|
|
721
|
+
'federation-service-proxy.test.ts'
|
|
722
|
+
)
|
|
723
|
+
foreach ($case in $tsCases) {
|
|
724
|
+
docker exec -e AUN_TEST_ISSUER_A=agentid.pub -e AUN_TEST_ISSUER_B=agentid.cn `
|
|
725
|
+
-e AUN_VERIFY_SSL=0 -w /workspace/ts kite-ts-tester bash -lc `
|
|
726
|
+
"node_modules/.bin/vitest run tests/integration/$case --no-file-parallelism --maxWorkers=1 --reporter=verbose"
|
|
727
|
+
if ($LASTEXITCODE -ne 0) { throw "TS federation 失败: $case" }
|
|
728
|
+
}
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
`federation-reconnect.test.ts` 需要按本指南的 Gateway 重启与 marker 协调流程单独运行,不并入上述无重启序列。隔离 federation 运行同一批 TS 用例时,必须显式传入 `AUN_TEST_ISSUER_A=dist-aid.com`、`AUN_TEST_ISSUER_B=dist-aid.net`,避免使用真实域默认值。
|
|
732
|
+
|
|
733
|
+
浏览器 JS federation 必须使用 Playwright,按文件串行执行。先构建一次,再分别运行 `federation.spec.ts` 与 `notify.spec.ts`;`AUN_TEST_DISABLE_LOCAL_DOCKER_PIN=1` 使浏览器使用共享 Docker DNS,不把域名 pin 到宿主机回环地址:
|
|
734
|
+
|
|
735
|
+
```powershell
|
|
736
|
+
docker exec -e AUN_TEST_ISSUER_A=agentid.pub -e AUN_TEST_ISSUER_B=agentid.cn `
|
|
737
|
+
-e AUN_TEST_DISABLE_LOCAL_DOCKER_PIN=1 -e AUN_VERIFY_SSL=0 `
|
|
738
|
+
-w /workspace/js kite-js-tester bash -lc "npm run build"
|
|
739
|
+
if ($LASTEXITCODE -ne 0) { throw 'JS federation 构建失败' }
|
|
740
|
+
|
|
741
|
+
$jsCases = @('federation.spec.ts', 'notify.spec.ts')
|
|
742
|
+
foreach ($case in $jsCases) {
|
|
743
|
+
docker exec -e AUN_TEST_ISSUER_A=agentid.pub -e AUN_TEST_ISSUER_B=agentid.cn `
|
|
744
|
+
-e AUN_TEST_DISABLE_LOCAL_DOCKER_PIN=1 -e AUN_VERIFY_SSL=0 `
|
|
745
|
+
-w /workspace/js kite-js-tester bash -lc `
|
|
746
|
+
"node_modules/.bin/playwright test --config=playwright.agentid-local.config.ts tests/e2e-browser/$case --reporter=line"
|
|
747
|
+
if ($LASTEXITCODE -ne 0) { throw "JS Playwright federation 失败: $case" }
|
|
748
|
+
}
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
失败后不得重复已通过文件;如果只需重跑 Notify,可将 `$jsCases` 改为 `@('notify.spec.ts')`。隔离 federation 运行时将两处 `agentid.pub/agentid.cn` 同时替换为 `dist-aid.com/dist-aid.net`。
|
|
752
|
+
|
|
703
753
|
除 `federation-boundary` 外,runner 在进入业务阶段前会用全新的运行目录分别完成两域固定身份登录。`auth.aid_login1` 必须依次通过 CA 的 `verify_chain`、`get_cert` 和 `ocsp_query` 数据库查询,因此登录成功就是 CA/Auth 数据库调用链可用的证据;日志必须先出现两条 `DOMAIN_READY ... evidence=aid_login`。`ca.status` 属于域内诊断 RPC,普通 AID 被 Gateway 拒绝是正确权限边界,不得为了预检放宽。任一域登录失败时,runner 会汇总两域结果并在业务 RPC 前停止;此时应先检查对应 MySQL 容器状态和 CA/Auth 重连日志,不能把 `ca.verify_chain: database not connected` 归因于联邦身份边界。
|
|
704
754
|
|
|
705
755
|
若 MySQL 日志出现 `Trying to access page ... outside the tablespace bounds`、`Server exits` 或 InnoDB assertion failure,只能判定运行中的 InnoDB 已访问非法页并退出,不能仅凭页越界日志判定磁盘数据文件已经物理损坏。立即停止后续业务阶段,保留原容器、挂载、完整错误日志和冷态只读副本;不得通过清库、删除表、替换固定 AID 材料或运行 `setup_aids.py` 掩盖现场。
|
|
@@ -984,7 +1034,7 @@ docker exec main-dist-client-a python /test/cross_domain_service_proxy.py
|
|
|
984
1034
|
- reconnect 相关测试
|
|
985
1035
|
- `e2e_real.py`、`e2e_comprehensive.py`、`e2e_coverage_gaps.py` 这类仍依赖宿主机 Docker 协调并可能硬编码旧容器名的脚本
|
|
986
1036
|
|
|
987
|
-
### TypeScript / Go federation
|
|
1037
|
+
### TypeScript / Go federation(dist-aid.com / dist-aid.net)
|
|
988
1038
|
|
|
989
1039
|
先复用已有 Tester,不得在断网阶段通过 `up` 或 `--force-recreate` 新建:
|
|
990
1040
|
|
|
@@ -996,12 +1046,43 @@ if ($missing) { throw "Federation Tester 尚未完成离线依赖准备,停止
|
|
|
996
1046
|
docker compose --env-file ../.env.distributed -f docker-compose.yml start ts-tester go-tester
|
|
997
1047
|
```
|
|
998
1048
|
|
|
1049
|
+
TS federation 按文件串行执行;失败后只保留失败文件及其后的未执行文件。隔离环境必须显式覆盖所有运行时域名变量,避免 TS 默认回到 `agentid.pub/agentid.cn`:
|
|
1050
|
+
|
|
999
1051
|
```powershell
|
|
1000
|
-
|
|
1052
|
+
$tsCases = @(
|
|
1053
|
+
'federation.test.ts',
|
|
1054
|
+
'federation-storage.test.ts',
|
|
1055
|
+
'federation-v2-thought.test.ts',
|
|
1056
|
+
'federation-notify.test.ts',
|
|
1057
|
+
'federation-service-proxy.test.ts'
|
|
1058
|
+
)
|
|
1059
|
+
foreach ($case in $tsCases) {
|
|
1060
|
+
docker exec `
|
|
1061
|
+
-e AUN_TEST_ISSUER=dist-aid.com `
|
|
1062
|
+
-e AUN_TEST_ISSUER_A=dist-aid.com `
|
|
1063
|
+
-e AUN_TEST_ISSUER_B=dist-aid.net `
|
|
1064
|
+
-e LOCAL_ISSUER=dist-aid.com `
|
|
1065
|
+
-e REMOTE_ISSUER=dist-aid.net `
|
|
1066
|
+
-e AUN_VERIFY_SSL=0 `
|
|
1067
|
+
-w /workspace/ts main-dist-federation-ts-tester bash -lc `
|
|
1068
|
+
"node_modules/.bin/vitest run tests/integration/$case --no-file-parallelism --maxWorkers=1 --reporter=verbose"
|
|
1069
|
+
if ($LASTEXITCODE -ne 0) { throw "TS federation 失败: $case" }
|
|
1070
|
+
}
|
|
1001
1071
|
```
|
|
1002
1072
|
|
|
1073
|
+
`federation-reconnect.test.ts` 需要按 Gateway 重启与 marker 协调流程单独运行,不并入上述无重启序列。
|
|
1074
|
+
|
|
1075
|
+
Go federation 显式覆盖同样的 issuer 变量;Service Proxy 用例还需覆盖 `LOCAL_ISSUER/REMOTE_ISSUER`:
|
|
1076
|
+
|
|
1003
1077
|
```powershell
|
|
1004
|
-
docker exec
|
|
1078
|
+
docker exec `
|
|
1079
|
+
-e AUN_TEST_ISSUER_A=dist-aid.com `
|
|
1080
|
+
-e AUN_TEST_ISSUER_B=dist-aid.net `
|
|
1081
|
+
-e LOCAL_ISSUER=dist-aid.com `
|
|
1082
|
+
-e REMOTE_ISSUER=dist-aid.net `
|
|
1083
|
+
-e AUN_VERIFY_SSL=0 `
|
|
1084
|
+
main-dist-federation-go-tester sh -lc `
|
|
1085
|
+
"cd /workspace/go && /usr/local/go/bin/go test -tags integration . -run Federation -count=1 -v"
|
|
1005
1086
|
```
|
|
1006
1087
|
|
|
1007
1088
|
如果用临时 Go 容器,需要加入分布式 federation 网络:
|
|
@@ -1066,7 +1147,7 @@ cd D:\modelunion\kite\docker-deploy
|
|
|
1066
1147
|
docker compose --env-file NUL -f docker-compose.yml up -d
|
|
1067
1148
|
```
|
|
1068
1149
|
|
|
1069
|
-
这一路径必须继续保持默认单实例值,不使用 `dist-*` 域名,不使用 `data-dist`,也不使用 `kite-dist-*`
|
|
1150
|
+
这一路径必须继续保持默认单实例值,不使用 `dist-*` 域名,不使用 `data-dist`,也不使用 `kite-dist-*` 容器名。默认单实例测试按主指南执行;本地真实域名双域按本指南 `agentid.pub/agentid.cn` 专节执行;隔离 federation 双域仅按 `dist-aid.com/dist-aid.net` 专节执行。两套双域的 CA、固定身份、数据根、Compose project、Tester 和日志不得混用。
|
|
1070
1151
|
|
|
1071
1152
|
如果目标是证明“同一批非 reconnect / 非 mail 用例可在分布式控制 master 上通过”,使用本指南的 `dist-agentid.pub` 和临时测试容器命令。
|
|
1072
1153
|
|
package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md
CHANGED
|
@@ -120,7 +120,7 @@ Docker Desktop 可能把 Windows bind source 返回为 `/run/desktop/mnt/host/<
|
|
|
120
120
|
| Python | `kite-python-sdk-tester` 容器内 `/tests/*.py` | 见分布式指南的 `main-dist-client-a` / `main-dist-client-b` | Docker 测试容器为主,少数 reconnect 脚本在宿主机 |
|
|
121
121
|
| TypeScript | `kite-ts-tester` 容器内 `tests/integration`、`tests/e2e` | 见分布式指南的 `main-dist-federation-ts-tester` | Docker 测试容器为主,reconnect 由宿主机协调 |
|
|
122
122
|
| Go | `kite-go-tester` 容器或临时 `golang:1.24-bookworm` 容器 | 见分布式指南的 `main-dist-federation-go-tester` | Docker 网络内运行,reconnect 由宿主机 marker 协调 |
|
|
123
|
-
| JavaScript | `kite-js-tester` 容器内运行 Vitest 或 Playwright `tests/e2e-browser` |
|
|
123
|
+
| JavaScript | `kite-js-tester` 容器内运行 Vitest 或 Playwright `tests/e2e-browser` | 见分布式指南的真实域名双域 Playwright 入口 | 默认单实例优先使用带 Chrome 的 Docker Tester;宿主机 Playwright 可作为备选 |
|
|
124
124
|
|
|
125
125
|
JS 的真实 E2E 统一使用 `js/tests/e2e-browser`,不要把浏览器 E2E 混入 `tests/integration`。`kite-js-tester` 已提供 Linux Chrome;宿主机运行时仍使用 `playwright.agentid-local.config.ts`。
|
|
126
126
|
|
|
@@ -25,6 +25,7 @@ export declare class MessageDeliveryEngine {
|
|
|
25
25
|
constructor(runtime: ClientRuntime);
|
|
26
26
|
private isPullOperationCurrent;
|
|
27
27
|
private ensurePullOperationCurrent;
|
|
28
|
+
private isPullScopedSource;
|
|
28
29
|
private forwardCoordinator;
|
|
29
30
|
private confirmPlainForwardAck;
|
|
30
31
|
commitPlainForwardPage(ns: string, afterSeq: number, method: 'message.pull' | 'group.pull', result: unknown, groupId?: string): Promise<{
|
package/dist/client/delivery.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { slotIsolationKey } from '../config.js';
|
|
2
|
+
import { trafficLogContext, withLogContext } from '../logger.js';
|
|
2
3
|
import { ConnectionState, isJsonObject } from '../types.js';
|
|
3
4
|
import { validateGroupAIDFormat } from '../validators.js';
|
|
4
5
|
import { hasGroupMentionModeField, normalizeGroupMentionMode } from './mention-mode.js';
|
|
@@ -91,20 +92,19 @@ export class MessageDeliveryEngine {
|
|
|
91
92
|
constructor(runtime) {
|
|
92
93
|
this.runtime = runtime;
|
|
93
94
|
}
|
|
94
|
-
isPullOperationCurrent() {
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return true;
|
|
99
|
-
const pipeline = client._rpcPipeline;
|
|
100
|
-
return typeof pipeline?.isPullGenerationCurrent === 'function'
|
|
101
|
-
? pipeline.isPullGenerationCurrent(generation)
|
|
95
|
+
isPullOperationCurrent(namespace = '') {
|
|
96
|
+
const pipeline = this.runtime.client._rpcPipeline;
|
|
97
|
+
return typeof pipeline?.isPullNamespaceCurrent === 'function'
|
|
98
|
+
? pipeline.isPullNamespaceCurrent(namespace)
|
|
102
99
|
: true;
|
|
103
100
|
}
|
|
104
|
-
ensurePullOperationCurrent() {
|
|
105
|
-
if (!this.isPullOperationCurrent())
|
|
101
|
+
ensurePullOperationCurrent(namespace = '') {
|
|
102
|
+
if (!this.isPullOperationCurrent(namespace))
|
|
106
103
|
throw new Error('pull invalidated');
|
|
107
104
|
}
|
|
105
|
+
isPullScopedSource(source) {
|
|
106
|
+
return source === 'pull';
|
|
107
|
+
}
|
|
108
108
|
forwardCoordinator() {
|
|
109
109
|
const coordinator = this.runtime.client._v2E2EE;
|
|
110
110
|
if (!coordinator
|
|
@@ -120,7 +120,7 @@ export class MessageDeliveryEngine {
|
|
|
120
120
|
}
|
|
121
121
|
async confirmPlainForwardAck(ns, method, ackSeq, groupId = '') {
|
|
122
122
|
const client = this.runtime.client;
|
|
123
|
-
this.ensurePullOperationCurrent();
|
|
123
|
+
this.ensurePullOperationCurrent(ns);
|
|
124
124
|
const coordinator = this.forwardCoordinator();
|
|
125
125
|
const generation = this.captureInlineAckGeneration();
|
|
126
126
|
coordinator.recordForwardAck(ns, ackSeq);
|
|
@@ -139,7 +139,7 @@ export class MessageDeliveryEngine {
|
|
|
139
139
|
_rpc_background: true,
|
|
140
140
|
};
|
|
141
141
|
const result = await client._rpcPipeline.rawCall(method, params, { background: true });
|
|
142
|
-
this.ensurePullOperationCurrent();
|
|
142
|
+
this.ensurePullOperationCurrent(ns);
|
|
143
143
|
const actualAckSeq = this.resolveForwardAckSeq(result, ackSeq);
|
|
144
144
|
if (actualAckSeq < ackSeq) {
|
|
145
145
|
throw new Error(`${method} server ACK watermark ${actualAckSeq} is below requested ${ackSeq}`);
|
|
@@ -157,7 +157,7 @@ export class MessageDeliveryEngine {
|
|
|
157
157
|
throw new Error(`${method} response must be an object`);
|
|
158
158
|
}
|
|
159
159
|
const client = this.runtime.client;
|
|
160
|
-
this.ensurePullOperationCurrent();
|
|
160
|
+
this.ensurePullOperationCurrent(ns);
|
|
161
161
|
this.syncStarted(ns);
|
|
162
162
|
const response = result;
|
|
163
163
|
const messages = deduplicatePlainForwardPageMessages(response.messages, afterSeq);
|
|
@@ -189,20 +189,20 @@ export class MessageDeliveryEngine {
|
|
|
189
189
|
let committed = false;
|
|
190
190
|
try {
|
|
191
191
|
for (const rawMessage of messages) {
|
|
192
|
-
this.ensurePullOperationCurrent();
|
|
192
|
+
this.ensurePullOperationCurrent(ns);
|
|
193
193
|
const seq = positiveSafeSequenceHint(rawMessage.seq);
|
|
194
194
|
if (method === 'message.pull') {
|
|
195
195
|
const appEvent = this.p2pAppEventForMessage(rawMessage);
|
|
196
196
|
if (await this.publishPulledMessage(appEvent.event, ns, seq, appEvent.payload, false)) {
|
|
197
197
|
publishedCount += 1;
|
|
198
198
|
}
|
|
199
|
-
this.ensurePullOperationCurrent();
|
|
199
|
+
this.ensurePullOperationCurrent(ns);
|
|
200
200
|
continue;
|
|
201
201
|
}
|
|
202
202
|
const message = normalizeGroupMentionMode(rawMessage);
|
|
203
203
|
if (this.recallEventFromGroupMessage(message)) {
|
|
204
204
|
if (await this.publishGroupRecallTombstone(groupId, seq, message, 'pull')) {
|
|
205
|
-
this.ensurePullOperationCurrent();
|
|
205
|
+
this.ensurePullOperationCurrent(ns);
|
|
206
206
|
this.markPublishedSeq(ns, seq);
|
|
207
207
|
publishedCount += 1;
|
|
208
208
|
}
|
|
@@ -211,11 +211,11 @@ export class MessageDeliveryEngine {
|
|
|
211
211
|
this.markPublishedSeq(ns, seq);
|
|
212
212
|
}
|
|
213
213
|
else if (await this.publishPulledMessage('group.message_created', ns, seq, message, false)) {
|
|
214
|
-
this.ensurePullOperationCurrent();
|
|
214
|
+
this.ensurePullOperationCurrent(ns);
|
|
215
215
|
publishedCount += 1;
|
|
216
216
|
}
|
|
217
217
|
}
|
|
218
|
-
this.ensurePullOperationCurrent();
|
|
218
|
+
this.ensurePullOperationCurrent(ns);
|
|
219
219
|
client._seqTracker.onPullResult(ns, messages, afterSeq, Number.isSafeInteger(result.contiguous_seq)
|
|
220
220
|
? result.contiguous_seq
|
|
221
221
|
: undefined);
|
|
@@ -224,21 +224,21 @@ export class MessageDeliveryEngine {
|
|
|
224
224
|
client._seqTracker.forceContiguousSeq(ns, commitTarget);
|
|
225
225
|
}
|
|
226
226
|
if (client._seqTracker.getContiguousSeq(ns) !== pageContigBefore) {
|
|
227
|
-
this.ensurePullOperationCurrent();
|
|
227
|
+
this.ensurePullOperationCurrent(ns);
|
|
228
228
|
await this.drainOrderedMessages(ns, undefined, false, false, 'pull');
|
|
229
|
-
this.ensurePullOperationCurrent();
|
|
229
|
+
this.ensurePullOperationCurrent(ns);
|
|
230
230
|
await client._commitSeqTrackerState(ns);
|
|
231
231
|
}
|
|
232
232
|
committed = true;
|
|
233
233
|
}
|
|
234
234
|
catch (exc) {
|
|
235
|
-
if (this.isPullOperationCurrent() && !committed && typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
|
|
235
|
+
if (this.isPullOperationCurrent(ns) && !committed && typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
|
|
236
236
|
client._seqTracker.restoreNamespaceSnapshot(ns, pageTrackerSnapshot);
|
|
237
237
|
this.dropSeqTrackerPending(ns);
|
|
238
238
|
}
|
|
239
239
|
throw exc;
|
|
240
240
|
}
|
|
241
|
-
this.ensurePullOperationCurrent();
|
|
241
|
+
this.ensurePullOperationCurrent(ns);
|
|
242
242
|
const committedAck = client._seqTracker.getContiguousSeq(ns);
|
|
243
243
|
if (deferredServerCursor > 0 && committedAck >= deferredServerCursor) {
|
|
244
244
|
coordinator.clearForwardCursor(ns, committedAck);
|
|
@@ -269,7 +269,7 @@ export class MessageDeliveryEngine {
|
|
|
269
269
|
if (clampedAckSeq < pendingAckSeq) {
|
|
270
270
|
throw new Error(`${ackMethod} cannot confirm pending Forward watermark ${pendingAckSeq}`);
|
|
271
271
|
}
|
|
272
|
-
this.ensurePullOperationCurrent();
|
|
272
|
+
this.ensurePullOperationCurrent(ns);
|
|
273
273
|
await this.confirmPlainForwardAck(ns, ackMethod, pendingAckSeq, groupId);
|
|
274
274
|
}
|
|
275
275
|
this.syncProgress(ns, {
|
|
@@ -654,15 +654,19 @@ export class MessageDeliveryEngine {
|
|
|
654
654
|
}
|
|
655
655
|
async publishOrderedQueueItem(ns, event, seq, payload, source, pullResponse = false, deliveryContext) {
|
|
656
656
|
const client = this.runtime.client;
|
|
657
|
-
this.
|
|
657
|
+
const pullScoped = this.isPullScopedSource(source);
|
|
658
|
+
if (pullScoped)
|
|
659
|
+
this.ensurePullOperationCurrent(ns);
|
|
658
660
|
if (event === 'group.changed' && this.isGroupEventNamespace(ns)) {
|
|
659
661
|
await this.publishOrderedGroupChanged(payload, source);
|
|
660
|
-
|
|
662
|
+
if (pullScoped)
|
|
663
|
+
this.ensurePullOperationCurrent(ns);
|
|
661
664
|
return;
|
|
662
665
|
}
|
|
663
666
|
if (event === 'message.recalled') {
|
|
664
667
|
await this.publishMessageRecallTombstone(seq, payload, source);
|
|
665
|
-
|
|
668
|
+
if (pullScoped)
|
|
669
|
+
this.ensurePullOperationCurrent(ns);
|
|
666
670
|
return;
|
|
667
671
|
}
|
|
668
672
|
if (pullResponse) {
|
|
@@ -675,7 +679,8 @@ export class MessageDeliveryEngine {
|
|
|
675
679
|
if (isPromiseLike(published))
|
|
676
680
|
await published;
|
|
677
681
|
}
|
|
678
|
-
|
|
682
|
+
if (pullScoped)
|
|
683
|
+
this.ensurePullOperationCurrent(ns);
|
|
679
684
|
}
|
|
680
685
|
async publishOrderedGroupChanged(payload, source = 'ordered') {
|
|
681
686
|
const client = this.runtime.client;
|
|
@@ -1272,7 +1277,10 @@ export class MessageDeliveryEngine {
|
|
|
1272
1277
|
};
|
|
1273
1278
|
if (opts.extra)
|
|
1274
1279
|
record.extra = opts.extra;
|
|
1275
|
-
|
|
1280
|
+
const direction = event === 'message.send' || event === 'group.send'
|
|
1281
|
+
|| event === 'message.thought.put' || event === 'group.thought.put'
|
|
1282
|
+
? 'outbound' : 'inbound';
|
|
1283
|
+
withLogContext(client._clientLog, trafficLogContext(direction, message)).debug(`message.debug ${client._debugJson(record)}`);
|
|
1276
1284
|
}
|
|
1277
1285
|
logAppMessagePublish(event, payload, source) {
|
|
1278
1286
|
if (!['message.received', 'message.undecryptable', 'group.message_created', 'group.message_undecryptable'].includes(event)) {
|
|
@@ -1594,8 +1602,7 @@ export class MessageDeliveryEngine {
|
|
|
1594
1602
|
const afterSeq = client._seqTracker.getContiguousSeq(ns);
|
|
1595
1603
|
client._clientLog.debug(`auto pull group messages start: group=${groupId}, after_seq=${afterSeq}, seq=${String(notification.seq ?? '')}`);
|
|
1596
1604
|
const operation = async () => {
|
|
1597
|
-
const
|
|
1598
|
-
const messages = await client._pullGroupV2(groupId, pullAfterSeq, 50, { gateLocked: true });
|
|
1605
|
+
const messages = await client._pullGroupV2(groupId, afterSeq, 50, { gateLocked: true });
|
|
1599
1606
|
this.prunePushedSeqs(ns);
|
|
1600
1607
|
return messages.length;
|
|
1601
1608
|
};
|
|
@@ -2194,6 +2201,7 @@ export class MessageDeliveryEngine {
|
|
|
2194
2201
|
const run = async (request, background, forceForward = false) => {
|
|
2195
2202
|
const invoke = async () => {
|
|
2196
2203
|
const live = this.syncState(ns);
|
|
2204
|
+
const after = Number(request.after_seq ?? 0) || 0;
|
|
2197
2205
|
if (request.window_mode === 'tail' && !forceTail && typeof client._seqTracker.getSyncState === 'function' && pushSeq <= live.head)
|
|
2198
2206
|
return { messages: [], raw_count: 0 };
|
|
2199
2207
|
if (request.window_mode !== 'tail' && !forceForward && !headForward && live.ack >= live.tail - 1)
|
|
@@ -2203,13 +2211,12 @@ export class MessageDeliveryEngine {
|
|
|
2203
2211
|
if (request.window_mode === 'tail') {
|
|
2204
2212
|
const tailResult = await client._v2E2EE.pullV2TailInternal({
|
|
2205
2213
|
window_mode: 'tail',
|
|
2206
|
-
after_seq:
|
|
2214
|
+
after_seq: after,
|
|
2207
2215
|
limit: pageLimit,
|
|
2208
2216
|
}, true);
|
|
2209
2217
|
tailHead = this.syncState(ns).head;
|
|
2210
2218
|
return tailResult;
|
|
2211
2219
|
}
|
|
2212
|
-
const after = live.ack;
|
|
2213
2220
|
const opts = { gateLocked: true };
|
|
2214
2221
|
if (request.max_pages !== undefined)
|
|
2215
2222
|
opts.maxPages = request.max_pages;
|
|
@@ -2271,6 +2278,7 @@ export class MessageDeliveryEngine {
|
|
|
2271
2278
|
const run = async (request, background, forceForward = false) => {
|
|
2272
2279
|
const invoke = async () => {
|
|
2273
2280
|
const live = this.syncState(ns);
|
|
2281
|
+
const after = Number(request.after_seq ?? 0) || 0;
|
|
2274
2282
|
if (request.window_mode === 'tail' && !forceTail && typeof client._seqTracker.getSyncState === 'function' && pushSeq <= live.head)
|
|
2275
2283
|
return { messages: [], raw_count: 0 };
|
|
2276
2284
|
if (request.window_mode !== 'tail' && !forceForward && !headForward && live.ack >= live.tail - 1)
|
|
@@ -2281,7 +2289,7 @@ export class MessageDeliveryEngine {
|
|
|
2281
2289
|
const tailResult = await client._v2E2EE.pullGroupV2TailInternal({
|
|
2282
2290
|
group_id: groupId,
|
|
2283
2291
|
window_mode: 'tail',
|
|
2284
|
-
after_seq:
|
|
2292
|
+
after_seq: after,
|
|
2285
2293
|
limit: pageLimit,
|
|
2286
2294
|
}, true);
|
|
2287
2295
|
tailHead = this.syncState(ns).head;
|
|
@@ -2290,7 +2298,7 @@ export class MessageDeliveryEngine {
|
|
|
2290
2298
|
const opts = { gateLocked: true };
|
|
2291
2299
|
if (request.max_pages !== undefined)
|
|
2292
2300
|
opts.maxPages = request.max_pages;
|
|
2293
|
-
const messages = await client._pullGroupV2(groupId,
|
|
2301
|
+
const messages = await client._pullGroupV2(groupId, after, pageLimit, opts);
|
|
2294
2302
|
return { messages, raw_count: messages.length };
|
|
2295
2303
|
};
|
|
2296
2304
|
if (!client._rpcPipeline)
|
|
@@ -2333,8 +2341,7 @@ export class MessageDeliveryEngine {
|
|
|
2333
2341
|
const client = this.runtime.client;
|
|
2334
2342
|
const request = { after_seq: this.syncState(ns).ack, limit: pageLimit, max_pages: 1 };
|
|
2335
2343
|
const invoke = async () => {
|
|
2336
|
-
const
|
|
2337
|
-
const messages = await client._pullV2(after, pageLimit, { gateLocked: true, maxPages: 1 });
|
|
2344
|
+
const messages = await client._pullV2(Number(request.after_seq), pageLimit, { gateLocked: true, maxPages: 1 });
|
|
2338
2345
|
return { messages, raw_count: messages.length };
|
|
2339
2346
|
};
|
|
2340
2347
|
if (!client._rpcPipeline)
|
|
@@ -2348,8 +2355,7 @@ export class MessageDeliveryEngine {
|
|
|
2348
2355
|
const maxPages = Math.max(1, Math.ceil(Math.max(0, throughSeq - ack) / pageLimit));
|
|
2349
2356
|
const request = { group_id: groupId, after_seq: ack, limit: pageLimit, max_pages: maxPages };
|
|
2350
2357
|
const invoke = async () => {
|
|
2351
|
-
const
|
|
2352
|
-
const messages = await client._pullGroupV2(groupId, after, pageLimit, { gateLocked: true, maxPages });
|
|
2358
|
+
const messages = await client._pullGroupV2(groupId, Number(request.after_seq), pageLimit, { gateLocked: true, maxPages });
|
|
2353
2359
|
return { messages, raw_count: messages.length };
|
|
2354
2360
|
};
|
|
2355
2361
|
if (!client._rpcPipeline)
|
|
@@ -3489,7 +3495,9 @@ export class MessageDeliveryEngine {
|
|
|
3489
3495
|
}
|
|
3490
3496
|
async drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true, source = 'push', context) {
|
|
3491
3497
|
const client = this.runtime.client;
|
|
3492
|
-
this.
|
|
3498
|
+
const pullScoped = this.isPullScopedSource(source);
|
|
3499
|
+
if (pullScoped)
|
|
3500
|
+
this.ensurePullOperationCurrent(ns);
|
|
3493
3501
|
const queue = client._pendingOrderedMsgs.get(ns);
|
|
3494
3502
|
if (!queue || queue.size === 0)
|
|
3495
3503
|
return;
|
|
@@ -3498,7 +3506,8 @@ export class MessageDeliveryEngine {
|
|
|
3498
3506
|
const drainContexts = new Map();
|
|
3499
3507
|
try {
|
|
3500
3508
|
while (true) {
|
|
3501
|
-
|
|
3509
|
+
if (pullScoped)
|
|
3510
|
+
this.ensurePullOperationCurrent(ns);
|
|
3502
3511
|
const contig = client._seqTracker.getContiguousSeq(ns);
|
|
3503
3512
|
const ready = [...queue.keys()]
|
|
3504
3513
|
.filter((seq) => seq <= contig && (beforeSeq === undefined || seq < beforeSeq))
|
|
@@ -3536,7 +3545,8 @@ export class MessageDeliveryEngine {
|
|
|
3536
3545
|
}
|
|
3537
3546
|
}
|
|
3538
3547
|
await this.publishOrderedQueueItem(ns, item.event, seq, item.payload, itemSource, pullResponse, itemContext);
|
|
3539
|
-
|
|
3548
|
+
if (pullScoped)
|
|
3549
|
+
this.ensurePullOperationCurrent(ns);
|
|
3540
3550
|
this.markPublishedSeq(ns, seq);
|
|
3541
3551
|
client._markOrderedSeqDelivered(ns, seq);
|
|
3542
3552
|
delivered = true;
|
|
@@ -3552,7 +3562,8 @@ export class MessageDeliveryEngine {
|
|
|
3552
3562
|
if (queue.size === 0) {
|
|
3553
3563
|
client._pendingOrderedMsgs.delete(ns);
|
|
3554
3564
|
if (delivered && persist) {
|
|
3555
|
-
|
|
3565
|
+
if (pullScoped)
|
|
3566
|
+
this.ensurePullOperationCurrent(ns);
|
|
3556
3567
|
await this.saveSeqTrackerState();
|
|
3557
3568
|
}
|
|
3558
3569
|
}
|
|
@@ -3581,12 +3592,15 @@ export class MessageDeliveryEngine {
|
|
|
3581
3592
|
}
|
|
3582
3593
|
async publishOrderedMessageInternal(event, ns, seq, payload, source, context) {
|
|
3583
3594
|
const client = this.runtime.client;
|
|
3584
|
-
this.
|
|
3595
|
+
const pullScoped = this.isPullScopedSource(source);
|
|
3596
|
+
if (pullScoped)
|
|
3597
|
+
this.ensurePullOperationCurrent(ns);
|
|
3585
3598
|
const seqNum = Number(seq);
|
|
3586
3599
|
if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0) {
|
|
3587
3600
|
client._clientLog.debug(`publish ordered direct(no-seq): event=${event}, ns=${ns || '<none>'}, seq=${String(seq)}`);
|
|
3588
3601
|
await this.publishOrderedQueueItem(ns, event, seqNum, payload, source, false, context);
|
|
3589
|
-
|
|
3602
|
+
if (pullScoped)
|
|
3603
|
+
this.ensurePullOperationCurrent(ns);
|
|
3590
3604
|
return true;
|
|
3591
3605
|
}
|
|
3592
3606
|
if (client._pushedSeqs.get(ns)?.has(seqNum)) {
|
|
@@ -3604,7 +3618,8 @@ export class MessageDeliveryEngine {
|
|
|
3604
3618
|
return false;
|
|
3605
3619
|
}
|
|
3606
3620
|
await this.drainOrderedMessages(ns, seqNum, false, true, source, context);
|
|
3607
|
-
|
|
3621
|
+
if (pullScoped)
|
|
3622
|
+
this.ensurePullOperationCurrent(ns);
|
|
3608
3623
|
if (client._pushedSeqs.get(ns)?.has(seqNum)) {
|
|
3609
3624
|
client._clientLog.debug(`publish ordered skipped after-drain duplicate: event=${event}, ns=${ns}, seq=${seqNum}`);
|
|
3610
3625
|
return false;
|
|
@@ -3614,32 +3629,38 @@ export class MessageDeliveryEngine {
|
|
|
3614
3629
|
if (queue && queue.size === 0)
|
|
3615
3630
|
client._pendingOrderedMsgs.delete(ns);
|
|
3616
3631
|
await this.publishOrderedQueueItem(ns, event, seqNum, payload, source, false, context);
|
|
3617
|
-
|
|
3632
|
+
if (pullScoped)
|
|
3633
|
+
this.ensurePullOperationCurrent(ns);
|
|
3618
3634
|
this.markPublishedSeq(ns, seqNum);
|
|
3619
3635
|
client._markOrderedSeqDelivered(ns, seqNum);
|
|
3620
3636
|
client._clientLog.debug(`publish ordered delivered: event=${event}, ns=${ns}, seq=${seqNum}`);
|
|
3621
3637
|
await this.drainOrderedMessages(ns, undefined, false, true, source, context);
|
|
3622
3638
|
if (!client._pendingOrderedMsgs.get(ns)) {
|
|
3623
|
-
|
|
3639
|
+
if (pullScoped)
|
|
3640
|
+
this.ensurePullOperationCurrent(ns);
|
|
3624
3641
|
await this.saveSeqTrackerState();
|
|
3625
3642
|
}
|
|
3626
3643
|
return true;
|
|
3627
3644
|
}
|
|
3628
3645
|
async publishPulledMessage(event, ns, seq, payload, persist = true, source = 'pull') {
|
|
3629
3646
|
const client = this.runtime.client;
|
|
3630
|
-
this.
|
|
3647
|
+
const pullScoped = this.isPullScopedSource(source);
|
|
3648
|
+
if (pullScoped)
|
|
3649
|
+
this.ensurePullOperationCurrent(ns);
|
|
3631
3650
|
const seqNum = Number(seq);
|
|
3632
3651
|
if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0 || !ns) {
|
|
3633
3652
|
client._clientLog.debug(`publish pulled direct(no-seq): event=${event}, ns=${ns || '<none>'}, seq=${String(seq)}`);
|
|
3634
3653
|
if (event === 'message.recalled') {
|
|
3635
3654
|
const published = await this.publishMessageRecallTombstone(seq, payload, source);
|
|
3636
|
-
|
|
3655
|
+
if (pullScoped)
|
|
3656
|
+
this.ensurePullOperationCurrent(ns);
|
|
3637
3657
|
return published;
|
|
3638
3658
|
}
|
|
3639
3659
|
const published = client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source));
|
|
3640
3660
|
if (isPromiseLike(published))
|
|
3641
3661
|
await published;
|
|
3642
|
-
|
|
3662
|
+
if (pullScoped)
|
|
3663
|
+
this.ensurePullOperationCurrent(ns);
|
|
3643
3664
|
return true;
|
|
3644
3665
|
}
|
|
3645
3666
|
const queue = client._pendingOrderedMsgs.get(ns);
|
|
@@ -3651,27 +3672,32 @@ export class MessageDeliveryEngine {
|
|
|
3651
3672
|
return false;
|
|
3652
3673
|
}
|
|
3653
3674
|
await this.drainOrderedMessages(ns, seqNum, true, persist, source);
|
|
3654
|
-
|
|
3675
|
+
if (pullScoped)
|
|
3676
|
+
this.ensurePullOperationCurrent(ns);
|
|
3655
3677
|
queue?.delete(seqNum);
|
|
3656
3678
|
if (queue && queue.size === 0)
|
|
3657
3679
|
client._pendingOrderedMsgs.delete(ns);
|
|
3658
3680
|
if (event === 'message.recalled') {
|
|
3659
3681
|
const recallPublished = await this.publishMessageRecallTombstone(seqNum, payload, source);
|
|
3660
|
-
|
|
3682
|
+
if (pullScoped)
|
|
3683
|
+
this.ensurePullOperationCurrent(ns);
|
|
3661
3684
|
this.markPublishedSeq(ns, seqNum);
|
|
3662
3685
|
client._clientLog.debug(`publish pulled delivered: event=${event}, ns=${ns}, seq=${seqNum}`);
|
|
3663
3686
|
await this.drainOrderedMessages(ns, undefined, true, persist, source);
|
|
3664
|
-
|
|
3687
|
+
if (pullScoped)
|
|
3688
|
+
this.ensurePullOperationCurrent(ns);
|
|
3665
3689
|
return recallPublished;
|
|
3666
3690
|
}
|
|
3667
3691
|
const published = client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source));
|
|
3668
3692
|
if (isPromiseLike(published))
|
|
3669
3693
|
await published;
|
|
3670
|
-
|
|
3694
|
+
if (pullScoped)
|
|
3695
|
+
this.ensurePullOperationCurrent(ns);
|
|
3671
3696
|
this.markPublishedSeq(ns, seqNum);
|
|
3672
3697
|
client._clientLog.debug(`publish pulled delivered: event=${event}, ns=${ns}, seq=${seqNum}`);
|
|
3673
3698
|
await this.drainOrderedMessages(ns, undefined, true, persist, source);
|
|
3674
|
-
|
|
3699
|
+
if (pullScoped)
|
|
3700
|
+
this.ensurePullOperationCurrent(ns);
|
|
3675
3701
|
return true;
|
|
3676
3702
|
}
|
|
3677
3703
|
}
|