@zhushanwen/subagent-core 0.8.0 → 0.9.0

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 (167) hide show
  1. package/dist/{chunk-OL5BK4VN.js → chunk-LZJOEDGQ.js} +455 -203
  2. package/dist/engine-discovery-scan-Da4Dkzsi.d.cts +818 -0
  3. package/dist/engine-discovery-scan-Da4Dkzsi.d.ts +818 -0
  4. package/dist/execution/engine/engine-discovery-scan.cjs +216 -155
  5. package/dist/execution/engine/engine-discovery-scan.d.cts +1 -2
  6. package/dist/execution/engine/engine-discovery-scan.d.ts +1 -2
  7. package/dist/execution/engine/engine-discovery-scan.js +1 -1
  8. package/dist/execution/relay-env.js +5 -4
  9. package/dist/index.cjs +7686 -5894
  10. package/dist/index.d.cts +1876 -1360
  11. package/dist/index.d.ts +1876 -1360
  12. package/dist/index.js +7048 -5493
  13. package/dist.bundle/index.cjs +7747 -5957
  14. package/package.json +4 -4
  15. package/src/__tests__/manifest-store.test.ts +16 -15
  16. package/src/__tests__/robustness-medium-batch1.test.ts +10 -14
  17. package/src/__tests__/robustness-medium-batch2.test.ts +7 -2
  18. package/src/execution/__tests__/alive-store.test.ts +117 -1
  19. package/src/execution/__tests__/batch-finalized.test.ts +206 -0
  20. package/src/execution/__tests__/chat-engine-routing.test.ts +117 -1
  21. package/src/execution/__tests__/{cold-resurrect.test.ts → cold-lookup.test.ts} +133 -33
  22. package/src/execution/__tests__/collect-coordinator-service.test.ts +7 -5
  23. package/src/execution/__tests__/conversation-continuation.test.ts +1452 -0
  24. package/src/execution/__tests__/delivery-methods.test.ts +90 -176
  25. package/src/execution/__tests__/dialog-queue.test.ts +70 -3
  26. package/src/execution/__tests__/dispose-manifest-recovery.test.ts +475 -0
  27. package/src/execution/__tests__/execute-and-await-worktree.test.ts +3 -3
  28. package/src/execution/__tests__/execute-nesting.test.ts +8 -5
  29. package/src/execution/__tests__/execution-record.test.ts +8 -148
  30. package/src/execution/__tests__/finalize-record.test.ts +295 -129
  31. package/src/execution/__tests__/get-record-for-action-restart.test.ts +21 -29
  32. package/src/execution/__tests__/helpers/fake-engine-port.ts +13 -68
  33. package/src/execution/__tests__/helpers/legacy-sidecar.ts +33 -0
  34. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +9 -5
  35. package/src/execution/__tests__/idle-gc.test.ts +60 -0
  36. package/src/execution/__tests__/lifecycle-manager.test.ts +3 -254
  37. package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +43 -22
  38. package/src/execution/__tests__/model-config-service.test.ts +111 -0
  39. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +10 -6
  40. package/src/execution/__tests__/notify-ledger.test.ts +42 -1
  41. package/src/execution/__tests__/rebuild-indexes.test.ts +329 -0
  42. package/src/execution/__tests__/record-binding.test.ts +613 -0
  43. package/src/execution/__tests__/record-origin.test.ts +318 -0
  44. package/src/execution/__tests__/record-store-intent-api.test.ts +600 -0
  45. package/src/execution/__tests__/record-store-orphan-revive.test.ts +71 -3
  46. package/src/execution/__tests__/record-store.test.ts +72 -54
  47. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +8 -5
  48. package/src/execution/__tests__/round-supervisor-workflow-origin.test.ts +205 -0
  49. package/src/execution/__tests__/run-orchestration-write-lease.test.ts +293 -0
  50. package/src/execution/__tests__/run-orchestration.test.ts +199 -0
  51. package/src/execution/__tests__/session-file-gc.test.ts +35 -4
  52. package/src/execution/__tests__/session-reconstructor.test.ts +78 -0
  53. package/src/execution/__tests__/state-marker.test.ts +288 -0
  54. package/src/execution/__tests__/stream-sink-retirement.test.ts +8 -5
  55. package/src/execution/__tests__/subagent-actions-core.test.ts +67 -16
  56. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +0 -1
  57. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +27 -11
  58. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +17 -11
  59. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +130 -53
  60. package/src/execution/__tests__/subagent-service.test.ts +17 -215
  61. package/src/execution/__tests__/subprocess-agent-runner-no-progress-killall.test.ts +138 -0
  62. package/src/execution/__tests__/subprocess-agent-runner.test.ts +105 -660
  63. package/src/execution/__tests__/sync-collect-recovery.test.ts +23 -16
  64. package/src/execution/__tests__/terminal-write-latency.test.ts +79 -0
  65. package/src/execution/__tests__/workflow-agent-dispatch.test.ts +782 -0
  66. package/src/execution/alive-store.ts +37 -28
  67. package/src/execution/{cold-resurrect.ts → cold-lookup.ts} +48 -45
  68. package/src/execution/config.ts +1 -1
  69. package/src/execution/conversation-continuation.ts +541 -0
  70. package/src/execution/dialog-queue.ts +60 -12
  71. package/src/execution/engine/__tests__/common/capability-gate.test.ts +1 -1
  72. package/src/execution/engine/__tests__/common/journal-wiring.test.ts +16 -1
  73. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/fake-engine-protocol.mjs +17 -51
  74. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/smoke-run.fixture.json +0 -5
  75. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +1 -1
  76. package/src/execution/engine/__tests__/conformance/protocol-blackbox.test.ts +5 -9
  77. package/src/execution/engine/__tests__/engine-discovery-scan.test.ts +0 -3
  78. package/src/execution/engine/__tests__/engine-discovery.test.ts +0 -3
  79. package/src/execution/engine/__tests__/model-prompt.test.ts +0 -3
  80. package/src/execution/engine/__tests__/registry.test.ts +0 -1
  81. package/src/execution/engine/__tests__/routing.test.ts +0 -1
  82. package/src/execution/engine/__tests__/run-failure-worktree-cleanup.test.ts +0 -3
  83. package/src/execution/engine/client/__tests__/__fixtures__/fake-engine.mjs +0 -3
  84. package/src/execution/engine/client/__tests__/engine-client-reap.test.ts +212 -0
  85. package/src/execution/engine/client/__tests__/mirror.test.ts +73 -1
  86. package/src/execution/engine/client/__tests__/protocol-closure.test.ts +2 -9
  87. package/src/execution/engine/client/__tests__/remote-engine.test.ts +46 -12
  88. package/src/execution/engine/client/engine-client.ts +81 -25
  89. package/src/execution/engine/client/mirror.ts +9 -0
  90. package/src/execution/engine/client/remote-engine.ts +52 -34
  91. package/src/execution/engine/client/reverse-router.ts +6 -27
  92. package/src/execution/engine/common/__tests__/run-signals.test.ts +79 -0
  93. package/src/execution/engine/common/capability-gate.ts +27 -0
  94. package/src/execution/engine/common/data-dir.ts +3 -2
  95. package/src/execution/engine/common/errors.ts +1 -1
  96. package/src/execution/engine/common/journal-wiring.ts +18 -9
  97. package/src/execution/engine/common/run-signals.ts +83 -0
  98. package/src/execution/engine/d8-compat.ts +1 -1
  99. package/src/execution/engine/host/__tests__/host-bridge.test.ts +3 -18
  100. package/src/execution/engine/host/host-bridge.ts +19 -41
  101. package/src/execution/engine/host/pi-host-binding.ts +5 -5
  102. package/src/execution/engine/port.ts +34 -54
  103. package/src/execution/engine/types.ts +11 -14
  104. package/src/execution/execution-record.ts +15 -47
  105. package/src/execution/finalize-record.ts +94 -197
  106. package/src/execution/idle-gc.ts +9 -3
  107. package/src/execution/lifecycle-manager.ts +25 -361
  108. package/src/execution/lifecycle-predicates.ts +22 -19
  109. package/src/execution/manifest-store.ts +40 -42
  110. package/src/execution/model-config-service.ts +1 -1
  111. package/src/execution/notifier.ts +39 -1
  112. package/src/execution/notify-host.ts +13 -4
  113. package/src/execution/record-entry.ts +20 -2
  114. package/src/execution/record-store.ts +925 -160
  115. package/src/execution/relay-env.ts +8 -3
  116. package/src/execution/round-supervisor/domain.ts +17 -5
  117. package/src/execution/round-supervisor/service-binding.test.ts +89 -53
  118. package/src/execution/round-supervisor/service-binding.ts +71 -21
  119. package/src/execution/round-supervisor/supervisor.ts +23 -9
  120. package/src/execution/service/record-access.ts +547 -0
  121. package/src/execution/service/record-lifecycle.ts +542 -0
  122. package/src/execution/service/run-orchestration.ts +1647 -0
  123. package/src/execution/service/service-bootstrap.ts +111 -0
  124. package/src/execution/service/service-constants.ts +27 -0
  125. package/src/execution/service/session-baselines.ts +401 -0
  126. package/src/execution/service/sync-collect-domain.ts +442 -0
  127. package/src/execution/service/workflow-dispatch.ts +567 -0
  128. package/src/execution/session-file-gc.ts +11 -8
  129. package/src/execution/session-reconstructor.ts +44 -1
  130. package/src/execution/sessions-index.ts +20 -3
  131. package/src/execution/settled-watchdog.ts +86 -43
  132. package/src/execution/state-marker.ts +516 -0
  133. package/src/execution/stream-sink.ts +1 -1
  134. package/src/execution/subagent-actions-core.ts +53 -31
  135. package/src/execution/subagent-service.ts +643 -2980
  136. package/src/execution/subprocess-agent-runner.ts +46 -323
  137. package/src/execution/types.ts +60 -43
  138. package/src/execution/ui-request-handler-factory.ts +5 -0
  139. package/src/execution/worktree-manager.ts +1 -1
  140. package/src/execution/worktree-registry.ts +1 -1
  141. package/src/index.ts +6 -5
  142. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +2 -3
  143. package/src/orchestration/__tests__/agent-call-stream.test.ts +24 -49
  144. package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +4 -4
  145. package/src/orchestration/__tests__/file-run-store.test.ts +5 -7
  146. package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +2 -3
  147. package/src/orchestration/__tests__/run-snapshot.test.ts +27 -43
  148. package/src/orchestration/__tests__/worker-message-pump-workflow-dispatch.test.ts +274 -0
  149. package/src/orchestration/execute-agent-call.ts +1 -1
  150. package/src/orchestration/file-run-store.ts +1 -1
  151. package/src/orchestration/models/ports.ts +20 -13
  152. package/src/orchestration/models/types.ts +11 -13
  153. package/src/orchestration/run-snapshot.ts +18 -22
  154. package/src/orchestration/worker-message-pump.ts +33 -57
  155. package/src/shared/atomic-write.ts +10 -8
  156. package/dist/chunk-T2SYBW3I.js +0 -24
  157. package/dist/engine-discovery-scan-BXLA8y-z.d.cts +0 -1624
  158. package/dist/engine-discovery-scan-BXLA8y-z.d.ts +0 -1624
  159. package/src/execution/__tests__/chat-round-first-round-watchdog.test.ts +0 -255
  160. package/src/execution/__tests__/finalized-marker.test.ts +0 -104
  161. package/src/execution/__tests__/lifecycle-manager-lock.test.ts +0 -211
  162. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +0 -488
  163. package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +0 -75
  164. package/src/execution/__tests__/tombstone-store.test.ts +0 -73
  165. package/src/execution/engine/__tests__/conformance/chat-round-protocol.test.ts +0 -230
  166. package/src/execution/finalized-marker.ts +0 -70
  167. package/src/execution/tombstone-store.ts +0 -72
