@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 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
- docker exec main-dist-federation-ts-tester bash -lc "cd /workspace/ts && node_modules/.bin/vitest run tests/integration/federation.test.ts tests/integration/federation-storage.test.ts"
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 main-dist-federation-go-tester sh -lc "cd /workspace/go && /usr/local/go/bin/go test -tags integration . -run Federation -count=1 -v"
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-*` 容器名。默认单实例测试按主指南执行;双域测试只按本指南的 `dist-aid.*` 环境执行。
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
 
@@ -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` | 当前无独立双域 JS 浏览器 E2E 标准命令 | 默认单实例优先使用带 Chrome 的 Docker Tester;宿主机 Playwright 可作为备选 |
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<{
@@ -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 client = this.runtime.client;
96
- const generation = client._pullOperationGeneration;
97
- if (generation === undefined)
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.ensurePullOperationCurrent();
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
- this.ensurePullOperationCurrent();
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
- this.ensurePullOperationCurrent();
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
- this.ensurePullOperationCurrent();
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
- client._clientLog.debug(`message.debug ${client._debugJson(record)}`);
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 pullAfterSeq = client._seqTracker.getContiguousSeq(ns);
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: live.ack,
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: live.ack,
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, live.ack, pageLimit, opts);
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 after = this.syncState(ns).ack;
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 after = this.syncState(ns).ack;
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.ensurePullOperationCurrent();
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
- this.ensurePullOperationCurrent();
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
- this.ensurePullOperationCurrent();
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
- this.ensurePullOperationCurrent();
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.ensurePullOperationCurrent();
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
- this.ensurePullOperationCurrent();
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
- this.ensurePullOperationCurrent();
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
- this.ensurePullOperationCurrent();
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
- this.ensurePullOperationCurrent();
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.ensurePullOperationCurrent();
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
- this.ensurePullOperationCurrent();
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
- this.ensurePullOperationCurrent();
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
- this.ensurePullOperationCurrent();
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
- this.ensurePullOperationCurrent();
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
- this.ensurePullOperationCurrent();
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
- this.ensurePullOperationCurrent();
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
- this.ensurePullOperationCurrent();
3699
+ if (pullScoped)
3700
+ this.ensurePullOperationCurrent(ns);
3675
3701
  return true;
3676
3702
  }
3677
3703
  }