@zhushanwen/subagent-core 0.6.0 → 0.7.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 (278) hide show
  1. package/README.md +1 -1
  2. package/dist/chunk-KXVBIVOU.js +16 -0
  3. package/dist/chunk-T2SYBW3I.js +24 -0
  4. package/dist/chunk-VURUAGMM.js +2305 -0
  5. package/dist/{types-C3fE3R9x.d.cts → engine-discovery-scan-ocpM8FmI.d.cts} +770 -668
  6. package/dist/{types-C3fE3R9x.d.ts → engine-discovery-scan-ocpM8FmI.d.ts} +770 -668
  7. package/dist/execution/engine/engine-discovery-scan.cjs +2210 -0
  8. package/dist/execution/engine/engine-discovery-scan.d.cts +4 -0
  9. package/dist/execution/engine/engine-discovery-scan.d.ts +4 -0
  10. package/dist/execution/engine/engine-discovery-scan.js +20 -0
  11. package/dist/execution/engine/paths.cjs +6 -23
  12. package/dist/execution/engine/paths.d.cts +1 -8
  13. package/dist/execution/engine/paths.d.ts +1 -8
  14. package/dist/execution/engine/paths.js +1 -1
  15. package/dist/execution/relay-env.cjs +12 -28
  16. package/dist/execution/relay-env.d.cts +1 -33
  17. package/dist/execution/relay-env.d.ts +1 -33
  18. package/dist/execution/relay-env.js +5 -3
  19. package/dist/index.cjs +14045 -16059
  20. package/dist/index.d.cts +1035 -1579
  21. package/dist/index.d.ts +1035 -1579
  22. package/dist/index.js +9838 -13800
  23. package/dist.bundle/index.cjs +28875 -30323
  24. package/package.json +20 -26
  25. package/src/__tests__/record-store-last-line.test.ts +16 -7
  26. package/src/core/error-message.ts +7 -6
  27. package/src/core/host-services.ts +8 -2
  28. package/src/core/notify-ports.ts +17 -2
  29. package/src/execution/__tests__/chat-engine-routing.test.ts +62 -28
  30. package/src/execution/__tests__/chat-round-first-round-watchdog.test.ts +255 -0
  31. package/src/execution/__tests__/collect-coordinator-service.test.ts +36 -47
  32. package/src/execution/__tests__/collect-mixed-dispatch.test.ts +20 -53
  33. package/src/execution/__tests__/delivery-methods.test.ts +179 -237
  34. package/src/execution/__tests__/execute-and-await-worktree.test.ts +0 -2
  35. package/src/execution/__tests__/execute-nesting.test.ts +20 -13
  36. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +20 -33
  37. package/src/execution/__tests__/gc-timer.test.ts +7 -22
  38. package/src/execution/__tests__/get-record-for-action-restart.test.ts +20 -10
  39. package/src/execution/__tests__/helpers/fake-engine-port.ts +187 -0
  40. package/src/execution/__tests__/helpers/spawn-mock.ts +36 -9
  41. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +1 -1
  42. package/src/execution/__tests__/idle-gc.test.ts +200 -0
  43. package/src/execution/__tests__/lifecycle-predicates.test.ts +26 -21
  44. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +5 -2
  45. package/src/execution/__tests__/reconcile-sweep.test.ts +200 -0
  46. package/src/execution/__tests__/record-store.test.ts +60 -6
  47. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +20 -13
  48. package/src/execution/__tests__/round-supervisor.test.ts +293 -0
  49. package/src/execution/__tests__/session-pending.test.ts +119 -17
  50. package/src/execution/__tests__/stream-sink-retirement.test.ts +10 -10
  51. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +1 -11
  52. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +33 -30
  53. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +2 -9
  54. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +59 -129
  55. package/src/execution/__tests__/subagent-service.test.ts +2 -2
  56. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +109 -7
  57. package/src/execution/__tests__/subprocess-agent-runner.test.ts +108 -36
  58. package/src/execution/__tests__/sync-collect-recovery.test.ts +21 -36
  59. package/src/execution/__tests__/workflow-state-root.test.ts +232 -0
  60. package/src/execution/agent-registry.ts +6 -2
  61. package/src/execution/dialog-queue.ts +6 -73
  62. package/src/execution/engine/__tests__/common/capability-gate.test.ts +115 -15
  63. package/src/execution/engine/__tests__/common/errors.test.ts +11 -18
  64. package/src/execution/engine/__tests__/common/kill-chain.test.ts +7 -0
  65. package/src/execution/engine/__tests__/common/nesting-guard.test.ts +9 -3
  66. package/src/execution/engine/__tests__/common/session-view-service.test.ts +90 -3
  67. package/src/execution/engine/__tests__/conformance/H9-test-disposition.md +116 -0
  68. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/fake-engine-protocol.mjs +377 -0
  69. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/recorded.fixture.json +331 -0
  70. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/smoke-run.fixture.json +213 -0
  71. package/src/execution/engine/__tests__/conformance/chat-round-protocol.test.ts +230 -0
  72. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +69 -42
  73. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +4 -81
  74. package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +67 -44
  75. package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +81 -54
  76. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +133 -70
  77. package/src/execution/engine/__tests__/conformance/engine-crash.test.ts +143 -0
  78. package/src/execution/engine/__tests__/conformance/fake-engine-capabilities.ts +19 -0
  79. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +7 -2
  80. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +6 -5
  81. package/src/execution/engine/__tests__/conformance/protocol-blackbox.test.ts +398 -0
  82. package/src/execution/engine/__tests__/conformance/registry-fork-filter.test.ts +221 -0
  83. package/src/execution/engine/__tests__/conformance/round-liveness-supervisor.test.ts +302 -0
  84. package/src/execution/engine/__tests__/conformance/static-spawn-guard.test.ts +58 -0
  85. package/src/execution/engine/__tests__/engine-discovery-scan.test.ts +558 -0
  86. package/src/execution/engine/__tests__/host-task-spec.test.ts +45 -0
  87. package/src/execution/engine/__tests__/model-ref-split.test.ts +74 -0
  88. package/src/execution/engine/__tests__/registry.test.ts +206 -1
  89. package/src/execution/engine/__tests__/routing.test.ts +97 -0
  90. package/src/execution/engine/__tests__/run-failure-worktree-cleanup.test.ts +97 -0
  91. package/src/execution/engine/client/__tests__/__fixtures__/fake-engine.mjs +294 -0
  92. package/src/execution/engine/client/__tests__/engine-client.test.ts +516 -0
  93. package/src/execution/engine/client/__tests__/mirror.test.ts +67 -0
  94. package/src/execution/engine/client/__tests__/pid-file.test.ts +257 -0
  95. package/src/execution/engine/client/__tests__/protocol-closure.test.ts +95 -0
  96. package/src/execution/engine/client/__tests__/remote-engine.test.ts +457 -0
  97. package/src/execution/engine/client/client-options.ts +103 -0
  98. package/src/execution/engine/client/engine-client.ts +687 -0
  99. package/src/execution/engine/client/mirror.ts +167 -0
  100. package/src/execution/engine/client/pid-file.ts +392 -0
  101. package/src/execution/engine/client/reaper.ts +95 -0
  102. package/src/execution/engine/client/remote-engine.ts +441 -0
  103. package/src/execution/engine/client/reverse-router.ts +255 -0
  104. package/src/execution/engine/common/capability-gate.ts +109 -22
  105. package/src/execution/engine/common/errors.ts +9 -25
  106. package/src/execution/engine/common/event-journal.ts +4 -50
  107. package/src/execution/engine/common/journal-replay.ts +3 -1
  108. package/src/execution/engine/common/kill-chain.ts +18 -251
  109. package/src/execution/engine/common/nesting-guard.ts +18 -114
  110. package/src/execution/engine/common/pool-manager.ts +4 -0
  111. package/src/execution/engine/common/session-view-service.ts +56 -84
  112. package/src/execution/engine/config.ts +151 -0
  113. package/src/execution/engine/d8-compat.ts +346 -0
  114. package/src/execution/engine/engine-discovery-roots.ts +107 -0
  115. package/src/execution/engine/engine-discovery-scan.ts +269 -0
  116. package/src/execution/engine/engine-discovery.ts +30 -7
  117. package/src/execution/engine/engine-inspect-package.ts +282 -0
  118. package/src/execution/engine/engine-manifest.ts +244 -0
  119. package/src/execution/engine/host/__tests__/host-askuser-endpoint.test.ts +123 -0
  120. package/src/execution/engine/host/__tests__/host-bridge.test.ts +135 -0
  121. package/src/execution/engine/host/__tests__/spawned-children.test.ts +73 -0
  122. package/src/execution/engine/host/host-bridge.ts +150 -0
  123. package/src/execution/engine/host/host-ui-endpoint.ts +47 -0
  124. package/src/execution/engine/host/pi-host-binding.ts +91 -0
  125. package/src/execution/engine/host/spawned-children.ts +139 -0
  126. package/src/execution/engine/host-task-spec.ts +29 -2
  127. package/src/execution/engine/model-validation.ts +55 -2
  128. package/src/execution/engine/paths.ts +16 -41
  129. package/src/execution/engine/port.ts +48 -2
  130. package/src/execution/engine/registry.ts +153 -34
  131. package/src/execution/engine/routing.ts +225 -52
  132. package/src/execution/engine/types.ts +25 -200
  133. package/src/execution/finalize-record.ts +25 -0
  134. package/src/execution/idle-gc.ts +89 -14
  135. package/src/execution/lifecycle-manager.ts +10 -7
  136. package/src/execution/lifecycle-predicates.ts +10 -8
  137. package/src/execution/record-store.ts +29 -2
  138. package/src/execution/relay-env.ts +19 -36
  139. package/src/execution/round-supervisor/domain.ts +89 -0
  140. package/src/execution/round-supervisor/index.ts +33 -0
  141. package/src/execution/round-supervisor/notify-accounting.ts +87 -0
  142. package/src/execution/round-supervisor/reconcile-sweep.ts +235 -0
  143. package/src/execution/round-supervisor/service-binding.test.ts +661 -0
  144. package/src/execution/round-supervisor/service-binding.ts +273 -0
  145. package/src/execution/round-supervisor/supervisor.ts +426 -0
  146. package/src/execution/session-pending.ts +68 -74
  147. package/src/execution/settled-watchdog.ts +39 -0
  148. package/src/execution/subagent-service.ts +746 -400
  149. package/src/execution/subprocess-agent-runner.ts +16 -8
  150. package/src/execution/types.ts +41 -117
  151. package/src/execution/ui-channels.ts +13 -214
  152. package/src/execution/ui-request-handler-factory.ts +7 -4
  153. package/src/execution/ui-request-observability.ts +2 -2
  154. package/src/execution/workflow-state-root.ts +67 -0
  155. package/src/execution/worktree-git-ops.ts +5 -0
  156. package/src/execution/worktree-manager.ts +11 -1
  157. package/src/index.ts +55 -26
  158. package/src/orchestration/__tests__/execute-agent-call.test.ts +1 -1
  159. package/src/orchestration/execute-agent-call.ts +1 -1
  160. package/src/orchestration/file-run-store.ts +144 -59
  161. package/src/orchestration/models/types.ts +43 -42
  162. package/src/shared/zcode-model-ref.ts +31 -0
  163. package/dist/chunk-43ONBFZX.js +0 -240
  164. package/dist/chunk-A4IVZWVX.js +0 -31
  165. package/dist/chunk-APZY4IME.js +0 -27
  166. package/dist/chunk-V3VHZ2VX.js +0 -59
  167. package/dist/execution/engine/engines/zcode/constants.cjs +0 -107
  168. package/dist/execution/engine/engines/zcode/constants.d.cts +0 -135
  169. package/dist/execution/engine/engines/zcode/constants.d.ts +0 -135
  170. package/dist/execution/engine/engines/zcode/constants.js +0 -54
  171. package/dist/execution/engine/engines/zcode/reader.cjs +0 -276
  172. package/dist/execution/engine/engines/zcode/reader.d.cts +0 -21
  173. package/dist/execution/engine/engines/zcode/reader.d.ts +0 -21
  174. package/dist/execution/engine/engines/zcode/reader.js +0 -9
  175. package/src/__tests__/append-system-prompt-assembly.test.ts +0 -243
  176. package/src/__tests__/fr4-get-state-handshake.test.ts +0 -125
  177. package/src/__tests__/session-runner.test.ts +0 -69
  178. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +0 -490
  179. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +0 -191
  180. package/src/execution/__tests__/descendant-sweep-guards.test.ts +0 -220
  181. package/src/execution/__tests__/descendant-sweep.test.ts +0 -269
  182. package/src/execution/__tests__/engine-model-validation.test.ts +0 -741
  183. package/src/execution/__tests__/epipe-fallback.test.ts +0 -248
  184. package/src/execution/__tests__/execution-runtime-face.test.ts +0 -272
  185. package/src/execution/__tests__/get-state-handshake.test.ts +0 -127
  186. package/src/execution/__tests__/helpers/session-runner-mocks.ts +0 -113
  187. package/src/execution/__tests__/keep-alive-no-progress.test.ts +0 -280
  188. package/src/execution/__tests__/kill-all-escalation.test.ts +0 -196
  189. package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +0 -113
  190. package/src/execution/__tests__/output-collector.test.ts +0 -469
  191. package/src/execution/__tests__/pi-invocation.test.ts +0 -166
  192. package/src/execution/__tests__/recursive-visibility-env.test.ts +0 -149
  193. package/src/execution/__tests__/rpc-mode.test.ts +0 -89
  194. package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +0 -387
  195. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +0 -277
  196. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +0 -254
  197. package/src/execution/__tests__/run-spawn-edges.test.ts +0 -1079
  198. package/src/execution/__tests__/run-spawn-integration.test.ts +0 -933
  199. package/src/execution/__tests__/run-spawn-resume.test.ts +0 -322
  200. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +0 -196
  201. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +0 -207
  202. package/src/execution/__tests__/service-kill-escalation.test.ts +0 -91
  203. package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +0 -110
  204. package/src/execution/__tests__/session-runner-close-prune.test.ts +0 -181
  205. package/src/execution/__tests__/session-runner-dispatch.test.ts +0 -489
  206. package/src/execution/__tests__/session-runner-epipe.test.ts +0 -179
  207. package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +0 -227
  208. package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +0 -290
  209. package/src/execution/__tests__/session-runner-schema-env.test.ts +0 -352
  210. package/src/execution/__tests__/settled-watchdog.test.ts +0 -524
  211. package/src/execution/__tests__/spawn-args.test.ts +0 -446
  212. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +0 -192
  213. package/src/execution/__tests__/spawn-event-adapter.test.ts +0 -167
  214. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +0 -208
  215. package/src/execution/__tests__/spawned-children.test.ts +0 -236
  216. package/src/execution/__tests__/start-sync-model-guard.test.ts +0 -150
  217. package/src/execution/__tests__/stdin-writer.test.ts +0 -464
  218. package/src/execution/__tests__/subagent-service-message-close.test.ts +0 -693
  219. package/src/execution/__tests__/temp-prompt.test.ts +0 -53
  220. package/src/execution/__tests__/timeout-integration.test.ts +0 -616
  221. package/src/execution/__tests__/turn-limiter-semantics.test.ts +0 -194
  222. package/src/execution/__tests__/turn-limiter.test.ts +0 -65
  223. package/src/execution/__tests__/ui-request-handler.test.ts +0 -205
  224. package/src/execution/__tests__/ui-request-queue.test.ts +0 -298
  225. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +0 -230
  226. package/src/execution/engine/__tests__/common/schema-emulation.test.ts +0 -128
  227. package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +0 -149
  228. package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +0 -28
  229. package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +0 -128
  230. package/src/execution/engine/__tests__/paths.test.ts +0 -39
  231. package/src/execution/engine/common/schema-emulation.ts +0 -189
  232. package/src/execution/engine/common/session-view-projection.ts +0 -51
  233. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +0 -615
  234. package/src/execution/engine/engines/pi/__tests__/reader.test.ts +0 -155
  235. package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +0 -257
  236. package/src/execution/engine/engines/pi/argv-mirror.ts +0 -135
  237. package/src/execution/engine/engines/pi/get-state-handshake.ts +0 -179
  238. package/src/execution/engine/engines/pi/output-collector.ts +0 -317
  239. package/src/execution/engine/engines/pi/pi-engine.ts +0 -811
  240. package/src/execution/engine/engines/pi/pi-invocation.ts +0 -120
  241. package/src/execution/engine/engines/pi/reader.ts +0 -59
  242. package/src/execution/engine/engines/pi/registration.ts +0 -37
  243. package/src/execution/engine/engines/pi/session-runner.ts +0 -2840
  244. package/src/execution/engine/engines/pi/spawn-event-adapter.ts +0 -404
  245. package/src/execution/engine/engines/pi/stdin-writer.ts +0 -199
  246. package/src/execution/engine/engines/pi/temp-prompt.ts +0 -62
  247. package/src/execution/engine/engines/pi/turn-limiter.ts +0 -102
  248. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +0 -301
  249. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +0 -36
  250. package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +0 -321
  251. package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +0 -473
  252. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +0 -43
  253. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +0 -124
  254. package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +0 -210
  255. package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +0 -71
  256. package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +0 -247
  257. package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +0 -451
  258. package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +0 -792
  259. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +0 -782
  260. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +0 -168
  261. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +0 -210
  262. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +0 -353
  263. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +0 -282
  264. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +0 -372
  265. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +0 -145
  266. package/src/execution/engine/engines/zcode/appserver-launcher.ts +0 -167
  267. package/src/execution/engine/engines/zcode/connection.ts +0 -598
  268. package/src/execution/engine/engines/zcode/constants.ts +0 -176
  269. package/src/execution/engine/engines/zcode/golden-sample.ts +0 -47
  270. package/src/execution/engine/engines/zcode/parser.ts +0 -102
  271. package/src/execution/engine/engines/zcode/preparer.ts +0 -235
  272. package/src/execution/engine/engines/zcode/reader.ts +0 -381
  273. package/src/execution/engine/engines/zcode/registration.ts +0 -37
  274. package/src/execution/engine/engines/zcode/session-channel.ts +0 -968
  275. package/src/execution/engine/engines/zcode/zcode-engine.ts +0 -1431
  276. package/src/execution/round-settlement.ts +0 -93
  277. package/src/execution/ui-request-queue.ts +0 -215
  278. package/src/shared/schema-env.ts +0 -44