@@ -1,12 +1,13 @@
1
1
  // src/execution/__tests__/get-record-for-action-restart.test.ts
2
2
  //
3
- // [M10] getRecordForAction 跨重启磁盘重建分支测试(subagent-service.ts L933-951)。
3
+ // [M10] getRecordForAction 跨重启磁盘重建分支测试(cold-lookup.coldLookupForAction)。
4
4
  //
5
5
  // 背景:内存 miss → collectRecords(1000,"all",undefined).find(status==="running") →
6
- // createRecord({chatMode: true}) → register → 回填 sessionFile/round。代码注释
7
- // (L946-949)自设强制约束「改动此处必须带 S3 回归场景(跨重启 message 续聊验证)」
8
- // ——该 S3 场景此前不存在,分支零测试。该分支含多个仅此处独有的决策:
9
- // - 无条件 chatMode: true(v4 A-3 跨重启恢复入口,V3 方案 A 方向)
6
+ // createRecord({chatMode: 持久化值}) → register → 回填 sessionFile/round
7
+ // [H1 U6] 原「无条件 chatMode: true」(v4 A-3)已改水合保留持久化 chatMode
8
+ //(D4/D5:升级置位迁 Continuation revive 格 + messageHandler gate 双写点)。
9
+ // 该分支含多个仅此处独有的决策:
10
+ // - chatMode 水合保留(磁盘无持久化 → false;升级在 message 链 gate 化)
10
11
  // - rootSessionFilter 传 undefined 后置校验(异树仍 throw not owned)
