@zhushanwen/pi-subagent-workflow 8.5.0 → 8.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 (336) hide show
  1. package/package.json +13 -16
  2. package/relay/relay.mjs +1 -1
  3. package/src/host/__tests__/pi-host.test.ts +214 -0
  4. package/src/host/pi-host.ts +109 -0
  5. package/src/index.ts +142 -32
  6. package/src/injectors/__tests__/engine-awareness.test.ts +292 -0
  7. package/src/injectors/__tests__/engine-section-stability.test.ts +341 -0
  8. package/src/injectors/__tests__/subagent-list-injector.test.ts +5 -5
  9. package/src/injectors/__tests__/workflow-list-injector.test.ts +5 -5
  10. package/src/injectors/engine-awareness.ts +123 -0
  11. package/src/injectors/model-list-injector.ts +1 -1
  12. package/src/injectors/subagent-list-injector.ts +8 -8
  13. package/src/injectors/workflow-list-injector.ts +8 -8
  14. package/src/interface/__tests__/detectors.test.ts +3 -3
  15. package/src/interface/__tests__/subagent-tool-path-guard.test.ts +157 -0
  16. package/src/interface/__tests__/subagent-tool-prompt.test.ts +12 -0
  17. package/src/interface/__tests__/tool-workflow-script-generate.test.ts +2 -2
  18. package/src/interface/__tests__/tool-workflow-throw-paths.test.ts +1 -1
  19. package/src/interface/bg-notify-render.ts +34 -13
  20. package/src/interface/commands.ts +3 -3
  21. package/src/interface/format.ts +2 -2
  22. package/src/interface/helpers.ts +3 -3
  23. package/src/interface/list-component.ts +4 -4
  24. package/src/interface/list-shared.ts +2 -2
  25. package/src/interface/list-view.ts +2 -2
  26. package/src/interface/subagent-actions.ts +288 -19
  27. package/src/interface/subagent-tool-schema.ts +163 -0
  28. package/src/interface/subagent-tool.ts +65 -129
  29. package/src/interface/subagents.ts +5 -5
  30. package/src/interface/tool-render.ts +34 -2
  31. package/src/interface/tool-workflow-script.ts +4 -4
  32. package/src/interface/tool-workflow.ts +6 -6
  33. package/src/interface/views/WorkflowsView.ts +5 -5
  34. package/src/interface/views/__tests__/WorkflowsView-signature.test.ts +3 -3
  35. package/src/interface/views/__tests__/detail-content-session-file.test.ts +2 -2
  36. package/src/interface/views/detail-content.ts +4 -4
  37. package/src/interface/views/format.ts +4 -4
  38. package/src/{orchestration/jsonl-run-store.ts → jsonl-run-store.ts} +132 -18
  39. package/src/execution/__tests__/__fixtures__/notifier-golden-snapshots.json +0 -53
  40. package/src/execution/__tests__/__fixtures__/truncline.snapshot.json +0 -1
  41. package/src/execution/__tests__/agent-registry.test.ts +0 -363
  42. package/src/execution/__tests__/agent-result-mapper.test.ts +0 -150
  43. package/src/execution/__tests__/alive-store.test.ts +0 -147
  44. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +0 -490
  45. package/src/execution/__tests__/before-agent-start-injection.test.ts +0 -132
  46. package/src/execution/__tests__/bg-notify-render.test.ts +0 -256
  47. package/src/execution/__tests__/channel-registry-handshake.test.ts +0 -243
  48. package/src/execution/__tests__/chat-engine-routing.test.ts +0 -597
  49. package/src/execution/__tests__/chatmode-first-round-closure-service.test.ts +0 -365
  50. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +0 -190
  51. package/src/execution/__tests__/chatmode-round-notify-real-chain.test.ts +0 -215
  52. package/src/execution/__tests__/concurrency-pool.test.ts +0 -250
  53. package/src/execution/__tests__/config.test.ts +0 -110
  54. package/src/execution/__tests__/conversation-wiring.test.ts +0 -198
  55. package/src/execution/__tests__/crash-recovery.test.ts +0 -320
  56. package/src/execution/__tests__/delivery-methods.test.ts +0 -385
  57. package/src/execution/__tests__/dialog-queue.test.ts +0 -299
  58. package/src/execution/__tests__/epipe-fallback.test.ts +0 -241
  59. package/src/execution/__tests__/execute-and-await-worktree.test.ts +0 -243
  60. package/src/execution/__tests__/execute-nesting.test.ts +0 -323
  61. package/src/execution/__tests__/execute-options-mapper.test.ts +0 -189
  62. package/src/execution/__tests__/execution-record.test.ts +0 -1284
  63. package/src/execution/__tests__/finalize-record.test.ts +0 -368
  64. package/src/execution/__tests__/finalized-marker.test.ts +0 -82
  65. package/src/execution/__tests__/format-schema-instruction.test.ts +0 -138
  66. package/src/execution/__tests__/format.test.ts +0 -458
  67. package/src/execution/__tests__/gc-timer.test.ts +0 -184
  68. package/src/execution/__tests__/get-record-for-action-restart.test.ts +0 -254
  69. package/src/execution/__tests__/gui-mode-dispatch.test.ts +0 -59
  70. package/src/execution/__tests__/helpers/mock-extension-api.ts +0 -30
  71. package/src/execution/__tests__/helpers/spawn-mock.ts +0 -238
  72. package/src/execution/__tests__/host-mode.test.ts +0 -87
  73. package/src/execution/__tests__/index-session-start-identity.test.ts +0 -371
  74. package/src/execution/__tests__/index-session-start.test.ts +0 -593
  75. package/src/execution/__tests__/lifecycle-manager-lock.test.ts +0 -211
  76. package/src/execution/__tests__/lifecycle-manager.test.ts +0 -337
  77. package/src/execution/__tests__/lifecycle-predicates.test.ts +0 -116
  78. package/src/execution/__tests__/list-component.test.ts +0 -401
  79. package/src/execution/__tests__/list-fields.test.ts +0 -109
  80. package/src/execution/__tests__/manifest-parentid.test.ts +0 -117
  81. package/src/execution/__tests__/model-resolver.test.ts +0 -413
  82. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +0 -287
  83. package/src/execution/__tests__/nested-visibility.test.ts +0 -325
  84. package/src/execution/__tests__/notifier-flush.test.ts +0 -574
  85. package/src/execution/__tests__/notifier-golden-snapshot.test.ts +0 -261
  86. package/src/execution/__tests__/one-shot-upgrade.test.ts +0 -205
  87. package/src/execution/__tests__/output-collector.test.ts +0 -61
  88. package/src/execution/__tests__/parent-child-matrix.test.ts +0 -336
  89. package/src/execution/__tests__/path-encoding.test.ts +0 -104
  90. package/src/execution/__tests__/pi-invocation.test.ts +0 -134
  91. package/src/execution/__tests__/record-store.test.ts +0 -1057
  92. package/src/execution/__tests__/records-cwd-isolation.test.ts +0 -91
  93. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +0 -339
  94. package/src/execution/__tests__/recursive-visibility-env.test.ts +0 -273
  95. package/src/execution/__tests__/relay-agent.test.ts +0 -448
  96. package/src/execution/__tests__/relay-env.test.ts +0 -42
  97. package/src/execution/__tests__/resource-policy.test.ts +0 -109
  98. package/src/execution/__tests__/rpc-mode.test.ts +0 -89
  99. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +0 -267
  100. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +0 -253
  101. package/src/execution/__tests__/run-spawn-edges.test.ts +0 -644
  102. package/src/execution/__tests__/run-spawn-integration.test.ts +0 -933
  103. package/src/execution/__tests__/run-spawn-resume.test.ts +0 -322
  104. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +0 -196
  105. package/src/execution/__tests__/sdk-contract.test.ts +0 -273
  106. package/src/execution/__tests__/session-context-resolver.test.ts +0 -167
  107. package/src/execution/__tests__/session-file-gc.test.ts +0 -293
  108. package/src/execution/__tests__/session-pending.test.ts +0 -197
  109. package/src/execution/__tests__/session-reconstructor.test.ts +0 -379
  110. package/src/execution/__tests__/session-runner-epipe.test.ts +0 -178
  111. package/src/execution/__tests__/session-runner-schema-env.test.ts +0 -311
  112. package/src/execution/__tests__/session-start-reaper.test.ts +0 -233
  113. package/src/execution/__tests__/spawn-args.test.ts +0 -434
  114. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +0 -189
  115. package/src/execution/__tests__/spawn-event-adapter.test.ts +0 -167
  116. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +0 -207
  117. package/src/execution/__tests__/spawned-children.test.ts +0 -92
  118. package/src/execution/__tests__/startup-config-declaration.test.ts +0 -35
  119. package/src/execution/__tests__/status-refactor.test.ts +0 -345
  120. package/src/execution/__tests__/stdin-writer.test.ts +0 -463
  121. package/src/execution/__tests__/stream-sink-retirement.test.ts +0 -261
  122. package/src/execution/__tests__/subagent-service-abort.test.ts +0 -60
  123. package/src/execution/__tests__/subagent-service-message-close.test.ts +0 -629
  124. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +0 -180
  125. package/src/execution/__tests__/subagent-service.test.ts +0 -788
  126. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +0 -310
  127. package/src/execution/__tests__/subprocess-agent-runner.test.ts +0 -506
  128. package/src/execution/__tests__/temp-prompt.test.ts +0 -53
  129. package/src/execution/__tests__/timeout-integration.test.ts +0 -397
  130. package/src/execution/__tests__/tombstone-store.test.ts +0 -73
  131. package/src/execution/__tests__/tool-action.test.ts +0 -385
  132. package/src/execution/__tests__/truncline-snapshot.test.ts +0 -81
  133. package/src/execution/__tests__/turn-limiter-semantics.test.ts +0 -194
  134. package/src/execution/__tests__/turn-limiter.test.ts +0 -65
  135. package/src/execution/__tests__/ui-channels.test.ts +0 -187
  136. package/src/execution/__tests__/ui-interaction-model.test.ts +0 -67
  137. package/src/execution/__tests__/ui-request-handler-factory.test.ts +0 -370
  138. package/src/execution/__tests__/ui-request-handler.test.ts +0 -204
  139. package/src/execution/__tests__/ui-request-observability.test.ts +0 -114
  140. package/src/execution/__tests__/ui-request-queue.test.ts +0 -133
  141. package/src/execution/__tests__/worktree-manager.test.ts +0 -633
  142. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +0 -229
  143. package/src/execution/__tests__/worktree-reconcile.integration.test.ts +0 -181
  144. package/src/execution/__tests__/worktree-registry.test.ts +0 -199
  145. package/src/execution/agent-registry.ts +0 -193
  146. package/src/execution/agent-result-mapper.ts +0 -90
  147. package/src/execution/alive-store.ts +0 -92
  148. package/src/execution/argv-mirror.ts +0 -109
  149. package/src/execution/best-effort.ts +0 -38
  150. package/src/execution/channel-registry-access.ts +0 -144
  151. package/src/execution/concurrency-pool.ts +0 -116
  152. package/src/execution/config.ts +0 -96
  153. package/src/execution/dialog-queue.ts +0 -330
  154. package/src/execution/engine/__tests__/common/data-dir.test.ts +0 -53
  155. package/src/execution/engine/__tests__/common/errors.test.ts +0 -132
  156. package/src/execution/engine/__tests__/common/event-journal.test.ts +0 -177
  157. package/src/execution/engine/__tests__/common/kill-chain.test.ts +0 -192
  158. package/src/execution/engine/__tests__/common/nesting-guard.test.ts +0 -81
  159. package/src/execution/engine/__tests__/common/persona-router.test.ts +0 -123
  160. package/src/execution/engine/__tests__/common/pool-manager.test.ts +0 -154
  161. package/src/execution/engine/__tests__/common/schema-emulation.test.ts +0 -128
  162. package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +0 -28
  163. package/src/execution/engine/__tests__/conformance/agent-event-invariants.ts +0 -141
  164. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +0 -109
  165. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +0 -101
  166. package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +0 -77
  167. package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +0 -104
  168. package/src/execution/engine/__tests__/conformance/contract.relay.test.ts +0 -342
  169. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +0 -201
  170. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +0 -76
  171. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +0 -79
  172. package/src/execution/engine/__tests__/engine-discovery.test.ts +0 -87
  173. package/src/execution/engine/__tests__/engines-declaration.test.ts +0 -36
  174. package/src/execution/engine/__tests__/model-prompt.test.ts +0 -85
  175. package/src/execution/engine/__tests__/paths.test.ts +0 -39
  176. package/src/execution/engine/__tests__/registry.test.ts +0 -120
  177. package/src/execution/engine/__tests__/routing.test.ts +0 -231
  178. package/src/execution/engine/common/data-dir.ts +0 -62
  179. package/src/execution/engine/common/errors.ts +0 -183
  180. package/src/execution/engine/common/event-journal.ts +0 -254
  181. package/src/execution/engine/common/journal-replay.ts +0 -62
  182. package/src/execution/engine/common/kill-chain.ts +0 -221
  183. package/src/execution/engine/common/nesting-guard.ts +0 -50
  184. package/src/execution/engine/common/persona-router.ts +0 -108
  185. package/src/execution/engine/common/pool-manager.ts +0 -226
  186. package/src/execution/engine/common/schema-emulation.ts +0 -189
  187. package/src/execution/engine/common/session-view-projection.ts +0 -51
  188. package/src/execution/engine/engine-discovery.ts +0 -65
  189. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +0 -469
  190. package/src/execution/engine/engines/pi/__tests__/reader.test.ts +0 -155
  191. package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
  192. package/src/execution/engine/engines/pi/pi-engine.ts +0 -415
  193. package/src/execution/engine/engines/pi/reader.ts +0 -48
  194. package/src/execution/engine/engines/pi/registration.ts +0 -35
  195. package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
  196. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
  197. package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
  198. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +0 -246
  199. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +0 -228
  200. package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +0 -210
  201. package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +0 -64
  202. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +0 -127
  203. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -567
  204. package/src/execution/engine/engines/zcode/constants.ts +0 -43
  205. package/src/execution/engine/engines/zcode/golden-sample.ts +0 -39
  206. package/src/execution/engine/engines/zcode/launcher.ts +0 -161
  207. package/src/execution/engine/engines/zcode/parser.ts +0 -436
  208. package/src/execution/engine/engines/zcode/preparer.ts +0 -363
  209. package/src/execution/engine/engines/zcode/reader.ts +0 -381
  210. package/src/execution/engine/engines/zcode/registration.ts +0 -37
  211. package/src/execution/engine/engines/zcode/zcode-engine.ts +0 -648
  212. package/src/execution/engine/host-task-spec.ts +0 -47
  213. package/src/execution/engine/model-prompt.ts +0 -59
  214. package/src/execution/engine/paths.ts +0 -42
  215. package/src/execution/engine/port.ts +0 -153
  216. package/src/execution/engine/registry.ts +0 -123
  217. package/src/execution/engine/routing.ts +0 -218
  218. package/src/execution/engine/types.ts +0 -304
  219. package/src/execution/execute-options-mapper.ts +0 -111
  220. package/src/execution/execution-record.ts +0 -906
  221. package/src/execution/finalize-record.ts +0 -251
  222. package/src/execution/finalized-marker.ts +0 -51
  223. package/src/execution/get-state-handshake.ts +0 -104
  224. package/src/execution/host-mode.ts +0 -56
  225. package/src/execution/idle-gc.ts +0 -47
  226. package/src/execution/lifecycle-manager.ts +0 -491
  227. package/src/execution/lifecycle-predicates.ts +0 -65
  228. package/src/execution/manifest-store.ts +0 -256
  229. package/src/execution/model-config-service.ts +0 -204
  230. package/src/execution/model-resolver.ts +0 -276
  231. package/src/execution/notifier.ts +0 -260
  232. package/src/execution/output-collector.ts +0 -88
  233. package/src/execution/path-encoding.ts +0 -60
  234. package/src/execution/pi-invocation.ts +0 -120
  235. package/src/execution/record-entry.ts +0 -132
  236. package/src/execution/record-store.ts +0 -1234
  237. package/src/execution/relay-env.ts +0 -37
  238. package/src/execution/session-context-resolver.ts +0 -64
  239. package/src/execution/session-file-gc.ts +0 -120
  240. package/src/execution/session-pending.ts +0 -164
  241. package/src/execution/session-reconstructor.ts +0 -678
  242. package/src/execution/session-runner.ts +0 -1548
  243. package/src/execution/sessions-index.ts +0 -304
  244. package/src/execution/spawn-event-adapter.ts +0 -363
  245. package/src/execution/stdin-writer.ts +0 -198
  246. package/src/execution/stream-sink.ts +0 -126
  247. package/src/execution/subagent-service.ts +0 -2119
  248. package/src/execution/subprocess-agent-runner.ts +0 -303
  249. package/src/execution/temp-prompt.ts +0 -62
  250. package/src/execution/tombstone-store.ts +0 -72
  251. package/src/execution/turn-limiter.ts +0 -102
  252. package/src/execution/types.ts +0 -910
  253. package/src/execution/ui-channels.ts +0 -216
  254. package/src/execution/ui-interaction-model.ts +0 -48
  255. package/src/execution/ui-request-handler-factory.ts +0 -212
  256. package/src/execution/ui-request-observability.ts +0 -81
  257. package/src/execution/ui-request-queue.ts +0 -174
  258. package/src/execution/worktree-manager.ts +0 -688
  259. package/src/execution/worktree-registry.ts +0 -222
  260. package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +0 -336
  261. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +0 -208
  262. package/src/orchestration/__tests__/agent-call-stream.test.ts +0 -157
  263. package/src/orchestration/__tests__/args-validator.test.ts +0 -143
  264. package/src/orchestration/__tests__/concurrency-gate.test.ts +0 -125
  265. package/src/orchestration/__tests__/config-loader.test.ts +0 -503
  266. package/src/orchestration/__tests__/error-recovery-handlers.test.ts +0 -724
  267. package/src/orchestration/__tests__/error-recovery-postmessage-defense.test.ts +0 -405
  268. package/src/orchestration/__tests__/error-recovery-serialize-failed-result.test.ts +0 -56
  269. package/src/orchestration/__tests__/error-recovery-workflow-call.test.ts +0 -166
  270. package/src/orchestration/__tests__/execute-agent-call.test.ts +0 -314
  271. package/src/orchestration/__tests__/jsonl-run-store-loadall-sources.test.ts +0 -171
  272. package/src/orchestration/__tests__/jsonl-run-store-session-file.test.ts +0 -1030
  273. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +0 -277
  274. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +0 -96
  275. package/src/orchestration/__tests__/lifecycle.test.ts +0 -625
  276. package/src/orchestration/__tests__/review-fix-loop-e2e.test.ts +0 -1971
  277. package/src/orchestration/__tests__/script-lint.test.ts +0 -831
  278. package/src/orchestration/__tests__/skill-discovery.test.ts +0 -157
  279. package/src/orchestration/__tests__/test-mocks.ts +0 -197
  280. package/src/orchestration/__tests__/worker-host.test.ts +0 -120
  281. package/src/orchestration/__tests__/worker-returnmeta-passthrough.test.ts +0 -164
  282. package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +0 -520
  283. package/src/orchestration/__tests__/worker-script-builder.test.ts +0 -309
  284. package/src/orchestration/__tests__/worker-script-template-snapshot.test.ts +0 -110
  285. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +0 -317
  286. package/src/orchestration/__tests__/workflow-script-lint-memo.test.ts +0 -110
  287. package/src/orchestration/__tests__/workflows-e2e.test.ts +0 -537
  288. package/src/orchestration/agent-opts-resolver.ts +0 -93
  289. package/src/orchestration/args-validator.ts +0 -127
  290. package/src/orchestration/concurrency-gate.ts +0 -69
  291. package/src/orchestration/config-loader.ts +0 -315
  292. package/src/orchestration/error-recovery.ts +0 -862
  293. package/src/orchestration/execute-agent-call.ts +0 -217
  294. package/src/orchestration/launcher.ts +0 -410
  295. package/src/orchestration/lifecycle.ts +0 -396
  296. package/src/orchestration/models/__tests__/budget.test.ts +0 -367
  297. package/src/orchestration/models/__tests__/trace.test.ts +0 -408
  298. package/src/orchestration/models/agent-call.ts +0 -83
  299. package/src/orchestration/models/budget.ts +0 -148
  300. package/src/orchestration/models/ports.ts +0 -164
  301. package/src/orchestration/models/run-runtime.ts +0 -89
  302. package/src/orchestration/models/run-spec.ts +0 -83
  303. package/src/orchestration/models/run-state.ts +0 -44
  304. package/src/orchestration/models/trace.ts +0 -183
  305. package/src/orchestration/models/types.ts +0 -292
  306. package/src/orchestration/models/workflow-run.ts +0 -254
  307. package/src/orchestration/models/workflow-script-registry.ts +0 -35
  308. package/src/orchestration/models/workflow-script.ts +0 -117
  309. package/src/orchestration/script-lint.ts +0 -766
  310. package/src/orchestration/skill-discovery.ts +0 -92
  311. package/src/orchestration/worker-handle.ts +0 -115
  312. package/src/orchestration/worker-host.ts +0 -98
  313. package/src/orchestration/worker-script-builder.ts +0 -408
  314. package/src/orchestration/workflow-files.ts +0 -85
  315. package/src/orchestration/workflow-script-registry-impl.ts +0 -133
  316. package/src/shared/__tests__/agent-ref.test.ts +0 -34
  317. package/src/shared/__tests__/meta-parser.test.ts +0 -304
  318. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -280
  319. package/src/shared/__tests__/resource-discovery.test.ts +0 -736
  320. package/src/shared/__tests__/resource-meta.test.ts +0 -51
  321. package/src/shared/__tests__/schema-jsonify.test.ts +0 -81
  322. package/src/shared/agent-event.ts +0 -13
  323. package/src/shared/agent-ref.ts +0 -57
  324. package/src/shared/meta-parser.ts +0 -261
  325. package/src/shared/resource-discovery.ts +0 -811
  326. package/src/shared/resource-meta.ts +0 -65
  327. package/src/shared/schema-jsonify.ts +0 -56
  328. package/src/shared/xml-injection.ts +0 -35
  329. package/workflows/README.md +0 -81
  330. package/workflows/_shared/agent-refs.cjs +0 -40
  331. package/workflows/chain.js +0 -131
  332. package/workflows/map-reduce.js +0 -174
  333. package/workflows/parallel.js +0 -148
  334. package/workflows/review-fix-loop-utils.cjs +0 -1307
  335. package/workflows/review-fix-loop.js +0 -1396
  336. package/workflows/scatter-gather.js +0 -178