@@ -1,194 +0,0 @@
1
- // src/execution/__tests__/turn-limiter-semantics.test.ts
2
- //
3
- // SP-9: turn-limiter chatMode 语义——chatMode 下 maxTurns 每轮 reset(不跨轮累计)。
4
- // 设计 D9 决策:续聊本质是无限轮,累计上限违背 G1。
5
-
6
- import { describe, expect, it, vi } from "vitest";
7
-
8
- import { createTurnLimiter } from "../engine/engines/pi/turn-limiter.ts";
9
-
10
- describe("SP-9: turn-limiter chatMode semantics (maxTurns reset per round)", () => {
11
- describe("TC-1: chatMode 多轮后 maxTurns 每轮 reset(不累计)", () => {
12
- it("reset 后 maxTurns 不累计——第二轮从 0 开始,不触发 steer/abort", () => {
13
- const steer = vi.fn();
14
- const abort = vi.fn();
15
- const limiter = createTurnLimiter({ maxTurns: 3, graceTurns: 2, steer, abort });
16
-
17
- // 第一轮:3 turns → steer(达到 maxTurns=3)
18
- limiter.onTurnEnd(1);
19
- limiter.onTurnEnd(2);
20
- limiter.onTurnEnd(3);
21
- expect(steer).toHaveBeenCalledTimes(1);
22
- expect(abort).not.toHaveBeenCalled();
23
-
24
- // agent_settled → reset(模拟 chatMode 每轮 reset)
25
- limiter.reset();
26
-
27
- // 第二轮从 turnCount=0 开始(record.turnCount 由 session-runner 归零):
28
- // 2 turns 不触发 steer(距离 maxTurns=3 还差 1)
29
- limiter.onTurnEnd(1);
30
- limiter.onTurnEnd(2);
31
- // steer 仍然是 1 次(第一轮那次),第二轮未触发
32
- expect(steer).toHaveBeenCalledTimes(1);
33
- expect(abort).not.toHaveBeenCalled();
34
- });
35
-
36
- it("reset 后第二轮同样达到 maxTurns 时仍触发 steer", () => {
37
- const steer = vi.fn();
38
- const abort = vi.fn();
39
- const limiter = createTurnLimiter({ maxTurns: 2, graceTurns: 1, steer, abort });
40
-
41
- // 第一轮:steer + grace + abort
42
- limiter.onTurnEnd(1);
43
- limiter.onTurnEnd(2); // steer
44
- limiter.onTurnEnd(3); // abort
45
- expect(steer).toHaveBeenCalledTimes(1);
46
- expect(abort).toHaveBeenCalledTimes(1);
47
-
48
- // reset(chatMode 新轮)
49
- limiter.reset();
50
- expect(limiter.didSteer).toBe(false);
51
- expect(limiter.didAbort).toBe(false);
52
-
53
- // 第二轮:同样走完 steer + grace 路径
54
- limiter.onTurnEnd(1);
55
- limiter.onTurnEnd(2); // steer again
56
- expect(steer).toHaveBeenCalledTimes(2);
57
- expect(abort).toHaveBeenCalledTimes(1); // 还没到 abort
58
-
59
- limiter.onTurnEnd(3); // abort again
60
- expect(abort).toHaveBeenCalledTimes(2);
61
- });
62
-
63
- it("多轮连续 reset 后 limiter 始终可用", () => {
64
- const steer = vi.fn();
65
- const abort = vi.fn();
66
- const limiter = createTurnLimiter({ maxTurns: 2, graceTurns: 0, steer, abort });
67
-
68
- for (let round = 1; round <= 5; round++) {
69
- limiter.onTurnEnd(1);
70
- limiter.onTurnEnd(2); // steer + abort (graceTurns=0)
71
- expect(steer).toHaveBeenCalledTimes(round);
72
- expect(abort).toHaveBeenCalledTimes(round);
73
- limiter.reset();
74
- }
75
- });
76
- });
77
-
78
- describe("TC-2: graceTurns 每轮 reset", () => {
79
- it("reset 后 graceTurns 从 0 重新计数", () => {
80
- const steer = vi.fn();
81
- const abort = vi.fn();
82
- const limiter = createTurnLimiter({ maxTurns: 2, graceTurns: 3, steer, abort });
83
-
84
- // 第一轮:steer at turn 2,用掉 1 个 grace turn
85
- limiter.onTurnEnd(1);
86
- limiter.onTurnEnd(2); // steer
87
- limiter.onTurnEnd(3); // grace turn 1
88
- expect(abort).not.toHaveBeenCalled();
89
-
90
- // reset(模拟 chatMode agent_settled)
91
- limiter.reset();
92
-
93
- // 第二轮:从 turn 1 开始,graceTurns 重新从 0 计数
94
- limiter.onTurnEnd(1);
95
- limiter.onTurnEnd(2); // steer again
96
- limiter.onTurnEnd(3); // grace turn 1(重计)
97
- limiter.onTurnEnd(4); // grace turn 2
98
- limiter.onTurnEnd(5); // grace turn 3 → abort
99
- expect(abort).toHaveBeenCalledTimes(1);
100
- });
101
-
102
- it("reset 前未 steer 时 reset 后仍正常工作", () => {
103
- const steer = vi.fn();
104
- const abort = vi.fn();
105
- const limiter = createTurnLimiter({ maxTurns: 5, graceTurns: 2, steer, abort });
106
-
107
- // 第一轮:只走了 2 turns(未达 maxTurns)
108
- limiter.onTurnEnd(1);
109
- limiter.onTurnEnd(2);
110
- expect(steer).not.toHaveBeenCalled();
111
-
112
- limiter.reset();
113
-
114
- // 第二轮:从 turn 1 开始,到 maxTurns=5 触发 steer
115
- limiter.onTurnEnd(1);
116
- limiter.onTurnEnd(2);
117
- limiter.onTurnEnd(3);
118
- limiter.onTurnEnd(4);
119
- limiter.onTurnEnd(5); // steer
120
- expect(steer).toHaveBeenCalledTimes(1);
121
- limiter.onTurnEnd(6);
122
- limiter.onTurnEnd(7); // abort (5+2)
123
- expect(abort).toHaveBeenCalledTimes(1);
124
- });
125
- });
126
-
127
- describe("TC-3: 非 chatMode 行为不变(全程累计)", () => {
128
- it("不调用 reset 时 turn-limiter 行为与原来完全一致", () => {
129
- const steer = vi.fn();
130
- const abort = vi.fn();
131
- const limiter = createTurnLimiter({ maxTurns: 3, graceTurns: 2, steer, abort });
132
-
133
- // 全程累计(非 chatMode,不 reset):3→steer,5→abort
134
- limiter.onTurnEnd(1);
135
- limiter.onTurnEnd(2);
136
- limiter.onTurnEnd(3); // steer
137
- expect(steer).toHaveBeenCalledTimes(1);
138
- limiter.onTurnEnd(4); // grace 1
139
- limiter.onTurnEnd(5); // grace 2 → abort
140
- expect(abort).toHaveBeenCalledTimes(1);
141
- expect(limiter.didSteer).toBe(true);
142
- expect(limiter.didAbort).toBe(true);
143
- });
144
-
145
- it("maxTurns=0(禁用)不因 reset 而误启用", () => {
146
- const steer = vi.fn();
147
- const abort = vi.fn();
148
- const limiter = createTurnLimiter({ maxTurns: 0, graceTurns: 2, steer, abort });
149
-
150
- limiter.onTurnEnd(100);
151
- expect(steer).not.toHaveBeenCalled();
152
-
153
- limiter.reset();
154
- limiter.onTurnEnd(100);
155
- // maxTurns=0(禁用),即使 reset 也不触发
156
- expect(steer).not.toHaveBeenCalled();
157
- expect(abort).not.toHaveBeenCalled();
158
- });
159
-
160
- it("全程累计(不 reset)达到 maxTurns+graceTurns 后不再触发", () => {
161
- const steer = vi.fn();
162
- const abort = vi.fn();
163
- const limiter = createTurnLimiter({ maxTurns: 2, graceTurns: 1, steer, abort });
164
-
165
- limiter.onTurnEnd(1);
166
- limiter.onTurnEnd(2); // steer
167
- limiter.onTurnEnd(3); // abort
168
- expect(steer).toHaveBeenCalledTimes(1);
169
- expect(abort).toHaveBeenCalledTimes(1);
170
-
171
- // 已 abort 后继续调用 onTurnEnd(不应再次触发)
172
- limiter.onTurnEnd(4);
173
- limiter.onTurnEnd(5);
174
- expect(steer).toHaveBeenCalledTimes(1);
175
- expect(abort).toHaveBeenCalledTimes(1);
176
- });
177
- });
178
-
179
- describe("TC-4: reset 诊断属性", () => {
180
- it("reset 清除 didSteer 和 didAbort 诊断标志", () => {
181
- const steer = vi.fn();
182
- const abort = vi.fn();
183
- const limiter = createTurnLimiter({ maxTurns: 1, graceTurns: 0, steer, abort });
184
-
185
- limiter.onTurnEnd(1); // steer + abort (graceTurns=0)
186
- expect(limiter.didSteer).toBe(true);
187
- expect(limiter.didAbort).toBe(true);
188
-
189
- limiter.reset();
190
- expect(limiter.didSteer).toBe(false);
191
- expect(limiter.didAbort).toBe(false);
192
- });
193
- });
194
- });
@@ -1,65 +0,0 @@
1
- // src/__tests__/turn-limiter.test.ts
2
- import { describe, expect, it, vi } from "vitest";
3
-
4
- import { createTurnLimiter } from "../engine/engines/pi/turn-limiter.ts";
5
-
6
- describe("createTurnLimiter", () => {
7
- it("does nothing before maxTurns", () => {
8
- const steer = vi.fn();
9
- const abort = vi.fn();
10
- const limiter = createTurnLimiter({ maxTurns: 3, graceTurns: 2, steer, abort });
11
- limiter.onTurnEnd(1);
12
- limiter.onTurnEnd(2);
13
- expect(steer).not.toHaveBeenCalled();
14
- expect(abort).not.toHaveBeenCalled();
15
- expect(limiter.didSteer).toBe(false);
16
- expect(limiter.didAbort).toBe(false);
17
- });
18
-
19
- it("steers on maxTurns and aborts after graceTurns", () => {
20
- const steer = vi.fn();
21
- const abort = vi.fn();
22
- const limiter = createTurnLimiter({ maxTurns: 3, graceTurns: 2, steer, abort });
23
- limiter.onTurnEnd(3); // 达到 maxTurns → steer
24
- expect(steer).toHaveBeenCalledWith(expect.stringContaining("You have reached your turn limit"));
25
- expect(abort).not.toHaveBeenCalled();
26
- expect(limiter.didSteer).toBe(true);
27
- limiter.onTurnEnd(4); // grace turn 1
28
- expect(abort).not.toHaveBeenCalled();
29
- limiter.onTurnEnd(5); // grace turn 2 → abort
30
- expect(abort).toHaveBeenCalledTimes(1);
31
- expect(limiter.didAbort).toBe(true);
32
- });
33
-
34
- it("disables when maxTurns is 0", () => {
35
- const steer = vi.fn();
36
- const abort = vi.fn();
37
- const limiter = createTurnLimiter({ maxTurns: 0, graceTurns: 2, steer, abort });
38
- limiter.onTurnEnd(100);
39
- expect(steer).not.toHaveBeenCalled();
40
- expect(abort).not.toHaveBeenCalled();
41
- expect(limiter.didSteer).toBe(false);
42
- });
43
-
44
- it("steers only once", () => {
45
- const steer = vi.fn();
46
- const abort = vi.fn();
47
- const limiter = createTurnLimiter({ maxTurns: 2, graceTurns: 3, steer, abort });
48
- limiter.onTurnEnd(2);
49
- limiter.onTurnEnd(3);
50
- expect(steer).toHaveBeenCalledTimes(1);
51
- expect(limiter.didSteer).toBe(true);
52
- });
53
-
54
- it("aborts on the same turn as steer when graceTurns is 0", () => {
55
- const steer = vi.fn();
56
- const abort = vi.fn();
57
- const limiter = createTurnLimiter({ maxTurns: 2, graceTurns: 0, steer, abort });
58
- // grace=0 → limit+grace == limit,steer 后同一 turn 即满足 abort 条件
59
- limiter.onTurnEnd(2); // maxTurns → steer + abort(同 turn)
60
- expect(steer).toHaveBeenCalledTimes(1);
61
- expect(abort).toHaveBeenCalledTimes(1);
62
- expect(limiter.didSteer).toBe(true);
63
- expect(limiter.didAbort).toBe(true);
64
- });
65
- });
@@ -1,205 +0,0 @@
1
- // src/__tests__/ui-request-handler.test.ts
2
- //
3
- // W2 测试(TC-W2 / SR-2):UI 请求处理 —— Pi 原生协议格式。
4
- //
5
- // 测试对象:session-runner.ts 的 handleUiRequest + spawn-event-adapter.ts 的 parseSpawnLine
6
- // 契约来源:.fix-plans/00-master-summary.md §二 2.1(ExtensionUiRequest)+ 2.2(UiRequest/channel)
7
- // + 2.3(stdin 回写 extension_ui_response 格式)+ §一冲突 2(channel 提取在 session-runner 层)
8
- //
9
- // 修复原因(FR-12/SR-2):
10
- // 旧测试用错误的 JSON-RPC 2.0 格式 mock(jsonrpc:"2.0" + params.marker:"ASK_USER"
11
- // + params.questions),测试绿但生产红(Pi 实际发 {type, method, title, options} 平铺)。
12
- // 改为 Pi 真实格式 + channel/channelPayload 断言 + extension_ui_response 回写格式。
13
- //
14
- // 红灯原因:handleUiRequest 签名仍是旧的 (child, id, params, ctx, signal)
15
- // + parseChannel 未接入 session-runner,编译失败。W2 改完签名后转绿。
16
-
17
- import { PassThrough } from "node:stream";
18
-
19
- import { describe, expect, it, vi } from "vitest";
20
-
21
- import { ASK_USER_RPC_PROMPT } from "../engine/engines/pi/session-runner.ts";
22
- import { parseSpawnLine } from "../engine/engines/pi/spawn-event-adapter.ts";
23
- import { parseChannel } from "../ui-channels.ts";
24
- import { createUiRequestQueue, type UiRequest, type UiRequestHandler } from "../ui-request-queue.ts";
25
-
26
- // ── Pi 原生协议样本构造 ────────────────────────────────────
27
- // 真实格式:{type:"extension_ui_request", id, method:"select",
28
- // title:"\0XYZ_ASK_USER", options:[JSON.stringify({questions, allowCancel})]}
29
- // 无 jsonrpc 字段、无 params 包裹、method 在顶层(非 method:"extension_ui_request")。
30
-
31
- const ASK_USER_MARKER = "\0XYZ_ASK_USER";
32
- const askUserPayload = {
33
- questions: [
34
- {
35
- question: "What is your preference?",
36
- options: [{ label: "Option A" }, { label: "Option B" }],
37
- },
38
- ],
39
- allowCancel: true,
40
- };
41
-
42
- function askUserLine(id: string): string {
43
- return JSON.stringify({
44
- type: "extension_ui_request",
45
- id,
46
- method: "select",
47
- title: ASK_USER_MARKER,
48
- options: [JSON.stringify(askUserPayload)],
49
- });
50
- }
51
-
52
- // ── 解析:Pi 原生格式 → ExtensionUiRequest(method 平铺,无 params) ─────
53
-
54
- describe("parseSpawnLine — ask_user 请求解析(Pi 原生格式)", () => {
55
- it("ask_user 行被识别为 extension_ui_request kind", () => {
56
- const result = parseSpawnLine(askUserLine("ui-req-001"));
57
- expect(result?.kind).toBe("extension_ui_request");
58
- });
59
-
60
- it("request.method === 'select'(ask_user 借道 select dialog 通道)", () => {
61
- const result = parseSpawnLine(askUserLine("ui-req-001"));
62
- if (result?.kind !== "extension_ui_request") {
63
- expect.fail("expected extension_ui_request kind");
64
- return;
65
- }
66
- expect(result.request.method).toBe("select");
67
- });
68
-
69
- it("request.title === ASK_USER_MARKER(NUL 前缀 marker 原样保留,由 parseChannel 提取)", () => {
70
- const result = parseSpawnLine(askUserLine("ui-req-001"));
71
- if (result?.kind !== "extension_ui_request") return;
72
- if (result.request.method !== "select") return expect.fail("request.method 应为 select");
73
- expect((result.request as { title?: string }).title).toBe(ASK_USER_MARKER);
74
- });
75
-
76
- it("id 被正确提取(用于 response 关联)", () => {
77
- const result = parseSpawnLine(askUserLine("ui-req-001"));
78
- if (result?.kind !== "extension_ui_request") return;
79
- expect(result.id).toBe("ui-req-001");
80
- });
81
- });
82
-
83
- // ── channel 提取(session-runner 层消费 parseChannel) ──────────────
84
- // adapter 层只做协议解析(method + 字段平铺),channel 提取在 session-runner 层。
85
- // 对 select.title 的 NUL 前缀解析出 channel='ask_user' + payload={questions, allowCancel}。
86
-
87
- describe("parseChannel — ask_user channel 提取(session-runner 层)", () => {
88
- it("select.title 含 ASK_USER_MARKER → channel='ask_user'", () => {
89
- const result = parseSpawnLine(askUserLine("ui-req-001"));
90
- if (result?.kind !== "extension_ui_request") return;
91
- const channel = parseChannel(result.request);
92
- expect(channel.channel).toBe("ask_user");
93
- });
94
-
95
- it("channelPayload 已 parse 为 {questions, allowCancel}", () => {
96
- const result = parseSpawnLine(askUserLine("ui-req-001"));
97
- if (result?.kind !== "extension_ui_request") return;
98
- const channel = parseChannel(result.request);
99
- expect(channel.channelPayload).toEqual(askUserPayload);
100
- });
101
- });
102
-
103
- // ── stdin 回写格式:extension_ui_response(非 JSON-RPC 2.0 result) ─────
104
-
105
- describe("handleUiRequest — stdin 回写 extension_ui_response(Pi 原生格式)", () => {
106
- it("handler 返回 {value} → stdin 写入 {type:extension_ui_response, id, value}", async () => {
107
- const stdin = new PassThrough();
108
- const written: string[] = [];
109
- stdin.on("data", (chunk: Buffer) => written.push(chunk.toString()));
110
-
111
- const child = { stdin, on: vi.fn(), removeListener: vi.fn() } as unknown as Parameters<
112
- typeof createUiRequestQueue
113
- >[0];
114
-
115
- const handler: UiRequestHandler = vi.fn(
116
- async (req: UiRequest) => ({ value: `answer-for-${req.id}` }),
117
- );
118
- const ctx = { uiRequestHandler: handler } as unknown as Parameters<
119
- typeof createUiRequestQueue
120
- >[1];
121
-
122
- const enqueue = createUiRequestQueue(child, ctx);
123
- // W2 新签名:enqueue(id, request) —— request 是 ExtensionUiRequest(method 平铺)
124
- enqueue("ui-req-002", {
125
- method: "select",
126
- title: ASK_USER_MARKER,
127
- options: [JSON.stringify(askUserPayload)],
128
- });
129
-
130
- // 等微任务 + stdin flush
131
- await new Promise((r) => setImmediate(r));
132
-
133
- const raw = written.join("");
134
- expect(raw).toContain('"type":"extension_ui_response"');
135
- expect(raw).toContain('"id":"ui-req-002"');
136
- expect(raw).toContain('"value":"answer-for-ui-req-002"');
137
- // 旧 JSON-RPC 2.0 格式不应出现
138
- expect(raw).not.toContain('"jsonrpc"');
139
- expect(raw).not.toContain('"result"');
140
- });
141
- });
142
-
143
- // ── handler 抛错兜底(M5):catch → 回 cancelled → 写 stdin ─────
144
- // session-runner.ts:489-494 的 catch 分支:handler reject 时兜底写 cancelled。
145
-
146
- describe("handleUiRequest — handler 抛错兜底回 cancelled", () => {
147
- it("handler reject(new Error('boom')) → stdin 写入 cancelled:true", async () => {
148
- const stdin = new PassThrough();
149
- const written: string[] = [];
150
- stdin.on("data", (chunk: Buffer) => written.push(chunk.toString()));
151
-
152
- const child = { stdin, on: vi.fn(), removeListener: vi.fn() } as unknown as Parameters<
153
- typeof createUiRequestQueue
154
- >[0];
155
-
156
- // handler 抛错(reject)
157
- const handler: UiRequestHandler = vi.fn(
158
- async () => Promise.reject(new Error("boom")),
159
- );
160
- const ctx = { uiRequestHandler: handler } as unknown as Parameters<
161
- typeof createUiRequestQueue
162
- >[1];
163
-
164
- const enqueue = createUiRequestQueue(child, ctx);
165
- enqueue("ui-req-err", {
166
- method: "select",
167
- title: ASK_USER_MARKER,
168
- options: [JSON.stringify(askUserPayload)],
169
- });
170
-
171
- // 等微任务 + stdin flush
172
- await new Promise((r) => setImmediate(r));
173
-
174
- const raw = written.join("");
175
- expect(raw).toContain('"type":"extension_ui_response"');
176
- expect(raw).toContain('"id":"ui-req-err"');
177
- expect(raw).toContain('"cancelled":true');
178
- });
179
- });
180
-
181
- // ── W4 提示词(保留,不受格式修复影响) ──────────────────────────
182
-
183
- describe("W4: ask_user RPC 系统提示词注入", () => {
184
- it("ASK_USER_RPC_PROMPT 常量已导出且非空", () => {
185
- expect(ASK_USER_RPC_PROMPT).toBeDefined();
186
- expect(typeof ASK_USER_RPC_PROMPT).toBe("string");
187
- expect(ASK_USER_RPC_PROMPT.length).toBeGreaterThan(0);
188
- });
189
-
190
- it("提示词包含 ask_user 工具说明", () => {
191
- expect(ASK_USER_RPC_PROMPT).toContain("ask_user");
192
- expect(ASK_USER_RPC_PROMPT).toContain("Tool Availability");
193
- });
194
-
195
- it("提示词告知 LLM ask_user 走 RPC 转发", () => {
196
- expect(ASK_USER_RPC_PROMPT).toContain("RPC");
197
- expect(ASK_USER_RPC_PROMPT).toContain("main agent");
198
- expect(ASK_USER_RPC_PROMPT).toContain("forwarded");
199
- });
200
-
201
- it("提示词说明用户在主 agent 界面回答", () => {
202
- expect(ASK_USER_RPC_PROMPT).toContain("user");
203
- expect(ASK_USER_RPC_PROMPT).toContain("answers");
204
- });
205
- });