11
12
  // - sessionFile/round 从磁盘重建结果回填(round 无磁盘持久化 → undefined)
12
13
  //
@@ -25,11 +26,10 @@ const { loggerMock } = vi.hoisted(() => ({
25
26
  }));
26
27
  vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
27
28
 
28
- // [W3 改写] vi.mock(inproc pi 引擎目录/session-runner) 随删件消亡——deliverChatMessage
29
- // 协议 seam(registerFakePiEngine 替身;跨重启无活进程 = 引擎冷路径拒绝,续聊落
30
- // resumeColdRound 守卫链)。
29
+ // [W3 改写 H1 U6] deliverChatMessage 走协议 seam(registerFakePiEngine 替身)——
30
+ // 每轮 = run + resume 锚点,续聊守卫链归 Continuation(dispatchRoundGuarded)。
31
31
 
32
- import { writeFinalized } from "../finalized-marker.ts";
32
+ import { writeFinalizedState } from "../state-marker.ts";
33
33
  import { registerFakePiEngine } from "./helpers/fake-engine-port.ts";
34
34
  import { clearEngines } from "../engine/registry.ts";
35
35
  import { ModelConfigService } from "../model-config-service.ts";
@@ -158,16 +158,17 @@ describe("[M10] getRecordForAction 跨重启磁盘重建(S3 回归场景)",
158
158
  for (const k of IDENTITY_ENV_KEYS) delete process.env[k];
159
159
  });
160
160
 