@@ -1,458 +0,0 @@
1
- // src/__tests__/format.test.ts
2
- import { visibleWidth } from "@earendil-works/pi-tui";
3
- import { describe, expect, it } from "vitest";
4
-
5
- import {
6
- formatElapsedSeconds,
7
- formatTokens,
8
- padToVisible,
9
- sanitizeLabel,
10
- segFillColored,
11
- shortId,
12
- spinnerGlyph,
13
- statusGlyph,
14
- truncLine,
15
- wrapText,
16
- } from "../../interface/format.ts";
17
-
18
- // ============================================================
19
- // formatTokens
20
- // ============================================================
21
- describe("formatTokens", () => {
22
- it("shows plain value below 1000", () => {
23
- expect(formatTokens(0)).toBe("0");
24
- expect(formatTokens(820)).toBe("820");
25
- expect(formatTokens(999)).toBe("999");
26
- });
27
-
28
- it("shows N.Nk between 1000 and 9999", () => {
29
- expect(formatTokens(1000)).toBe("1.0k");
30
- expect(formatTokens(8200)).toBe("8.2k");
31
- expect(formatTokens(9999)).toBe("10.0k");
32
- });
33
-
34
- it("shows rounded Nk at 10000+", () => {
35
- expect(formatTokens(10000)).toBe("10k");
36
- expect(formatTokens(23000)).toBe("23k");
37
- expect(formatTokens(99999)).toBe("100k");
38
- });
39
- });
40
-
41
- // ============================================================
42
- // formatElapsedSeconds
43
- // ============================================================
44
- describe("formatElapsedSeconds", () => {
45
- it("shows Xs below 60", () => {
46
- expect(formatElapsedSeconds(0)).toBe("0s");
47
- expect(formatElapsedSeconds(12)).toBe("12s");
48
- expect(formatElapsedSeconds(59)).toBe("59s");
49
- });
50
-
51
- it("shows Xm Ys between 60 and 3599", () => {
52
- expect(formatElapsedSeconds(60)).toBe("1m0s");
53
- expect(formatElapsedSeconds(72)).toBe("1m12s");
54
- expect(formatElapsedSeconds(3599)).toBe("59m59s");
55
- });
56
-
57
- it("shows Xh Ym at 3600+", () => {
58
- expect(formatElapsedSeconds(3600)).toBe("1h0m");
59
- expect(formatElapsedSeconds(3661)).toBe("1h1m");
60
- expect(formatElapsedSeconds(7325)).toBe("2h2m");
61
- });
62
- });
63
-
64
- // ============================================================
65
- // statusGlyph
66
- // ============================================================
67
- describe("statusGlyph", () => {
68
- it("running → no icon, accent color", () => {
69
- expect(statusGlyph("running")).toEqual({ icon: undefined, color: "accent" });
70
- });
71
-
72
- it("done → checkmark, success", () => {
73
- expect(statusGlyph("closed")).toEqual({ icon: "✓", color: "success" });
74
- });
75
-
76
- it("closed → checkmark, success", () => {
77
- expect(statusGlyph("closed")).toEqual({ icon: "✓", color: "success" });
78
- });
79
- });
80
-
81
- // ============================================================
82
- // spinnerGlyph
83
- // ============================================================
84
- describe("spinnerGlyph", () => {
85
- it("returns a frame for valid seed", () => {
86
- expect(spinnerGlyph(0)).toBe("⠋");
87
- expect(spinnerGlyph(1)).toBe("⠙");
88
- expect(spinnerGlyph(9)).toBe("⠏");
89
- });
90
-
91
- it("wraps around (mod 10)", () => {
92
- expect(spinnerGlyph(10)).toBe("⠋");
93
- expect(spinnerGlyph(15)).toBe("⠴"); // index 5
94
- });
95
-
96
- it("falls back to frame 0 on NaN", () => {
97
- expect(spinnerGlyph(NaN)).toBe("⠋");
98
- });
99
-
100
- it("handles negative seeds via abs", () => {
101
- expect(spinnerGlyph(-1)).toBe("⠙"); // abs(-1) % 10 = 1 → ⠙
102
- expect(spinnerGlyph(-10)).toBe("⠋");
103
- });
104
-
105
- it("falls back to frame 0 on Infinity", () => {
106
- expect(spinnerGlyph(Infinity)).toBe("⠋");
107
- });
108
- });
109
-
110
- // ============================================================
111
- // sanitizeLabel
112
- // ============================================================
113
- describe("sanitizeLabel", () => {
114
- it("replaces CRLF/LF with single space", () => {
115
- expect(sanitizeLabel("line1\r\nline2\nline3")).toBe("line1 line2 line3");
116
- });
117
-
118
- it("replaces tabs with 2 spaces", () => {
119
- expect(sanitizeLabel("a\tb")).toBe("a b");
120
- });
121
-
122
- it("collapses multiple consecutive newlines into one space", () => {
123
- // /[\r\n]+/g treats \r\n\r\n as one match → single space
124
- expect(sanitizeLabel("a\r\n\r\nb")).toBe("a b");
125
- });
126
-
127
- it("leaves clean text unchanged", () => {
128
- expect(sanitizeLabel("read foo.ts")).toBe("read foo.ts");
129
- });
130
- });
131
-
132
- // ============================================================
133
- // padToVisible
134
- // ============================================================
135
- describe("padToVisible", () => {
136
- it("pads short text to width with trailing spaces", () => {
137
- expect(padToVisible("ab", 5)).toBe("ab ");
138
- });
139
-
140
- it("returns unchanged when already at width", () => {
141
- expect(padToVisible("hello", 5)).toBe("hello");
142
- });
143
-
144
- it("returns unchanged when wider than width", () => {
145
- expect(padToVisible("hello world", 5)).toBe("hello world");
146
- });
147
-
148
- it("handles CJK width (2 columns per char)", () => {
149
- // 你好 = 4 visible columns
150
- expect(padToVisible("你好", 6)).toBe("你好 ");
151
- });
152
- });
153
-
154
- // ============================================================
155
- // segFillColored
156
- // ============================================================
157
- describe("segFillColored", () => {
158
- it("returns empty string for width <= 0", () => {
159
- expect(segFillColored("title", "-", 0)).toBe("");
160
- expect(segFillColored("title", "-", -1)).toBe("");
161
- });
162
-
163
- it("pure fill when no title", () => {
164
- expect(segFillColored(undefined, "-", 5)).toBe("-----");
165
- });
166
-
167
- it("title + fill to width", () => {
168
- expect(segFillColored("Hi", "-", 5)).toBe("Hi---");
169
- });
170
-
171
- it("truncates title when wider than width", () => {
172
- const result = segFillColored("Hello World", "-", 5);
173
- // title visible width 11 > 5 → truncated to 5 (with ellipsis = 4 chars + …)
174
- expect(result.length).toBeLessThanOrEqual(10); // visible width 5 but may include ANSI
175
- });
176
-
177
- it("preserves ANSI in title and fill separately (no nesting color loss)", () => {
178
- const redTitle = "\x1b[31mHi\x1b[0m";
179
- const blueFill = "\x1b[34m-\x1b[0m";
180
- const result = segFillColored(redTitle, blueFill, 5);
181
- // title visible width = 2 ("Hi"), fill count = 3
182
- expect(result).toContain(redTitle);
183
- expect(result).toContain(blueFill);
184
- // fill repeated 3 times
185
- expect(result).toBe(redTitle + blueFill + blueFill + blueFill);
186
- });
187
- });
188
-
189
- // ============================================================
190
- // truncLine
191
- // ============================================================
192
- describe("truncLine", () => {
193
- it("returns text unchanged when within width", () => {
194
- expect(truncLine("hello", 10)).toBe("hello");
195
- expect(truncLine("hello", 5)).toBe("hello");
196
- });
197
-
198
- it("returns empty string for width <= 0", () => {
199
- expect(truncLine("hello", 0)).toBe("");
200
- });
201
-
202
- it("truncates with ellipsis when exceeding width", () => {
203
- const result = truncLine("hello world", 8);
204
- // 纯文本截断不发 \x1b[0m(全局重置会破坏外层背景色)
205
- expect(result.endsWith("…")).toBe(true);
206
- expect(result).not.toContain("\x1b[0m");
207
- // visible width should be 8 (7 chars + ellipsis)
208
- });
209
-
210
- it("handles CJK characters (2 columns each)", () => {
211
- // 你好世界 = 8 visible columns; truncate to 5 → 2 chars (4 cols) + …
212
- const result = truncLine("你好世界", 5);
213
- expect(result.endsWith("…")).toBe(true);
214
- expect(result).not.toContain("\x1b[0m");
215
- });
216
-
217
- it("handles emoji correctly", () => {
218
- const result = truncLine("😀😁😂🤣😃", 3);
219
- expect(result.endsWith("…")).toBe(true);
220
- expect(result).not.toContain("\x1b[0m");
221
- });
222
-
223
- it("reapplies active ANSI styles before ellipsis (no background break)", () => {
224
- // red text that exceeds width → ellipsis should have red re-applied
225
- const input = "\x1b[31mhello world this is long\x1b[0m";
226
- const result = truncLine(input, 10);
227
- expect(result.endsWith("…\x1b[0m")).toBe(true);
228
- // The ellipsis should be preceded by the active red style (re-applied)
229
- // Check that the last grapheme sequence includes the red SGR before …
230
- expect(result).toMatch(/\x1b\[31m…\x1b\[0m$/);
231
- });
232
-
233
- it("clears style stack on reset code", () => {
234
- // text with reset in the middle → after reset, no style re-applied
235
- const input = "\x1b[31mab\x1b[0mcdefghijk";
236
- const result = truncLine(input, 6);
237
- // reset 后 activeStyles 为空 → 截断不发 \x1b[0m
238
- expect(result.endsWith("…")).toBe(true);
239
- });
240
-
241
- it("flattens newlines to spaces (single-line rendering safety)", () => {
242
- // 多行 prompt / turn.text 含 \n,单行渲染时 \n 会意外换行破坏行对齐。
243
- // truncLine 作为单行渲染入口必须剥离 \n(用空格替代保留词边界)。
244
- const multiLine = "只读任务:分析核心逻辑。\n项目根:/Users/test\n输出:报告";
245
- const result = truncLine(multiLine, 80);
246
- expect(result).not.toContain("\n");
247
- expect(result).toContain("只读任务");
248
- expect(result).toContain("项目根");
249
- });
250
-
251
- it("flattens \r\n sequences", () => {
252
- const crlf = "line1\r\nline2\r\nline3";
253
- const result = truncLine(crlf, 80);
254
- expect(result).not.toContain("\r");
255
- expect(result).not.toContain("\n");
256
- });
257
- });
258
-
259
- // ============================================================
260
- // truncLine — legacy 对拍(indexOf 化重构的逐字节等价锚定)
261
- // ============================================================
262
-
263
- /**
264
- * 旧实现参照:改造前的 per-char 版本,逐字取自 git 历史(093e28fe3~1 的
265
- * format.ts truncLine)。现行实现(indexOf("\\x1b") 段跳过)必须与其逐字节
266
- * 等价——本函数即对拍 oracle,与 helpers-bounded-serialize.test.ts 的
267
- * legacySerialize 同形态(参照物随测试落盘,防「验证只存在于改造当时」)。
268
- */
269
- const legacySegmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
270
-
271
- function legacyTruncLine(text: string, maxWidth: number): string {
272
- if (maxWidth <= 0) return "";
273
- const flat = text.replace(/[\r\n]+/g, " ");
274
- if (visibleWidth(flat) <= maxWidth) return flat;
275
-
276
- const targetWidth = Math.max(0, maxWidth - 1);
277
- let result = "";
278
- let currentWidth = 0;
279
- let activeStyles: string[] = [];
280
- let i = 0;
281
-
282
- while (i < flat.length) {
283
- // 捕获 ANSI SGR 序列
284
- const ansiMatch = flat.slice(i).match(/^\x1b\[[0-9;]*m/);
285
- if (ansiMatch) {
286
- const code = ansiMatch[0];
287
- result += code;
288
-
289
- if (code === "\x1b[0m" || code === "\x1b[m") {
290
- activeStyles = []; // reset → 清空栈
291
- } else {
292
- activeStyles.push(code);
293
- }
294
- i += code.length;
295
- continue;
296
- }
297
-
298
- // 找到下一段纯文本(非 ANSI)的边界
299
- let end = i;
300
- while (end < flat.length && !flat.slice(end).match(/^\x1b\[[0-9;]*m/)) {
301
- end++;
302
- }
303
-
304
- // 按 grapheme 迭代这段文本,累加到 targetWidth
305
- const textPortion = flat.slice(i, end);
306
- for (const seg of legacySegmenter.segment(textPortion)) {
307
- const grapheme = seg.segment;
308
- const graphemeWidth = visibleWidth(grapheme);
309
-
310
- if (currentWidth + graphemeWidth > targetWidth) {
311
- return result + activeStyles.join("") + "…" + (activeStyles.length ? "\x1b[0m" : "");
312
- }
313
-
314
- result += grapheme;
315
- currentWidth += graphemeWidth;
316
- }
317
- i = end;
318
- }
319
-
320
- // 理论上 visibleWidth 检查已提前返回,此行兜底
321
- return result + activeStyles.join("") + "…" + (activeStyles.length ? "\x1b[0m" : "");
322
- }
323
-
324
- /** 确定性 PRNG(mulberry32):fuzz 用例固定 seed 生成,失败可按 case 编号 + seed 复现。 */
325
- function mulberry32(seed: number): () => number {
326
- let s = seed | 0;
327
- return () => {
328
- s = (s + 0x6d2b79f5) | 0;
329
- let t = Math.imul(s ^ (s >>> 15), 1 | s);
330
- t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
331
- return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
332
- };
333
- }
334
-
335
- /** 定向混排片段池:ASCII / CJK / emoji(含肤色修饰与 ZWJ 组合)/ SGR / 非 SGR ESC(OSC、CSI-K、裸 ESC)/ 换行。 */
336
- const FUZZ_PIECES = [
337
- "plain", " ", "a", "0", "MiXeD-Case_123",
338
- "你好世界", "汉字宽度测试", "日本語テキスト",
339
- "😀", "😁😂🤣", "👍🏽", "👨‍👩‍👧‍👦", "é", "é",
340
- "\x1b[31m", "\x1b[0m", "\x1b[m", "\x1b[1;32m", "\x1b[38;5;196m",
341
- "\x1b[K", "\x1b]0;title\x07", "\x1bZ", "\x1b",
342
- "\n", "\r\n",
343
- ];
344
-
345
- function genFuzzText(rand: () => number): string {
346
- const pieceCount = Math.floor(rand() * 40);
347
- let out = "";
348
- for (let p = 0; p < pieceCount; p++) {
349
- out += FUZZ_PIECES[Math.floor(rand() * FUZZ_PIECES.length)];
350
- }
351
- return out;
352
- }
353
-
354
- describe("truncLine — legacy 对拍(per-char 参照逐字节等价)", () => {
355
- it("定向 fuzz:300 随机 ESC/SGR/CJK/emoji 混排用例(固定 seed 可复现)与旧实现逐字节一致", () => {
356
- const rand = mulberry32(20260815);
357
- let truncated = 0;
358
- for (let i = 0; i < 300; i++) {
359
- const text = genFuzzText(rand);
360
- const maxWidth = Math.floor(rand() * 61); // 0..60,含 0/1 边界
361
- const actual = truncLine(text, maxWidth);
362
- expect(
363
- actual,
364
- `case #${i} text=${JSON.stringify(text)} maxWidth=${maxWidth}`,
365
- ).toBe(legacyTruncLine(text, maxWidth));
366
- if (actual.endsWith("…")) truncated++;
367
- }
368
- // 前置有效性:fuzz 确实命中了截断路径(否则对拍只测了「宽度内原样返回」早退分支)
369
- expect(truncated).toBeGreaterThan(100);
370
- });
371
-
372
- it("手工对抗用例:SGR 重叠 / OSC 内截断 / CSI-K 混排 / 串尾裸 ESC", () => {
373
- const cases: Array<[string, number]> = [
374
- ["\x1b[31m红\x1b[32m绿\x1b[1;4m加粗下划线尾巴超宽截断点", 9],
375
- ["AB\x1b]0;title\x07CDEFGHIJKLMNOP", 10],
376
- ["\x1b[31mred\x1b[K\x1b[32mgreen tail past width", 10],
377
- ["text almost at width limit\x1b", 22],
378
- ["before \x1bZ after bare esc tail", 12],
379
- ["\x1b[m你\x1b[0m好\x1b[38;5;196m世👨‍👩‍👧‍👦界".repeat(3), 14],
380
- ];
381
- for (const [text, maxWidth] of cases) {
382
- expect(truncLine(text, maxWidth)).toBe(legacyTruncLine(text, maxWidth));
383
- }
384
- });
385
- });
386
-
387
- // ============================================================
388
- // wrapText
389
- // ============================================================
390
- describe("wrapText", () => {
391
- it("returns text as-is when shorter than width", () => {
392
- expect(wrapText("hello", 80)).toEqual(["hello"]);
393
- });
394
-
395
- it("wraps long text into multiple lines (no truncation)", () => {
396
- const text = "abcdefghij"; // 10 chars
397
- const lines = wrapText(text, 4);
398
- // 每行最多 4 列,10 字符 → 3 行(4+4+2),不截断不省略号
399
- expect(lines.join("")).toBe("abcdefghij");
400
- for (const l of lines.slice(0, -1)) {
401
- expect(visibleWidth(l)).toBeLessThanOrEqual(4);
402
- }
403
- });
404
-
405
- it("preserves original newlines as paragraph breaks", () => {
406
- const text = "第一行\n第二行";
407
- const lines = wrapText(text, 80);
408
- expect(lines).toEqual(["第一行", "第二行"]);
409
- });
410
-
411
- it("wraps CJK text correctly (2 columns each)", () => {
412
- // 你好世界你好世界 = 8 CJK chars = 16 columns, wrap to 4 columns
413
- const text = "你好世界你好世界";
414
- const lines = wrapText(text, 4);
415
- // 每行最多 4 列 = 2 CJK chars, 共 4 行
416
- expect(lines).toHaveLength(4);
417
- expect(lines.join("")).toBe(text);
418
- });
419
-
420
- it("handles width <= 0 by returning original", () => {
421
- expect(wrapText("hello", 0)).toEqual(["hello"]);
422
- });
423
-
424
- it("preserves empty lines from blank paragraphs", () => {
425
- const text = "a\n\nb";
426
- expect(wrapText(text, 80)).toEqual(["a", "", "b"]);
427
- });
428
- });
429
-
430
- // ============================================================
431
- // shortId
432
- // ============================================================
433
- describe("shortId", () => {
434
- it("returns sync id unchanged (run-N already short)", () => {
435
- expect(shortId("run-1")).toBe("run-1");
436
- expect(shortId("run-42")).toBe("run-42");
437
- });
438
-
439
- it("strips timestamp from background id (bg-tag-seq-<ts> → bg-tag-seq)", () => {
440
- // 算法回归:多段 id 取前 3 段(SHORT_ID_BG_SEGMENTS)。
441
- // 注意:当前 subagent ID 已改为 sa-<uuid>(见下面 sa- 用例),workflow ID 为 wf-<ts>-<rand>,
442
- // 实际不再产生 bg- 形态 id;此处保留作为 shortId 算法的多段降级回归(4 段 → 3 段)。
443
- expect(shortId("bg-f6f731-10-1719500000000")).toBe("bg-f6f731-10");
444
- expect(shortId("bg-abc123-99-1719500123456")).toBe("bg-abc123-99");
445
- });
446
-
447
- it("handles pure uuid and wf- runId (regression baseline)", () => {
448
- // 纯 UUID 回归(5段 → 取前3段)
449
- expect(shortId("550e8400-e29b-41d4-a716-446655440000")).toBe("550e8400-e29b-41d4");
450
- // wf- 前缀 runId 回归(3段 → 取前3段=原样)
451
- expect(shortId("wf-1719500000000-a1b2c3")).toBe("wf-1719500000000-a1b2c3");
452
- });
453
-
454
- it("keeps sa- prefix for subagent id (sa-<uuid> → sa-<uuid 前3段>)", () => {
455
- // sa- 前缀 subagent ID(保留前缀 + UUID 前 3 段)
456
- expect(shortId("sa-550e8400-e29b-41d4-a716-446655440000")).toBe("sa-550e8400-e29b-41d4");
457
- });
458
- });
@@ -1,184 +0,0 @@
1
- // src/execution/__tests__/gc-timer.test.ts
2
- //
3
- // [M8] idle record GC 定时器测试(subagent-service.ts startGcTimer,L485-507)。
4
- //
5
- // 背景:GC 定时器(1h interval 扫描 + isResumable && idleSince 30 天 TTL + store.archive)
6
- // 此前零测试覆盖——所有相关测试把 startGcTimer mock 成 no-op(index-session-start /
7
- // crash-recovery / stream-sink-guard / session-start-reaper / index-session-start-identity)。
8
- // 误判 isResumable/idleSince 会把可续聊 record 从内存错误归档(用户数据路径),
9
- // 或永不触发(内存驻留失效)——两个方向都无回归拦截。
10
- //
11
- // 本文件用真实 SubagentService + 真实 startGcTimer(不 mock GC 逻辑本身),仅 mock
12
- // session-runner(import 链依赖 + isResumable 的 hasLiveProcessHandle 查询点)与 logger。
13
- // timer 全部 vi.useFakeTimers,不真实等待。
14
-
15
- import type { ChildProcess } from "node:child_process";
16
- import * as fs from "node:fs";
17
- import * as os from "node:os";
18
- import * as path from "node:path";
19
-
20
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
21
-
22
- const { loggerMock } = vi.hoisted(() => ({
23
- loggerMock: { debug: vi.fn(), warn: vi.fn(), error: vi.fn(), info: vi.fn() },
24
- }));
25
- vi.mock("@zhushanwen/pi-extension-logger", () => ({ getLogger: () => loggerMock }));
26
-
27
- // mock session-runner:runSpawn/killAllSpawnedChildren 占位(import 链需要),
28
- // getChildByRecord 默认返回 undefined(无活进程 → isResumable=true),用例内按需覆盖。
29
- vi.mock("../session-runner.ts", () => ({
30
- runSpawn: vi.fn(),
31
- killAllSpawnedChildren: vi.fn(),
32
- getChildByRecord: vi.fn(() => undefined),
33
- spawnedChildren: new Map(),
34
- }));
35
-
36
- import { getChildByRecord } from "../session-runner.ts";
37
- import { createRecord } from "../execution-record.ts";
38
- import { ModelConfigService } from "../model-config-service.ts";
39
- import { RecordStore } from "../record-store.ts";
40
- import { SubagentService } from "../subagent-service.ts";
41
- import type { ExecutionRecord } from "../types.ts";
42
-
43
- const mockGetChildByRecord = vi.mocked(getChildByRecord);
44
-
45
- /** 与 startGcTimer 内部常量一致(1h 扫描 / 30 天 TTL)。 */
46
- const GC_INTERVAL_MS = 60 * 60 * 1000;
47
- const IDLE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
48
-
49
- /** initSession 注入的最小 pi duck-type(同 subagent-service PiLike 形状,结构匹配即可)。 */
50
- interface PiStub {
51
- appendEntry(customType: string, data?: unknown): void;
52
- events: { emit(channel: string, data: unknown): void };
53
- sendMessage(
54
- message: { customType: string; content: string; display: boolean; details?: unknown },
55
- options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" },
56
- ): void;
57
- }
58
-
59
- function makePi(): PiStub {
60
- return {
61
- appendEntry: vi.fn(),
62
- events: { emit: vi.fn() },
63
- sendMessage: vi.fn(),
64
- };
65
- }
66
-
67
- /** 构造 running + 无活进程 + 指定 idleSince 的 record(GC 扫描的目标态)。
68
- * listAllActive 只返回 status==="running"(createRecord 初始即 running)。 */
69
- function makeIdleRecord(id: string, idleSince: number): ExecutionRecord {
70
- const record = createRecord(id, {
71
- agent: "general-purpose",
72
- model: "test/model",
73
- mode: "background",
74
- task: "test",
75
- slug: "test",
76
- startedAt: idleSince,
77
- rootSessionId: "root-session",
78
- chatMode: true,
79
- controller: new AbortController(),
80
- });
81
- record.idleSince = idleSince;
82
- return record;
83
- }
84
-
85
- /** 暴露私有字段供测试读取 store。 */
86
- interface ServiceInternals {
87
- store: RecordStore;
88
- }
89
-
90
- describe("[M8] idle record GC 定时器(startGcTimer)", () => {
91
- let agentDir: string;
92
- let service: SubagentService;
93
- let store: RecordStore;
94
-
95
- beforeEach(() => {
96
- vi.useFakeTimers();
97
- agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "gc-timer-"));
98
- const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
99
- service = new SubagentService({ cwd: agentDir, modelService });
100
- service.initSession({ pi: makePi(), sessionId: "root-session" });
101
- store = (service as unknown as ServiceInternals).store;
102
- mockGetChildByRecord.mockReset();
103
- // 默认无活进程(isResumable=true)——Path B:进程已回收、可冷路径 resume 的等待续聊态
104
- mockGetChildByRecord.mockImplementation(() => undefined);
105
- loggerMock.warn.mockClear();
106
- });
107
-
108
- afterEach(() => {
109
- service.dispose();
110
- fs.rmSync(agentDir, { recursive: true, force: true });
111
- vi.useRealTimers();
112
- });
113
-
114
- it("TTL 边界:超 1ms 归档、差 1ms 不归档(同一轮扫描)", () => {
115
- // idleSince 相对 interval 首次触发时刻(fake clock + 1h)构造:
116
- // over = fireAt - TTL - 1 → 扫描时 age = TTL + 1 > TTL → archive
117
- // under = fireAt - TTL + 1 → 扫描时 age = TTL - 1 ≤ TTL → 留内存
118
- const fireAt = Date.now() + GC_INTERVAL_MS;
119
- store.register(makeIdleRecord("sa-over", fireAt - IDLE_TTL_MS - 1));
120
- store.register(makeIdleRecord("sa-under", fireAt - IDLE_TTL_MS + 1));
121
-
122
- service.startGcTimer();
123
- vi.advanceTimersByTime(GC_INTERVAL_MS);
124
-
125
- expect(store.getMutable("sa-over")).toBeUndefined(); // archive 从内存移除
126
- expect(store.getMutable("sa-under")).toBeDefined(); // 未超 TTL 留内存
127
- expect(loggerMock.warn).toHaveBeenCalledTimes(1);
128
- expect(loggerMock.warn).toHaveBeenCalledWith(
129
- expect.stringContaining("GC: archiving idle record sa-over"),
130
- );
131
- });
132
-
133
- it("有活进程的 record 不归档(isResumable=false,即使 idleSince 超 TTL)", () => {
134
- const fireAt = Date.now() + GC_INTERVAL_MS;
135
- // Path A:进程保活等待续聊(idle timer armed、child 未 kill)——归档会让活进程失管
136
- store.register(makeIdleRecord("sa-live", fireAt - IDLE_TTL_MS - 1));
137
- mockGetChildByRecord.mockImplementation(
138
- (id: string): ChildProcess | undefined =>
139
- id === "sa-live" ? ({ killed: false } as unknown as ChildProcess) : undefined,
140
- );
141
-
142
- service.startGcTimer();
143
- vi.advanceTimersByTime(GC_INTERVAL_MS);
144
-
145
- expect(store.getMutable("sa-live")).toBeDefined(); // 有活进程 → 不归档
146
- expect(loggerMock.warn).not.toHaveBeenCalled();
147
- });
148
-
149
- it("每小时重复扫描:上一轮差 1ms 的 record 下一轮超龄被归档", () => {
150
- const fireAt = Date.now() + GC_INTERVAL_MS;
151
- store.register(makeIdleRecord("sa-late", fireAt - IDLE_TTL_MS + 1));
152
-
153
- service.startGcTimer();
154
- vi.advanceTimersByTime(GC_INTERVAL_MS);
155
- expect(store.getMutable("sa-late")).toBeDefined(); // 第一轮:age = TTL - 1,未到
156
-
157
- vi.advanceTimersByTime(GC_INTERVAL_MS); // 第二轮:age = TTL + 1h - 1 > TTL
158
- expect(store.getMutable("sa-late")).toBeUndefined();
159
- });
160
-
161
- it("startGcTimer 幂等:重复调用不产生第二个 interval", () => {
162
- const fireAt = Date.now() + GC_INTERVAL_MS;
163
- store.register(makeIdleRecord("sa-over", fireAt - IDLE_TTL_MS - 1));
164
-
165
- service.startGcTimer();
166
- service.startGcTimer(); // 已有 timer 直接 return(防重复 interval)
167
- vi.advanceTimersByTime(GC_INTERVAL_MS);
168
-
169
- // 同一 record 一轮只扫一次(重复 interval 会对同一 record 产生第二条 GC warn)
170
- expect(loggerMock.warn).toHaveBeenCalledTimes(1);
171
- });
172
-
173
- it("dispose 后 interval 停止:超 TTL record 不再被归档", () => {
174
- service.startGcTimer();
175
- vi.advanceTimersByTime(GC_INTERVAL_MS); // 第一轮正常触发
176
- service.dispose(); // stopGcTimer + disposeAllRecords
177
-
178
- // dispose 后塞入超 TTL record,interval 已停 → 无论推进多久都不归档
179
- store.register(makeIdleRecord("sa-after-dispose", Date.now() - IDLE_TTL_MS - 1));
180
- vi.advanceTimersByTime(GC_INTERVAL_MS * 10);
181
-
182
- expect(store.getMutable("sa-after-dispose")).toBeDefined();
183
- });
184
- });