161
- it("内存 miss + 磁盘 running(无 .alive)→ 重建 record:chatMode 无条件 true、sessionFile 回填、register 进内存", () => {
162
- // identity entry 故意不带 chatMode 字段——证明重建分支的无条件 chatMode:true
163
- //(不依赖磁盘是否记录过对话模式标志)
161
+ it("内存 miss + 磁盘 running(无 .alive)→ 重建 record:chatMode 水合保留(无持久化为 false,[H1 U6])、sessionFile 回填、register 进内存", () => {
162
+ // identity entry 故意不带 chatMode 字段——证明重建分支只水合持久化 chatMode
163
+ //([H1 U6] 无条件置位已迁 Continuation revive 格 + gate;one-shot 跨重启重建
164
+ // false 后经 message 链 gate 升级置位)
164
165
  const file = writeSessionJsonl(sessionsDir, { id: "sa-restart-1", rootSessionId: "root-session" });
165
166
  expect(store.getMutable("sa-restart-1")).toBeUndefined(); // 前置:内存确无
166
167
 
167
168
  const record = service.chatActions.getRecordForAction("sa-restart-1");
168
169
 
169
- // [v4 A-3] 无条件 chatMode=true(跨重启恢复入口)
170
- expect(record.chatMode).toBe(true);
170
+ // [v4 A-3 → H1 U6] 水合保留:磁盘无 chatMode → false(升级在 message 链 gate 化)
171
+ expect(record.chatMode).toBe(false);
171
172
  // sessionFile 从磁盘重建结果回填
172
173
  expect(record.sessionFile).toBe(file);
173
174
  // round 从 found 回填:light 磁盘重建无 round(内存态字段,跨重启不恢复)→ undefined
@@ -204,9 +205,9 @@ describe("[M10] getRecordForAction 跨重启磁盘重建(S3 回归场景)",
204
205
  expect(() => service.chatActions.getRecordForAction("sa-grand")).toThrow(/direct parent/);
205
206
  });
206
207
 
207
- it(".finalized sidecar(closed 终态)不重建 → throw not found or not owned", () => {
208
+ it(".state sidecar(closed 终态)不重建 → throw not found or not owned", () => {
208
209
  const file = writeSessionJsonl(sessionsDir, { id: "sa-fin", rootSessionId: "root-session" });
209
- writeFinalized(file); // sidecar 矩阵分支 2 → status=closed → find(status==="running") miss
210
+ writeFinalizedState(file); // sidecar 矩阵分支 2 → status=closed → find(status==="running") miss
210
211
 
211
212
  expect(() => service.chatActions.getRecordForAction("sa-fin")).toThrow(/not found or not owned/);
212
213
  expect(store.getMutable("sa-fin")).toBeUndefined(); // 未重建注册
@@ -221,12 +222,8 @@ describe("[M10] getRecordForAction 跨重启磁盘重建(S3 回归场景)",
221
222
  it("[review round2] worktree record 跨重启重建 → hadWorktree 标记 + 续聊被拒(行动语言)", async () => {
222
223
  clearEngines();
223
224
  const fake = registerFakePiEngine();
224
- // 跨重启无活进程 引擎冷路径拒绝 续聊落 resumeColdRound 守卫链
225
- fake.interactMessageResult = {
226
- ok: false,
227
- code: "engine_session_not_resumable",
228
- message: "no live process (cold path)",
229
- };
225
+ // [H1 U6] 每轮 = run + resume 锚点(旧 interact 冷路径替身注入随 interact 面退役),
226
+ // 续聊守卫链归 Continuation(dispatchRoundGuarded)。
230
227
  writeSessionJsonl(sessionsDir, {
231
228
  id: "sa-wt",
232
229
  rootSessionId: "root-session",
@@ -240,7 +237,7 @@ describe("[M10] getRecordForAction 跨重启磁盘重建(S3 回归场景)",
240
237
  expect(record.worktreeHandle).toBeUndefined();
241
238
 
242
239
  // 冷路径续聊(无活进程)→ 续轮守卫拒绝,不 spawn 回落主 repo
243
- await expect(service.chatActions.deliverChatMessage(record, "resume after restart", false)).rejects.toThrow(
240
+ await expect(service.chatActions.deliverChatMessage(record, "resume after restart")).rejects.toThrow(
244
241
  /worktree isolation.*lost when the parent process restarted/,
245
242
  );
246
243
  });
@@ -248,11 +245,6 @@ describe("[M10] getRecordForAction 跨重启磁盘重建(S3 回归场景)",
248
245
  it("[review round2] 非 worktree record 跨重启重建 → 续聊不受 worktree 守卫拦截(向后兼容)", async () => {
249
246
  clearEngines();
250
247
  const fake = registerFakePiEngine();
251
- fake.interactMessageResult = {
252
- ok: false,
253
- code: "engine_session_not_resumable",
254
- message: "no live process (cold path)",
255
- };
256
248
  // identity entry 无 worktree 字段(旧文件)→ found.worktree undefined → hadWorktree false
257
249
  writeSessionJsonl(sessionsDir, { id: "sa-nowt", rootSessionId: "root-session" });
258
250
 
@@ -261,6 +253,6 @@ describe("[M10] getRecordForAction 跨重启磁盘重建(S3 回归场景)",
261
253
 
262
254
  // 冷路径续聊不被 worktree 守卫拦截(resume 正常发起;后续 spawn 编排
263
255
  // 超出本用例关注点——runSpawn 在本文件是 no-op mock)
264
- await expect(service.chatActions.deliverChatMessage(record, "resume normal", false)).resolves.toBeUndefined();
256
+ await expect(service.chatActions.deliverChatMessage(record, "resume normal")).resolves.toBeUndefined();
265
257
  });
266
258
  });
@@ -1,33 +1,27 @@
1
1
  // src/execution/__tests__/helpers/fake-engine-port.ts
2
2
  //
3
- // [W3 chat 域收口] SubagentService 编排层测试共享的 pi EnginePort 替身。
3
+ // [W3 chat 域收口 → H1 U6 定形] SubagentService 编排层测试共享的 pi EnginePort 替身。
4
4
  //
5
- // 背景:chat 域执行链路自 W3 起经协议客户端(registry 'pi' 的 cli 形态 port——
6
- // RemoteEngine)发往 pi-subagent-cli 引擎进程;原 inproc 链路的测试替身
7
- //(vi.mock session-runner / FakeChild stdout pump)随 inproc pi 引擎目录 删除消亡。
8
- // 本 helper 是协议 seam 的替身单源:测试经 registerEngine("pi", fake) 注入,
9
- // 拿到 fake 实例后驱动 run/interact/roundLifecycle,断言协议交互。
5
+ // 背景:会话形态轮经协议客户端(registry 'pi' 的 cli 形态 port——RemoteEngine)发往
6
+ // pi-subagent-cli 引擎进程;原 inproc 链路的测试替身随删件消亡。本 helper 是协议 seam
7
+ // 的替身单源:测试经 registerEngine("pi", fake) 注入,拿到 fake 实例后驱动 run,
8
+ // 断言协议交互。
10
9
  //
11
10
  // 消费方式(与 spawn-mock.ts 同款先例):
12
11
  // const fake = registerFakePiEngine();
13
12
  // await service.execute({ task: "...", conversation: true });
14
- // fake.runs[0].settle({ content: "round text" }); // 模拟首轮 agent_settled 应答
15
- // fake.interacts[0] // interact 受理断言
13
+ // fake.runs[0].settle({ content: "round text" }); // 模拟 agent_settled 应答
16
14
  //
17
- // 形态对照(协议语义,见 pi-subagent-cli chat-session.ts):
18
- // - run 应答时点 = 首轮 agent_settled(outcome = 本轮内容非会话终态);
19
- // - interact message 受理 = { ok: true, delivered: true };冷路径 = engine_session_not_resumable;
20
- // - interact cancel = { ok: true, delivered: true }(引擎侧 SIGTERM → settle → 杀链);
21
- // - roundLifecycle 相位由测试显式驱动(idle 帧先于 run 应答帧)。
15
+ // 形态对照([H1] chat-run 统一终态):每轮 = 新 run + resume 锚点(ctx.resume),
16
+ // run 应答时点 = agent_settled(outcome = 本轮内容非会话终态);[H1 U6] interact 面
17
+ // recordId 键路由模拟面已随 chat 域退役删除。
22
18
 
23
- import type { EnginePort, EngineRunResult, RunContext, ChatRoundRoute } from "../../../execution/engine/port.ts";
19
+ import type { EnginePort, EngineRunResult, RunContext } from "../../../execution/engine/port.ts";
24
20
  import type { AgentCallOpts } from "../../../orchestration/models/types.ts";
25
21
  import type {
26
22
  AgentOutcome,
27
23
  EngineCapabilities,
28
24
  EngineHandle,
29
- InteractAction,
30
- InteractResult,
31
25
  ProbeReport,
32
26
  SessionView,
33
27
  } from "../../../execution/engine/types.ts";
@@ -50,7 +44,7 @@ export class FakeRun {
50
44
  });
51
45
  }
52
46
 
53
- /** 模拟 run 正常应答(首轮 agent_settled 口径;outcome 缺省 = 成功空轮)。 */
47
+ /** 模拟 run 正常应答(agent_settled 口径;outcome 缺省 = 成功空轮)。 */
54
48
  settle(outcome: Partial<AgentOutcome> = {}): void {
55
49
  this.resolve({
56
50
  handle: {
@@ -80,44 +74,22 @@ export class FakeRun {
80
74
  this.ctx.onEvent?.(event as never);
81
75
  }
82
76
 
83
- /** 模拟 host/streamDelta(runId 键——首轮)。 */
77
+ /** 模拟 host/streamDelta(runId 键——run 作用域路由)。 */
84
78
  emitDelta(delta: string): void {
85
79
  this.ctx.stream?.onDelta(delta);
86
80
  }
87
81
 
88
- /** 模拟 host/roundLifecycle(runId 键——首轮)。 */
89
- emitLifecycle(phase: Record<string, unknown>): void {
90
- this.ctx.onRoundLifecycle?.({ runId: this.ctx.taskId, ...phase } as never);
91
- }
92
-
93
82
  /** 模拟 host/handleReady(运行中句柄回填)。 */
94
83
  emitHandleReady(sessionRef: Record<string, string>, poolKey = "shared"): void {
95
84
  this.ctx.onHandleReady?.({ sessionRef, poolKey });
96
85
  }
97
86
  }
98
87
 
99
- /** 一次 interact 调用的捕获(action 原样记录,供断言)。 */
100
- export interface FakeInteractCall {
101
- handle: EngineHandle;
102
- action: InteractAction;
103
- result: InteractResult | Promise<InteractResult>;
104
- }
105
-
106
88
  export class FakePiEnginePort implements EnginePort {
107
89
  readonly id = "pi";
108
90
 
109
91
  /** run 调用捕获(按序)。 */
110
92
  readonly runs: FakeRun[] = [];
111
- /** interact 调用捕获(按序;result = 受理时返回值)。 */
112
- readonly interacts: FakeInteractCall[] = [];
113
- /** recordId 键路由注册捕获(recordId → 注销函数)。 */
114
- readonly chatRoutes = new Map<string, ChatRoundRoute>();
115
- readonly chatRouteUnregisters: string[] = [];
116
-
117
- /** interact message 的应答形态(默认受理;测试改写为冷路径拒绝等)。 */
118
- interactMessageResult: InteractResult = { ok: true, delivered: true };
119
- /** interact cancel / close 的应答形态。 */
120
- interactControlResult: InteractResult = { ok: true, delivered: true };
121
93
 
122
94
  capabilities(): EngineCapabilities {
123
95
  // 与 pi-subagent-cli manifest 逐位一致(gate 同步面放行 pi 全参数——V4⑤ 反向守护)。
@@ -146,37 +118,10 @@ export class FakePiEnginePort implements EnginePort {
146
118
  return captured.promise;
147
119
  }
148
120
 
149
- async interact(handle: EngineHandle, action: InteractAction): Promise<InteractResult> {
150
- const result = action.kind === "message" ? this.interactMessageResult : this.interactControlResult;
151
- this.interacts.push({ handle, action, result });
152
- return result;
153
- }
154
-
155
121
  async read(handle: EngineHandle): Promise<SessionView> {
122
+ void handle;
156
123
  return { engineId: "pi", turns: [], source: "outcome-only" };
157
124
  }
158
-
159
- registerChatRoundRoute(recordId: string, route: ChatRoundRoute): () => void {
160
- this.chatRoutes.set(recordId, route);
161
- return () => {
162
- if (this.chatRoutes.get(recordId) === route) {
163
- this.chatRoutes.delete(recordId);
164
- this.chatRouteUnregisters.push(recordId);
165
- }
166
- };
167
- }
168
-
169
- // ── 测试驱动面(recordId 键路由——interact 续聊轮的协议回流)──
170
-
171
- /** 向指定 record 的 chat 路由发 recordId 键 delta。 */
172
- emitRecordDelta(recordId: string, delta: string): void {
173
- void this.chatRoutes.get(recordId)?.onStreamDelta?.(delta);
174
- }
175
-
176
- /** 向指定 record 的 chat 路由发 recordId 键 roundLifecycle 相位。 */
177
- emitRecordLifecycle(recordId: string, phase: Record<string, unknown>): void {
178
- void this.chatRoutes.get(recordId)?.onRoundLifecycle?.({ recordId, ...phase } as never);
179
- }
180
125
  }
181
126
 
182
127
  /** 注册替身 pi 引擎进 registry(覆盖同 id 既有注册——测试隔离自负责清理)。 */
@@ -0,0 +1,33 @@
1
+ // src/execution/__tests__/helpers/legacy-sidecar.ts
2
+ //
3
+ // 兼容期旧格式终态 sidecar fixture(`.finalized` / `.cancelled`)——L4 合并后生产
4
+ // 只写 `.state`,但读侧仍兼容旧名(存量文件不迁移)。测试需要造旧格式文件验证兼容
5
+ // 读路径,此处是唯一 fixture 入口(格式细节单源,避免各测试散落 fs.writeFileSync)。
6
+ //
7
+ // 形态 = 合并前两模块的写侧逐字等价:
8
+ // .finalized 内容 = reason 字符串(undefined → 空文件,v8.5 前格式)
9
+ // .cancelled 内容 = 单行 JSON tombstone {id, status, agent, startedAt, endedAt}
10
+
11
+ import * as fs from "node:fs";
12
+
13
+ /** 旧 .cancelled tombstone 形态(合并前 CancelledTombstone 逐字等价)。 */
14
+ export interface LegacyCancelledTombstone {
15
+ id: string;
16
+ status: "cancelled";
17
+ agent: string;
18
+ startedAt: number;
19
+ endedAt: number;
20
+ }
21
+
22
+ /** 写旧格式 `.finalized`(测试 fixture;reason 缺省 = 空文件旧格式)。 */
23
+ export function writeLegacyFinalizedSidecar(sessionFile: string, reason?: string): void {
24
+ fs.writeFileSync(`${sessionFile}.finalized`, reason ?? "", "utf-8");
25
+ }
26
+
27
+ /** 写旧格式 `.cancelled`(测试 fixture;完整 tombstone 形态)。 */
28
+ export function writeLegacyCancelledSidecar(
29
+ sessionFile: string,
30
+ meta: LegacyCancelledTombstone,
31
+ ): void {
32
+ fs.writeFileSync(`${sessionFile}.cancelled`, `${JSON.stringify(meta)}\n`, "utf-8");
33
+ }
@@ -88,11 +88,15 @@ export function aliveStoreModule(actual: typeof import("../../alive-store.ts"))
88
88
  };
89
89
  }
90
90
 
91
- /** ../finalized-marker.ts mock 模块(避免真实 fs 写 sidecar)。 */
92
- export function finalizedMarkerModule() {
91
+ /** ../state-marker.ts mock 模块(避免真实 fs 写 sidecar;读侧恒「无终态标记」,
92
+ * 对齐原 finalized-marker mock 的 no-finalized 语义)。 */
93
+ export function stateMarkerModule() {
93
94
  return {
94
- writeFinalized: vi.fn(),
95
- readFinalized: vi.fn(() => false),
95
+ writeFinalizedState: vi.fn(),
96
+ writeCancelledState: vi.fn(),
97
+ readStateMarker: vi.fn(() => undefined),
98
+ statStateStamp: vi.fn(() => null),
99
+ STATE_SIDECAR_EXT: ".state",
96
100
  };
97
101
  }
98
102
 
@@ -107,7 +111,7 @@ export function manifestStoreModule() {
107
111
  writeManifest = vi.fn(async () => {});
108
112
  readManifest = vi.fn(async () => null);
109
113
  listAllSync = vi.fn(() => []);
110
- recoverTmpFiles = vi.fn(async () => []);
114
+ sweepTmpFiles = vi.fn(async () => 0);
111
115
  }
112
116
  return { ManifestStore: vi.fn(function (_recordsDir: string) { return new FakeManifestStore(); }) };
113
117
  }
@@ -4,6 +4,10 @@
4
4
  // 只归档不补注销(archive 不发 pending:unregister——注销统一交对账 sweep)、
5
5
  // WorkflowRun store 纳入(startedAt 锚终态化 + save)。fake timers 推进 GC
6
6
  // interval;RecordStore 用 mkdtemp 自建目录 + pi=null(archive 纯内存,零磁盘写)。
7
+ //
8
+ // [U2b / C1] markIdleArchived 归口:归档时 `.alive` 写权声明同步 release(D3a
9
+ // release 出口②)——S6 验收锚点链的单元级断言(fake clock 推进 30 天 → 归档 →
10
+ // marker 已删 → fork-from 探针放行 → 接管 acquire 重声明)。
7
11
 
8
12
  import * as fs from "node:fs";
9
13
  import * as os from "node:os";
@@ -11,6 +15,7 @@ import * as path from "node:path";
11
15
 
12
16
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
13
17
 
18
+ import { findForeignLiveInstance } from "../alive-store.ts";
14
19
  import { createRecord } from "../execution-record.ts";
15
20
  import { startIdleGc } from "../idle-gc.ts";
16
21
  import { RecordStore } from "../record-store.ts";
@@ -53,6 +58,61 @@ function makeRecord(id: string, overrides: Partial<ExecutionRecord> = {}): Execu
53
58
  return record;
54
59
  }
55
60
 
61
+ describe("idle-GC 归口 markIdleArchived(U2b/C1——D3a release 出口②闭环链,S6 锚点)", () => {
62
+ it("fake clock 推进 30 天 → 归档 + `.alive` 已删 + fork-from 探针放行 + 接管 acquire 重声明(闭环链)", async () => {
63
+ const store = makeStore();
64
+ const sessionFile = path.join(tmpDir, "lease-session.jsonl");
65
+ fs.writeFileSync(sessionFile, "{}\n", "utf-8");
66
+ const rec = makeRecord("bg-lease", {
67
+ startedAt: Date.now() - 31 * DAY_MS,
68
+ idleSince: Date.now() - 31 * DAY_MS,
69
+ sessionFile,
70
+ });
71
+ store.register(rec);
72
+ // 持有期声明在位(模拟 spawn 侧 acquireWriteLease 已声明写权的 resumable record)。
73
+ store.acquireWriteLease(sessionFile, rec.id);
74
+ expect(fs.existsSync(`${sessionFile}.alive`)).toBe(true);
75
+
76
+ stop = startIdleGc(store);
77
+ await vi.advanceTimersByTimeAsync(GC_INTERVAL_MS + 1);
78
+
79
+ // ① 归档生效(内存移除——record 磁盘仍 running 可接管,非终态化)。
80
+ expect(store.getMutable("bg-lease")).toBeUndefined();
81
+ // ② marker 已删(release 生效——归档 = 放弃持有 = 放弃写权声明)。
82
+ expect(fs.existsSync(`${sessionFile}.alive`)).toBe(false);
83
+ // ③ fork-from 放行:探针无 marker 即无声明,不再被残留声明拦。
84
+ expect(findForeignLiveInstance(sessionFile)).toBeUndefined();
85
+ // ④ message 接管 acquire 重声明:接管时统一 acquireWriteLease(接管链经
86
+ // markResurrected 的 acquire-first,本处以 store 内部 acquire 动作直驱同语义)。
87
+ store.acquireWriteLease(sessionFile, "bg-lease");
88
+ const marker = JSON.parse(fs.readFileSync(`${sessionFile}.alive`, "utf-8")) as {
89
+ pid: number;
90
+ id: string;
91
+ };
92
+ expect(marker).toMatchObject({ pid: process.pid, id: "bg-lease" });
93
+ // 重声明后探针对本进程仍放行(self-pid 排除——自有声明不构成 foreign)。
94
+ expect(findForeignLiveInstance(sessionFile)).toBeUndefined();
95
+ });
96
+
97
+ it("锚窗内的 record 不归档且 `.alive` 不 release(无早释)", async () => {
98
+ const store = makeStore();
99
+ const sessionFile = path.join(tmpDir, "lease-fresh.jsonl");
100
+ fs.writeFileSync(sessionFile, "{}\n", "utf-8");
101
+ const rec = makeRecord("bg-hold", {
102
+ startedAt: Date.now() - 1 * DAY_MS,
103
+ sessionFile,
104
+ });
105
+ store.register(rec);
106
+ store.acquireWriteLease(sessionFile, rec.id);
107
+
108
+ stop = startIdleGc(store);
109
+ await vi.advanceTimersByTimeAsync(GC_INTERVAL_MS + 1);
110
+
111
+ expect(store.getMutable("bg-hold")).toBeDefined();
112
+ expect(fs.existsSync(`${sessionFile}.alive`)).toBe(true);
113
+ });
114
+ });
115
+
56
116
  describe("idle-gc record 锚扩展(W4)", () => {
57
117
  it("无 idleSince 的 resumable record 以 startedAt 为锚:超 30 天归档", async () => {
58
118
  const store = makeStore();
@@ -1,10 +1,8 @@
1
- // lifecycle-manager 单测 —— V2 §5.2 模块 1(进程生命周期管理)五项职责。
1
+ // lifecycle-manager 单测 —— V2 §5.2 模块 1 现存唯一职责(idle timer)。原职责 2/3/4
2
+ // 骨架未接线、职责 5 activate 互斥无生产调用方,已随简化清扫删除。
2
3
  //
3
4
  // 测试策略:
4
- // - idle timer / ceiling LRU 用 vi.useFakeTimers() + advanceTimersByTime(同时 mock
5
- // Date.now(),让 lastTouched 时间戳可控)。
6
- // - activate 互斥锁是纯 Promise 链(无 timer),用 advanceTimersByTimeAsync(0)
7
- // flush microtask 验证串行化。
5
+ // - idle timer 用 vi.useFakeTimers() + advanceTimersByTime
8
6
  // - 每个用例 beforeEach 调 _resetLifecycleState() 隔离模块级单例状态。
9
7
 
10
8
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
@@ -20,19 +18,10 @@ vi.mock("../../core/logger.ts", () => ({
20
18
 
21
19
  import {
22
20
  DEFAULT_IDLE_TIMEOUT_MS,
23
- DEFAULT_MAX_ALIVE_PROCESSES,
24
21
  _resetLifecycleState,
25
- acquireActivateLock,
26
22
  armIdleTimer,
27
23
  disarmIdleTimer,
28
- evictIfOverCeiling,
29
- getActiveProcessCount,
30
24
  hasIdleTimer,
31
- reapAllAliveProcesses,
32
- registerActiveProcess,
33
- scanOrphanProcesses,
34
- touchActiveProcess,
35
- unregisterActiveProcess,
36
25
  } from "../lifecycle-manager.ts";
37
26
 
38
27
  describe("lifecycle-manager — V2 §5.2 模块 1", () => {
@@ -179,244 +168,4 @@ describe("lifecycle-manager — V2 §5.2 模块 1", () => {
179
168
  });
180
169
  });
181
170
 
182
- // ============================================================
183
- // 职责 2:全局 ceiling(LRU 挤出)
184
- // ============================================================
185
- describe("职责2 全局 ceiling", () => {
186
- it("register 超过上限时 evictIfOverCeiling 挤出最久空闲", () => {
187
- registerActiveProcess("sa-1"); // 最早
188
- vi.advanceTimersByTime(10);
189
- registerActiveProcess("sa-2");
190
- vi.advanceTimersByTime(10);
191
- registerActiveProcess("sa-3"); // 超限(size=3 > ceiling=2)
192
- expect(getActiveProcessCount()).toBe(3);
193
-
194
- const evicted: string[] = [];
195
- evictIfOverCeiling((id) => evicted.push(id), 2);
196
-
197
- expect(evicted).toEqual(["sa-1"]); // lastTouched 最老的被挤出
198
- expect(getActiveProcessCount()).toBe(2);
199
- });
200
-
201
- it("不超过上限时 evictIfOverCeiling 不挤出", () => {
202
- registerActiveProcess("sa-1");
203
- registerActiveProcess("sa-2");
204
-
205
- const evicted: string[] = [];
206
- evictIfOverCeiling((id) => evicted.push(id), 5);
207
-
208
- expect(evicted).toEqual([]);
209
- expect(getActiveProcessCount()).toBe(2);
210
- });
211
-
212
- it("touch 更新 LRU:被 touch 的不再是挤出候选", () => {
213
- registerActiveProcess("sa-1");
214
- vi.advanceTimersByTime(10);
215
- registerActiveProcess("sa-2");
216
- vi.advanceTimersByTime(10);
217
- touchActiveProcess("sa-1"); // sa-1 变最新 → sa-2 变最老
218
-
219
- const evicted: string[] = [];
220
- evictIfOverCeiling((id) => evicted.push(id), 1);
221
-
222
- expect(evicted).toEqual(["sa-2"]); // sa-2 最久未 touch,被挤出
223
- });
224
-
225
- it("touch 不存在的 record 为 no-op(不隐式创建)", () => {
226
- expect(() => touchActiveProcess("never-registered")).not.toThrow();
227
- expect(getActiveProcessCount()).toBe(0);
228
- });
229
-
230
- it("unregister 清理活进程集合", () => {
231
- registerActiveProcess("sa-1");
232
- registerActiveProcess("sa-2");
233
- unregisterActiveProcess("sa-1");
234
- expect(getActiveProcessCount()).toBe(1);
235
- });
236
-
237
- it("挤出时连带 disarm 被挤 record 的 idle timer", () => {
238
- registerActiveProcess("sa-1");
239
- armIdleTimer("sa-1", vi.fn(), 1000);
240
- registerActiveProcess("sa-2");
241
- vi.advanceTimersByTime(10);
242
- registerActiveProcess("sa-3"); // sa-1 最老,将被挤出
243
-
244
- evictIfOverCeiling(() => {
245
- /* kill */
246
- }, 2);
247
-
248
- expect(hasIdleTimer("sa-1")).toBe(false); // 挤出 cascade disarm
249
- });
250
-
251
- it("unregister 连带 disarm idle timer(进程终态 timer 不残留)", () => {
252
- registerActiveProcess("sa-1");
253
- armIdleTimer("sa-1", vi.fn(), 1000);
254
- unregisterActiveProcess("sa-1");
255
- expect(hasIdleTimer("sa-1")).toBe(false);
256
- });
257
-
258
- it("默认上限 = DEFAULT_MAX_ALIVE_PROCESSES(不传 maxAlive)", () => {
259
- for (let i = 0; i < DEFAULT_MAX_ALIVE_PROCESSES; i++) {
260
- registerActiveProcess(`sa-${i}`);
261
- vi.advanceTimersByTime(1); // 保证 lastTouched 严格递增
262
- }
263
- registerActiveProcess("sa-over"); // 超限
264
- const evicted: string[] = [];
265
- evictIfOverCeiling((id) => evicted.push(id)); // 用默认上限
266
-
267
- expect(evicted).toEqual(["sa-0"]); // 默认上限下挤出最早
268
- expect(getActiveProcessCount()).toBe(DEFAULT_MAX_ALIVE_PROCESSES);
269
- });
270
-
271
- it("大幅超限时 while 循环挤出多个直到 ≤ ceiling", () => {
272
- for (let i = 0; i < 5; i++) {
273
- registerActiveProcess(`sa-${i}`);
274
- vi.advanceTimersByTime(1);
275
- }
276
- const evicted: string[] = [];
277
- evictIfOverCeiling((id) => evicted.push(id), 2);
278
-
279
- expect(evicted).toEqual(["sa-0", "sa-1", "sa-2"]); // 挤出 3 个(5-2),按 LRU
280
- });
281
- });
282
-
283
- // ============================================================
284
- // 职责 3:shutdown 收割
285
- // ============================================================
286
- describe("职责3 shutdown 收割", () => {
287
- it("reapAllAliveProcesses 遍历活进程调 killFn 并返回列表", () => {
288
- registerActiveProcess("sa-1");
289
- registerActiveProcess("sa-2");
290
-
291
- const killed: string[] = [];
292
- const reaped = reapAllAliveProcesses((id) => killed.push(id));
293
-
294
- expect(killed).toHaveLength(2);
295
- expect(killed).toEqual(expect.arrayContaining(["sa-1", "sa-2"]));
296
- expect(reaped).toEqual(expect.arrayContaining(["sa-1", "sa-2"]));
297
- expect(getActiveProcessCount()).toBe(0);
298
- });
299
-
300
- it("reap 后 idle timer 已 disarm(advance 不再触发)", () => {
301
- registerActiveProcess("sa-1");
302
- const onTimeout = vi.fn();
303
- armIdleTimer("sa-1", onTimeout, 1000);
304
-
305
- reapAllAliveProcesses(() => {
306
- /* kill */
307
- });
308
- vi.advanceTimersByTime(10000);
309
-
310
- expect(onTimeout).not.toHaveBeenCalled(); // timer 已被 reap 清理
311
- });
312
-
313
- it("无活进程时 reapAll 返回空列表", () => {
314
- const reaped = reapAllAliveProcesses(() => {
315
- /* kill */
316
- });
317
- expect(reaped).toEqual([]);
318
- });
319
- });
320
-
321
- // ============================================================
322
- // 职责 4:孤儿扫描
323
- // ============================================================
324
- describe("职责4 孤儿扫描", () => {
325
- it("pid 存活 + 不在活进程集合 = 孤儿", () => {
326
- registerActiveProcess("sa-active"); // 在集合内
327
- const records = [
328
- { id: "sa-active", pid: 100 },
329
- { id: "sa-orphan", pid: 200 }, // pid 活但不在集合 → 孤儿
330
- { id: "sa-dead", pid: 300 }, // pid 不活 → 非孤儿
331
- { id: "sa-nopid" }, // 无 pid → 非孤儿
332
- ];
333
-
334
- const orphans = scanOrphanProcesses(records, (pid) => pid === 100 || pid === 200);
335
-
336
- expect(orphans).toEqual(["sa-orphan"]);
337
- });
338
-
339
- it("pid 不存活 = 非孤儿", () => {
340
- const records = [{ id: "sa-1", pid: 999 }];
341
- const orphans = scanOrphanProcesses(records, () => false); // 全不活
342
- expect(orphans).toEqual([]);
343
- });
344
-
345
- it("在活进程集合内 = 非孤儿(即使 pid 活)", () => {
346
- registerActiveProcess("sa-1");
347
- const records = [{ id: "sa-1", pid: 100 }];
348
- const orphans = scanOrphanProcesses(records, () => true);
349
- expect(orphans).toEqual([]);
350
- });
351
-
352
- it("无 pid 的 record 不视为孤儿", () => {
353
- const records = [
354
- { id: "sa-1" },
355
- { id: "sa-2", sessionFile: "/x.jsonl" },
356
- ];
357
- const orphans = scanOrphanProcesses(records, () => true);
358
- expect(orphans).toEqual([]);
359
- });
360
-
361
- it("扫描是只读(不改活进程集合)", () => {
362
- registerActiveProcess("sa-1");
363
- scanOrphanProcesses([{ id: "sa-2", pid: 200 }], () => true);
364
- expect(getActiveProcessCount()).toBe(1); // 集合不变
365
- });
366
- });
367
-
368
- // ============================================================
369
- // 职责 5:activate 互斥锁
370
- // ============================================================
371
- describe("职责5 activate 互斥锁", () => {
372
- it("首次 acquire 立即 resolve 返回 release 函数", async () => {
373
- const release = await acquireActivateLock("sa-1");
374
- expect(typeof release).toBe("function");
375
- release();
376
- });
377
-
378
- it("并发 acquire 同一 recordId 串行化:第二次等首次 release", async () => {
379
- const release1 = await acquireActivateLock("sa-1");
380
-
381
- let secondResolved = false;
382
- const secondPromise = acquireActivateLock("sa-1").then((r) => {
383
- secondResolved = true;
384
- return r;
385
- });
386
-
387
- await vi.advanceTimersByTimeAsync(0); // flush microtask
388
- expect(secondResolved).toBe(false); // 还在等首次 release
389
-
390
- release1();
391
- await vi.advanceTimersByTimeAsync(0); // flush microtask
392
- expect(secondResolved).toBe(true);
393
-
394
- const release2 = await secondPromise;
395
- release2();
396
- });
397
-
398
- it("不同 recordId 不互斥(各自独立链)", async () => {
399
- const release1 = await acquireActivateLock("sa-1");
400
-
401
- let secondResolved = false;
402
- acquireActivateLock("sa-2").then((r) => {
403
- secondResolved = true;
404
- r();
405
- });
406
-
407
- await vi.advanceTimersByTimeAsync(0); // flush microtask
408
- expect(secondResolved).toBe(true); // sa-2 不被 sa-1 阻塞
409
-
410
- release1();
411
- });
412
-
413
- it("release 后同 recordId 可再次 acquire", async () => {
414
- const r1 = await acquireActivateLock("sa-1");
415
- r1();
416
-
417
- const r2 = await acquireActivateLock("sa-1");
418
- expect(typeof r2).toBe("function");
419
- r2();
420
- });
421
- });
422
171
  });