@gotgenes/pi-subagents 18.0.1 → 18.0.2

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 (260) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/docs/architecture/architecture.md +157 -204
  3. package/docs/architecture/history/phase-19-implement-ui-decisions.md +282 -0
  4. package/package.json +5 -6
  5. package/src/lifecycle/subagent.ts +5 -9
  6. package/src/observation/notification.ts +23 -10
  7. package/src/observation/subagent-events-observer.ts +1 -3
  8. package/src/tools/get-result-report.ts +60 -0
  9. package/src/tools/get-result-tool.ts +25 -40
  10. package/src/types.ts +1 -1
  11. package/src/ui/session-navigator.ts +0 -2
  12. package/.prettierignore +0 -5
  13. package/AGENTS.md +0 -7
  14. package/docs/plans/0048-implement-subagents-api.md +0 -304
  15. package/docs/plans/0049-remove-group-join-output-file-rpc.md +0 -180
  16. package/docs/plans/0051-update-adr-0001-hard-fork.md +0 -77
  17. package/docs/plans/0052-remove-scheduled-subagents.md +0 -133
  18. package/docs/plans/0053-extract-model-resolution-from-execute.md +0 -181
  19. package/docs/plans/0054-decompose-index-into-modules.md +0 -302
  20. package/docs/plans/0057-structured-debug-logging.md +0 -154
  21. package/docs/plans/0061-session-format-transcript.md +0 -284
  22. package/docs/plans/0066-replace-as-any-with-sdk-types.md +0 -215
  23. package/docs/plans/0069-create-subagent-runtime.md +0 -345
  24. package/docs/plans/0070-extract-event-handlers.md +0 -306
  25. package/docs/plans/0071-extract-session-config-assembler.md +0 -362
  26. package/docs/plans/0072-inject-agent-manager-collaborators.md +0 -329
  27. package/docs/plans/0076-inject-cwd-into-agent-manager.md +0 -102
  28. package/docs/plans/0077-inject-project-agents-dir.md +0 -115
  29. package/docs/plans/0080-consolidate-agent-config-lookup.md +0 -247
  30. package/docs/plans/0084-extract-git-worktree-manager.md +0 -142
  31. package/docs/plans/0087-evolve-subagent-runtime-methods.md +0 -240
  32. package/docs/plans/0098-extract-agent-record-state-machine.md +0 -435
  33. package/docs/plans/0099-replace-ctx-with-parent-snapshot.md +0 -488
  34. package/docs/plans/0100-replace-callback-threading-with-session-subscription.md +0 -454
  35. package/docs/plans/0102-consolidate-test-record-factory.md +0 -176
  36. package/docs/plans/0108-extract-agent-type-registry.md +0 -322
  37. package/docs/plans/0109-extract-settings-manager.md +0 -276
  38. package/docs/plans/0110-agent-activity-tracker.md +0 -297
  39. package/docs/plans/0111-split-agent-record-lifecycle.md +0 -582
  40. package/docs/plans/0112-replace-agent-manager-callbacks.md +0 -241
  41. package/docs/plans/0113-disambiguate-spawn-options.md +0 -155
  42. package/docs/plans/0114-narrow-agent-tool-menu-deps.md +0 -279
  43. package/docs/plans/0115-decompose-agent-tool.md +0 -337
  44. package/docs/plans/0116-type-housekeeping.md +0 -351
  45. package/docs/plans/0118-settings-manager-apply-methods.md +0 -271
  46. package/docs/plans/0123-remove-vi-fn-cast-smell.md +0 -179
  47. package/docs/plans/0131-consolidate-shared-test-fixtures.md +0 -207
  48. package/docs/plans/0132-inject-io-into-session-config.md +0 -219
  49. package/docs/plans/0133-inject-sdk-boundary-into-agent-runner.md +0 -373
  50. package/docs/plans/0134-reduce-as-any-casts.md +0 -366
  51. package/docs/plans/0135-extract-display-helpers.md +0 -182
  52. package/docs/plans/0136-decompose-agent-menu.md +0 -300
  53. package/docs/plans/0144-consolidate-observation-model.md +0 -263
  54. package/docs/plans/0145-decompose-execute-push-ctx-to-boundary.md +0 -290
  55. package/docs/plans/0146-narrow-ui-context.md +0 -319
  56. package/docs/plans/0147-inject-wrap-text-into-conversation-viewer.md +0 -166
  57. package/docs/plans/0148-split-agent-widget-rendering.md +0 -255
  58. package/docs/plans/0152-add-prompt-snippet.md +0 -91
  59. package/docs/plans/0164-reorganize-into-domain-directories.md +0 -409
  60. package/docs/plans/0165-decompose-resolved-spawn-config.md +0 -157
  61. package/docs/plans/0166-extract-parent-session-info.md +0 -231
  62. package/docs/plans/0167-narrow-runner-io.md +0 -150
  63. package/docs/plans/0168-extract-tool-filter-config.md +0 -173
  64. package/docs/plans/0169-extract-run-context-from-run-options.md +0 -194
  65. package/docs/plans/0170-reduce-build-content-lines-complexity.md +0 -225
  66. package/docs/plans/0171-reduce-render-result-complexity.md +0 -200
  67. package/docs/plans/0172-extract-turn-formatting.md +0 -206
  68. package/docs/plans/0180-reorder-append-prompt-for-kv-cache.md +0 -100
  69. package/docs/plans/0185-remove-persistent-agent-memory.md +0 -161
  70. package/docs/plans/0188-replace-any-casts-with-sdk-types.md +0 -162
  71. package/docs/plans/0192-define-session-context-interface.md +0 -107
  72. package/docs/plans/0193-runtime-owns-context-queries.md +0 -245
  73. package/docs/plans/0194-align-tool-interfaces-for-structural-typing.md +0 -139
  74. package/docs/plans/0195-convert-tool-factories-to-classes.md +0 -264
  75. package/docs/plans/0196-convert-runner-menu-to-classes.md +0 -268
  76. package/docs/plans/0205-decompose-render-widget-lines.md +0 -140
  77. package/docs/plans/0206-decompose-show-agent-detail.md +0 -211
  78. package/docs/plans/0207-decompose-agent-widget-update.md +0 -242
  79. package/docs/plans/0208-extract-shared-test-fixtures.md +0 -298
  80. package/docs/plans/0214-convert-remaining-closure-factories-to-classes.md +0 -261
  81. package/docs/plans/0215-decompose-build-parent-context.md +0 -166
  82. package/docs/plans/0216-decompose-start-agent.md +0 -255
  83. package/docs/plans/0217-extract-overwrite-guard.md +0 -176
  84. package/docs/plans/0218-push-sdk-boundary-in-settings.md +0 -172
  85. package/docs/plans/0219-reduce-test-duplication-top-3-clone-families.md +0 -162
  86. package/docs/plans/0227-evolve-agent-record-into-agent.md +0 -322
  87. package/docs/plans/0228-async-start-agent-dissolve-run-handle.md +0 -288
  88. package/docs/plans/0229-agent-born-complete.md +0 -564
  89. package/docs/plans/0230-extract-concurrency-queue.md +0 -265
  90. package/docs/plans/0231-push-exec-registry-to-runner.md +0 -245
  91. package/docs/plans/0232-agent-resume-internal-observer-lifecycle.md +0 -180
  92. package/docs/plans/0237-remove-disallowed-tools.md +0 -180
  93. package/docs/plans/0238-remove-extensions-filtering.md +0 -191
  94. package/docs/plans/0239-collapse-filter-active-tools.md +0 -217
  95. package/docs/plans/0242-rename-agent-tool-to-subagent.md +0 -165
  96. package/docs/plans/0256-extract-worktree-isolation.md +0 -256
  97. package/docs/plans/0257-extract-child-session-factory.md +0 -285
  98. package/docs/plans/0262-add-workspace-provider-seam.md +0 -264
  99. package/docs/plans/0264-remove-extension-lifecycle-control.md +0 -277
  100. package/docs/plans/0265-born-complete-subagent-session.md +0 -333
  101. package/docs/plans/0270-type-consumable-public-surface.md +0 -204
  102. package/docs/plans/0272-export-workspace-collaborator-types.md +0 -147
  103. package/docs/plans/0277-encapsulate-agent-session.md +0 -304
  104. package/docs/plans/0280-rename-agent-to-subagent.md +0 -197
  105. package/docs/plans/0360-custom-agents-default-append.md +0 -132
  106. package/docs/plans/0373-extract-subagent-state.md +0 -250
  107. package/docs/plans/0374-encapsulate-subagent-start-notification.md +0 -268
  108. package/docs/plans/0375-extract-run-listener-workspace-bracket.md +0 -300
  109. package/docs/plans/0376-extract-manager-observer.md +0 -232
  110. package/docs/plans/0377-split-widget-delegation-from-runtime.md +0 -254
  111. package/docs/plans/0378-consolidate-lifecycle-test-fixtures.md +0 -243
  112. package/docs/plans/0379-consolidate-ui-tools-test-fixtures.md +0 -335
  113. package/docs/plans/0380-extract-layered-settings-loader.md +0 -223
  114. package/docs/plans/0381-replace-concurrency-queue-with-limiter.md +0 -267
  115. package/docs/plans/0400-include-parent-prompt-in-replace-mode.md +0 -199
  116. package/docs/plans/0403-abort-subagents-on-interrupt.md +0 -180
  117. package/docs/plans/0412-unify-session-mock-builders.md +0 -146
  118. package/docs/plans/0420-fold-run-metrics-live-activity-onto-record.md +0 -228
  119. package/docs/plans/0421-migrate-activity-readers-to-record-getters.md +0 -309
  120. package/docs/plans/0422-delete-activity-tracker-ui-observer.md +0 -204
  121. package/docs/plans/0423-widget-self-drive-from-lifecycle.md +0 -264
  122. package/docs/plans/0424-drop-widget-dep-from-subagent-tool.md +0 -166
  123. package/docs/plans/0425-reconcile-subagent-events-contract.md +0 -144
  124. package/docs/plans/0426-consolidate-residual-test-clone-families.md +0 -235
  125. package/docs/plans/0427-reconsider-ui-direction.md +0 -212
  126. package/docs/plans/0441-remove-agent-definition-management-subtree.md +0 -180
  127. package/docs/plans/0442-dissolve-agents-conversation-viewer.md +0 -195
  128. package/docs/plans/0443-consolidate-remaining-test-clone-families.md +0 -268
  129. package/docs/plans/0444-shrink-widget-to-background-agents.md +0 -156
  130. package/docs/plans/0445-native-session-navigation.md +0 -365
  131. package/docs/plans/0446-spike-session-navigation-entry-criteria.md +0 -171
  132. package/docs/plans/0447-extract-subagents-settings-command.md +0 -220
  133. package/docs/plans/0448-block-disabled-agent-spawn.md +0 -207
  134. package/docs/plans/0462-navigation-renderer-tui-components.md +0 -338
  135. package/docs/plans/0463-file-snapshot-source-evicted-agents.md +0 -306
  136. package/docs/plans/0470-refresh-readme-command-surface.md +0 -159
  137. package/docs/retro/0048-implement-subagents-api.md +0 -44
  138. package/docs/retro/0049-remove-group-join-output-file-rpc.md +0 -49
  139. package/docs/retro/0051-update-adr-0001-hard-fork.md +0 -39
  140. package/docs/retro/0053-extract-model-resolution-from-execute.md +0 -40
  141. package/docs/retro/0054-decompose-index-into-modules.md +0 -53
  142. package/docs/retro/0057-structured-debug-logging.md +0 -77
  143. package/docs/retro/0061-session-format-transcript.md +0 -41
  144. package/docs/retro/0066-replace-as-any-with-sdk-types.md +0 -44
  145. package/docs/retro/0069-create-subagent-runtime.md +0 -43
  146. package/docs/retro/0070-extract-event-handlers.md +0 -49
  147. package/docs/retro/0071-extract-session-config-assembler.md +0 -60
  148. package/docs/retro/0072-inject-agent-manager-collaborators.md +0 -46
  149. package/docs/retro/0076-inject-cwd-into-agent-manager.md +0 -35
  150. package/docs/retro/0077-inject-project-agents-dir.md +0 -33
  151. package/docs/retro/0080-consolidate-agent-config-lookup.md +0 -35
  152. package/docs/retro/0084-extract-git-worktree-manager.md +0 -37
  153. package/docs/retro/0087-evolve-subagent-runtime-methods.md +0 -42
  154. package/docs/retro/0098-extract-agent-record-state-machine.md +0 -46
  155. package/docs/retro/0099-replace-ctx-with-parent-snapshot.md +0 -37
  156. package/docs/retro/0100-replace-callback-threading-with-session-subscription.md +0 -36
  157. package/docs/retro/0102-consolidate-test-record-factory.md +0 -30
  158. package/docs/retro/0108-extract-agent-type-registry.md +0 -41
  159. package/docs/retro/0109-extract-settings-manager.md +0 -55
  160. package/docs/retro/0110-agent-activity-tracker.md +0 -44
  161. package/docs/retro/0111-split-agent-record-lifecycle.md +0 -61
  162. package/docs/retro/0112-replace-agent-manager-callbacks.md +0 -35
  163. package/docs/retro/0113-disambiguate-spawn-options.md +0 -29
  164. package/docs/retro/0114-narrow-agent-tool-menu-deps.md +0 -38
  165. package/docs/retro/0115-decompose-agent-tool.md +0 -51
  166. package/docs/retro/0116-type-housekeeping.md +0 -42
  167. package/docs/retro/0118-settings-manager-apply-methods.md +0 -40
  168. package/docs/retro/0123-remove-vi-fn-cast-smell.md +0 -49
  169. package/docs/retro/0131-consolidate-shared-test-fixtures.md +0 -46
  170. package/docs/retro/0132-inject-io-into-session-config.md +0 -33
  171. package/docs/retro/0133-inject-sdk-boundary-into-agent-runner.md +0 -45
  172. package/docs/retro/0134-reduce-as-any-casts.md +0 -56
  173. package/docs/retro/0135-extract-display-helpers.md +0 -38
  174. package/docs/retro/0136-decompose-agent-menu.md +0 -43
  175. package/docs/retro/0144-consolidate-observation-model.md +0 -39
  176. package/docs/retro/0145-decompose-execute-push-ctx-to-boundary.md +0 -56
  177. package/docs/retro/0146-narrow-ui-context.md +0 -70
  178. package/docs/retro/0147-inject-wrap-text-into-conversation-viewer.md +0 -90
  179. package/docs/retro/0148-split-agent-widget-rendering.md +0 -39
  180. package/docs/retro/0152-add-prompt-snippet.md +0 -34
  181. package/docs/retro/0164-reorganize-into-domain-directories.md +0 -82
  182. package/docs/retro/0165-decompose-resolved-spawn-config.md +0 -78
  183. package/docs/retro/0166-extract-parent-session-info.md +0 -66
  184. package/docs/retro/0167-narrow-runner-io.md +0 -63
  185. package/docs/retro/0168-extract-tool-filter-config.md +0 -76
  186. package/docs/retro/0169-extract-run-context-from-run-options.md +0 -68
  187. package/docs/retro/0170-reduce-build-content-lines-complexity.md +0 -72
  188. package/docs/retro/0171-reduce-render-result-complexity.md +0 -77
  189. package/docs/retro/0172-extract-turn-formatting.md +0 -80
  190. package/docs/retro/0180-reorder-append-prompt-for-kv-cache.md +0 -62
  191. package/docs/retro/0185-remove-persistent-agent-memory.md +0 -73
  192. package/docs/retro/0188-replace-any-casts-with-sdk-types.md +0 -69
  193. package/docs/retro/0192-define-session-context-interface.md +0 -59
  194. package/docs/retro/0193-runtime-owns-context-queries.md +0 -75
  195. package/docs/retro/0194-align-tool-interfaces-for-structural-typing.md +0 -62
  196. package/docs/retro/0195-convert-tool-factories-to-classes.md +0 -84
  197. package/docs/retro/0196-convert-runner-menu-to-classes.md +0 -73
  198. package/docs/retro/0205-decompose-render-widget-lines.md +0 -64
  199. package/docs/retro/0206-decompose-show-agent-detail.md +0 -67
  200. package/docs/retro/0207-decompose-agent-widget-update.md +0 -92
  201. package/docs/retro/0208-extract-shared-test-fixtures.md +0 -102
  202. package/docs/retro/0214-convert-remaining-closure-factories-to-classes.md +0 -66
  203. package/docs/retro/0215-decompose-build-parent-context.md +0 -61
  204. package/docs/retro/0216-decompose-start-agent.md +0 -80
  205. package/docs/retro/0217-extract-overwrite-guard.md +0 -63
  206. package/docs/retro/0218-push-sdk-boundary-in-settings.md +0 -72
  207. package/docs/retro/0219-reduce-test-duplication-top-3-clone-families.md +0 -64
  208. package/docs/retro/0227-evolve-agent-record-into-agent.md +0 -80
  209. package/docs/retro/0228-async-start-agent-dissolve-run-handle.md +0 -80
  210. package/docs/retro/0229-agent-born-complete.md +0 -89
  211. package/docs/retro/0230-extract-concurrency-queue.md +0 -38
  212. package/docs/retro/0231-push-exec-registry-to-runner.md +0 -71
  213. package/docs/retro/0232-agent-resume-internal-observer-lifecycle.md +0 -109
  214. package/docs/retro/0237-remove-disallowed-tools.md +0 -78
  215. package/docs/retro/0238-remove-extensions-filtering.md +0 -72
  216. package/docs/retro/0239-collapse-filter-active-tools.md +0 -70
  217. package/docs/retro/0242-rename-agent-tool-to-subagent.md +0 -82
  218. package/docs/retro/0256-extract-worktree-isolation.md +0 -89
  219. package/docs/retro/0257-extract-child-session-factory.md +0 -33
  220. package/docs/retro/0262-add-workspace-provider-seam.md +0 -89
  221. package/docs/retro/0264-remove-extension-lifecycle-control.md +0 -91
  222. package/docs/retro/0265-born-complete-subagent-session.md +0 -97
  223. package/docs/retro/0270-type-consumable-public-surface.md +0 -108
  224. package/docs/retro/0272-export-workspace-collaborator-types.md +0 -38
  225. package/docs/retro/0277-encapsulate-agent-session.md +0 -80
  226. package/docs/retro/0280-rename-agent-to-subagent.md +0 -96
  227. package/docs/retro/0360-custom-agents-default-append.md +0 -81
  228. package/docs/retro/0373-extract-subagent-state.md +0 -94
  229. package/docs/retro/0374-encapsulate-subagent-start-notification.md +0 -171
  230. package/docs/retro/0375-extract-run-listener-workspace-bracket.md +0 -104
  231. package/docs/retro/0376-extract-manager-observer.md +0 -81
  232. package/docs/retro/0377-split-widget-delegation-from-runtime.md +0 -105
  233. package/docs/retro/0378-consolidate-lifecycle-test-fixtures.md +0 -100
  234. package/docs/retro/0379-consolidate-ui-tools-test-fixtures.md +0 -52
  235. package/docs/retro/0380-extract-layered-settings-loader.md +0 -97
  236. package/docs/retro/0381-replace-concurrency-queue-with-limiter.md +0 -95
  237. package/docs/retro/0400-include-parent-prompt-in-replace-mode.md +0 -84
  238. package/docs/retro/0403-abort-subagents-on-interrupt.md +0 -90
  239. package/docs/retro/0412-unify-session-mock-builders.md +0 -80
  240. package/docs/retro/0420-fold-run-metrics-live-activity-onto-record.md +0 -90
  241. package/docs/retro/0421-migrate-activity-readers-to-record-getters.md +0 -105
  242. package/docs/retro/0422-delete-activity-tracker-ui-observer.md +0 -97
  243. package/docs/retro/0423-widget-self-drive-from-lifecycle.md +0 -112
  244. package/docs/retro/0424-drop-widget-dep-from-subagent-tool.md +0 -89
  245. package/docs/retro/0425-reconcile-subagent-events-contract.md +0 -101
  246. package/docs/retro/0426-consolidate-residual-test-clone-families.md +0 -100
  247. package/docs/retro/0427-reconsider-ui-direction.md +0 -102
  248. package/docs/retro/0441-remove-agent-definition-management-subtree.md +0 -97
  249. package/docs/retro/0442-dissolve-agents-conversation-viewer.md +0 -108
  250. package/docs/retro/0443-consolidate-remaining-test-clone-families.md +0 -28
  251. package/docs/retro/0444-shrink-widget-to-background-agents.md +0 -81
  252. package/docs/retro/0445-native-session-navigation.md +0 -122
  253. package/docs/retro/0446-spike-session-navigation-entry-criteria.md +0 -99
  254. package/docs/retro/0447-extract-subagents-settings-command.md +0 -91
  255. package/docs/retro/0448-block-disabled-agent-spawn.md +0 -75
  256. package/docs/retro/0462-navigation-renderer-tui-components.md +0 -105
  257. package/docs/retro/0463-file-snapshot-source-evicted-agents.md +0 -103
  258. package/docs/retro/0470-refresh-readme-command-surface.md +0 -40
  259. package/src/observation/notification-state.ts +0 -27
  260. package/vitest.config.ts +0 -14
@@ -1,254 +0,0 @@
1
- ---
2
- issue: 377
3
- issue_title: "Split widget delegation out of SubagentRuntime"
4
- ---
5
-
6
- # Split widget delegation out of SubagentRuntime
7
-
8
- ## Problem Statement
9
-
10
- `SubagentRuntime.widget` is assigned after construction (`runtime.widget = new AgentWidget(...)` in `index.ts`), violating construct-complete (principle 8).
11
- The runtime then carries five relay-only delegation methods — `setUICtx`, `onTurnStart`, `markFinished`, `update`, `ensureTimer` — that do nothing but forward to `this.widget?`.
12
- That is a relay-only dependency (design-review check 5): the runtime holds the widget purely to hand it to others, and the coupling leaks into tests, where the `AgentToolRuntime` fixture stubs all five widget methods on the runtime mock.
13
-
14
- The issue's "Proposed change" — construct the widget before its consumers and pass the `WidgetLike` handle directly to the tool deps and `NotificationManager` — is the right intent but is **not directly feasible** as stated.
15
- `NotificationManager` is not just a consumer of the widget; it is a transitive *dependency* of the widget.
16
- The construction graph contains a genuine cycle:
17
-
18
- ```text
19
- NotificationManager → widget → manager → observer (SubagentEventsObserver) → NotificationManager
20
- ```
21
-
22
- The widget needs the manager (`listAgents()`); the manager needs the observer; the observer needs the `NotificationSystem`; and `NotificationManager` needs the widget (`markFinished`/`update`).
23
- The current `runtime.widget` lazy field exists precisely to break this cycle.
24
- Removing it forces the single unavoidable late seam to move — and the operator's design principles (no setters, instantiate ready-to-work, constructor DI) rule out relocating it to a setter or a forward-referenced `let`.
25
-
26
- The cycle has exactly one weak edge: `NotificationManager → widget`.
27
- Dissolving that edge collapses the cycle entirely, after which the remaining relay removal is mechanical.
28
-
29
- ## Goals
30
-
31
- - Remove the `widget` field and the five relay methods (`setUICtx`, `onTurnStart`, `markFinished`, `update`, `ensureTimer`) from `SubagentRuntime`.
32
- - Eliminate the post-construction `runtime.widget =` write from `index.ts`.
33
- - Inject the `AgentWidget` handle directly into its real consumers (`AgentTool`, `ToolStartHandler`) via constructor DI.
34
- - Dissolve `NotificationManager`'s widget dependency so the construction graph is a clean linear DAG (`notifications → observer → manager → widget → tool/handler`) with no cycle, no setter, and no forward-referenced `let`.
35
- - Narrow `AgentToolRuntime` to drop the four widget methods it currently declares.
36
- - Behavior-preserving: no observable change to widget rendering, the finished-agent linger countdown, or completion notifications.
37
-
38
- This change is **not breaking** — it is an internal refactor.
39
- `SubagentRuntime` is not part of the published service surface (`src/service/service.ts`); no exported API, event channel, or config default changes.
40
-
41
- ## Non-Goals
42
-
43
- - Reworking how the widget is rendered, the `/agents` menu, the conversation viewer, or any first-principles UI reconsideration — that is Phase 18.
44
- - Replacing the widget's poll-based refresh with an event-subscription model — heavier machinery, deferred.
45
- - Changing `NotificationManager`'s nudge scheduling, `sendMessage` wiring, or `agentActivity` ownership.
46
- - Touching the foreground-runner / background-spawner direct widget calls (they are already clean consumers that receive the widget from `AgentTool`) beyond the type of the handle they accept.
47
- - Phase 17 Steps 7–9 (test-fixture consolidation, settings-loader duplication).
48
-
49
- ## Background
50
-
51
- Relevant modules and their current widget coupling:
52
-
53
- | Module | Current widget coupling | Disposition |
54
- | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
55
- | `src/runtime.ts` | `WidgetLike` interface, `widget` field, 5 relay methods | Remove all widget knowledge |
56
- | `src/index.ts` | `runtime.widget = new AgentWidget(...)` post-construction; wires `runtime.markFinished`/`runtime.update` into `NotificationManager` | Construct widget as a `const`; inject directly; reorder |
57
- | `src/observation/notification.ts` | `NotificationManager` ctor takes `markFinished` + `updateWidget` callbacks; `sendCompletion`/`cleanupCompleted` call them | Drop both callbacks; keep `agentActivity.delete` + nudge |
58
- | `src/ui/agent-widget.ts` | `AgentWidget(manager, agentActivity, registry)`; `markFinished(id)` seeds `finishedTurnAge`; `update()` renders | Self-seed `finishedTurnAge` from `listAgents()` during `update()` |
59
- | `src/tools/agent-tool.ts` | `AgentToolRuntime` declares `setUICtx`/`ensureTimer`/`update`/`markFinished`; `execute` calls `this.runtime.setUICtx`; passes `this.runtime` as the widget to runner/spawner | Add a `widget` ctor param; narrow `AgentToolRuntime` |
60
- | `src/handlers/tool-start.ts` | `ToolStartRuntime` declares `setUICtx`/`onTurnStart`; calls them on `this.runtime` | Accept a widget instead of the runtime |
61
- | `src/tools/foreground-runner.ts` | `ForegroundWidgetDeps { ensureTimer, markFinished }` | Unchanged interface; receives the real widget |
62
- | `src/tools/background-spawner.ts` | `BackgroundWidgetDeps { ensureTimer, update }` | Unchanged interface; receives the real widget |
63
-
64
- Key facts establishing behavior preservation for the dissolve:
65
-
66
- - The widget timer (`ensureTimer()` → `setInterval(update, 80)`) runs while any agent is active or lingering, and is cleared only on `update()`'s idle path (`!hasActive && !hasFinished`).
67
- A background agent is **active at the moment it completes**, so the timer is always running then, and `hasFinished` keeps it running through the linger window.
68
- - The linger countdown is **turn-based**: `markFinished` seeds `finishedTurnAge[id] = 0`, `onTurnStart` ages every entry by 1, and `shouldShowFinished` hides an agent once its age reaches `maxAge` (1 for completed, 2 for error/aborted).
69
- Seeding ≤80ms later (on the next timer tick) lands in the same turn, so the expiry behavior is identical.
70
- - `NotificationManager.sendCompletion`/`cleanupCompleted` only deletes the live `AgentActivityTracker` (running-display state) — finished agents render from `listAgents()` + `finishedTurnAge`, so the deletion is orthogonal to linger.
71
-
72
- AGENTS.md / architecture constraints that apply:
73
-
74
- - Principle 8 (construct complete; no post-construction field writes from external code) is the motivating rule — the fix must not relocate the smell to a setter or forward-ref `let` (operator steer).
75
- - Phase 17 Step 1 deleted a `prefer-const` eslint-disable dance; reintroducing a forward-referenced `let widget` (which trips `prefer-const`, per the `code-design` skill) would regress that and is explicitly avoided.
76
- - `src/runtime.ts` is **not** in the rolled public type bundle (`exports` points at `src/service/service.ts`), so this change does not require `pnpm run verify:public-types`.
77
-
78
- `AgentWidget`, `NotificationManager`, and the runtime are all internal; the published `SubagentsService` (via `service-adapter.ts`, which consumes the runtime's `currentCtx`/`buildSnapshot` only) is untouched.
79
-
80
- ## Design Overview
81
-
82
- ### Decision: dissolve the cycle, don't relocate the seam
83
-
84
- Three candidate seam placements were considered (recorded for the retro):
85
-
86
- 1. **Late-bind notifications into the observer via a setter** — rejected: violates the no-setters principle; leaves a post-construction write.
87
- 2. **Forward-referenced `let widget` + closures into `NotificationManager`** — rejected: trips `prefer-const` → eslint-disable, reintroducing the exact smell Phase 17 Step 1 removed; not "ready-to-work."
88
- 3. **Dissolve `NotificationManager`'s widget dependency (chosen)** — the widget owns its own finished-agent detection; `NotificationManager` no longer references the widget; the cycle disappears and every object is constructible ready-to-work in a single linear pass.
89
-
90
- ### Tidy-first sequencing
91
-
92
- Per "make the change that makes the change easy, then make the easy change," the work splits into a hard preparatory refactor and a mechanical follow-up:
93
-
94
- - **Prep (Step 1, the hard part):** make the widget self-sufficient and remove the widget callbacks from `NotificationManager`.
95
- This breaks the cycle while `SubagentRuntime` still carries its relay methods (so `AgentTool`/`ToolStartHandler` are untouched and the repo stays green).
96
- - **Easy (Step 2):** with the cycle gone, the widget becomes a `const` injected directly into `AgentTool` and `ToolStartHandler`; the five relay methods, the `widget` field, and the post-construction write delete cleanly.
97
-
98
- ### Widget self-seeding
99
-
100
- Add a private helper invoked at the top of `update()`, before state assembly:
101
-
102
- ```ts
103
- /** Seed linger tracking for any newly-observed finished agent (replaces external markFinished). */
104
- private seedFinishedAgents(agents: readonly AgentSummary[]): void {
105
- for (const a of agents) {
106
- if (a.completedAt && !this.finishedTurnAge.has(a.id)) {
107
- this.finishedTurnAge.set(a.id, 0);
108
- }
109
- }
110
- }
111
- ```
112
-
113
- `update()` calls `this.seedFinishedAgents(allAgents)` immediately after `const allAgents = this.manager.listAgents()`.
114
- The existing public `markFinished(id)` stays (foreground-runner still calls it directly for immediacy, and it remains idempotent — `seedFinishedAgents` only seeds when absent).
115
-
116
- ### NotificationManager after the dissolve
117
-
118
- Constructor drops the last two params:
119
-
120
- ```ts
121
- constructor(
122
- private sendMessage: (msg: {...}, opts?: {...}) => void,
123
- private agentActivity: Map<string, AgentActivityTracker>,
124
- ) {}
125
- ```
126
-
127
- `sendCompletion(record)` → `agentActivity.delete(record.id)`; `scheduleNudge(...)` (drop `markFinished` + `updateWidget`).
128
- `cleanupCompleted(id)` → `agentActivity.delete(id)` (drop `markFinished` + `updateWidget`).
129
- The `NotificationSystem` interface is unchanged (method signatures identical).
130
-
131
- ### Consumer interfaces after the easy change (ISP — narrow per consumer)
132
-
133
- Drop the shared 5-method `WidgetLike` from `runtime.ts`.
134
- Each consumer declares only what it uses; `AgentWidget` satisfies all structurally:
135
-
136
- ```ts
137
- // agent-tool.ts — the slice AgentTool + its runner/spawner need
138
- export interface AgentToolWidget {
139
- setUICtx(ctx: UICtx): void;
140
- ensureTimer(): void;
141
- markFinished(id: string): void;
142
- update(): void;
143
- }
144
-
145
- // tool-start.ts — the slice the turn handler needs
146
- export interface ToolStartWidget {
147
- setUICtx(ctx: unknown): void;
148
- onTurnStart(): void;
149
- }
150
- ```
151
-
152
- `AgentTool` gains a `widget: AgentToolWidget` constructor param; `execute` calls `this.widget.setUICtx(...)` and passes `this.widget` to `runForeground`/`spawnBackground` (which already accept the narrow `ForegroundWidgetDeps`/`BackgroundWidgetDeps`).
153
- `ToolStartHandler` accepts a `ToolStartWidget` instead of `ToolStartRuntime` (rename).
154
- `AgentToolRuntime` loses `setUICtx`, `ensureTimer`, `update`, `markFinished` — keeping `agentActivity`, `buildSnapshot`, `getModelInfo`, `getSessionInfo`.
155
-
156
- ### Final construction order in `index.ts`
157
-
158
- ```text
159
- registry → runtime → notifications(sendMessage, agentActivity)
160
- → settings → observer(notifications) → limiter → manager(observer)
161
- → service → lifecycle(runtime) → session handlers
162
- → const widget = new AgentWidget(manager, agentActivity, registry)
163
- → toolStart = new ToolStartHandler(widget)
164
- → AgentTool(manager, runtime, widget, settings, registry, agentDir)
165
- → GetResultTool / SteerTool / agentsMenu
166
- ```
167
-
168
- No object is mutated after construction; the widget is a `const`.
169
- `service-adapter.ts` and `lifecycle.ts` keep consuming `runtime` for `currentCtx`/`buildSnapshot`/`setSessionContext` — none of which involve the widget.
170
-
171
- ## Module-Level Changes
172
-
173
- - `src/observation/notification.ts` — remove `markFinished` and `updateWidget` constructor params; drop their calls in `sendCompletion`/`cleanupCompleted`.
174
- `NotificationSystem` interface unchanged.
175
- - `src/ui/agent-widget.ts` — add private `seedFinishedAgents(agents)`; call it at the top of `update()` after `listAgents()`.
176
- `markFinished` retained (idempotent).
177
- - `src/runtime.ts` — delete the `WidgetLike` interface, the `widget` field, and the five relay methods (`setUICtx`, `onTurnStart`, `markFinished`, `update`, `ensureTimer`); remove the now-unused `UICtx` import.
178
- Update the class/file doc comment to drop "Persistent widget reference" / "Widget delegation methods."
179
- - `src/tools/agent-tool.ts` — add `AgentToolWidget` interface; add `widget` constructor param; `execute` uses `this.widget` for `setUICtx` and passes it to runner/spawner; narrow `AgentToolRuntime` (drop 4 widget methods); remove now-unused `WidgetLike`-related imports if any.
180
- - `src/handlers/tool-start.ts` — rename `ToolStartRuntime` → `ToolStartWidget`; constructor takes the widget; `handleToolExecutionStart` calls `this.widget.setUICtx`/`this.widget.onTurnStart`.
181
- - `src/index.ts` — construct `NotificationManager` with two args; construct `const widget = new AgentWidget(...)` after the manager; pass `widget` to `new ToolStartHandler(...)` and `new AgentTool(...)`; remove the `runtime.widget =` line and the stale comment block above the `NotificationManager` construction.
182
- - `test/observation/notification.test.ts` — drop the `markFinished`/`updateWidget` stub fields and the two `toHaveBeenCalled` assertions; assert `agentActivity.delete` + nudge behavior instead.
183
- - `test/ui/agent-widget.test.ts` — add coverage: `update()` self-seeds `finishedTurnAge` for a completed agent in `listAgents()`; the agent then expires after `maxAge` turns via `onTurnStart`; an already-seeded agent is not re-seeded.
184
- - `test/runtime.test.ts` — remove the "widget delegation methods" describe block, the "widget field accepts a `WidgetLike` stub" test, the `runtime.widget` default assertion, the `WidgetLike` import, and the `createWidgetStub` helper.
185
- - `test/helpers/make-deps.ts` — add a `widget` field (stub satisfying `AgentToolWidget`) to `AgentToolFixture`; narrow the `runtime` stub (drop `setUICtx`/`ensureTimer`/`update`/`markFinished`); update the doc comment that claims the runtime "also satisfies `BackgroundWidgetDeps`/`ForegroundWidgetDeps`."
186
- - `test/tools/agent-tool.test.ts` — `makeTool` passes `deps.widget`; the "sets UI context on runtime" test asserts on `deps.widget.setUICtx`.
187
- - `test/handlers/tool-start.test.ts` — construct `ToolStartHandler` with a widget stub; assert on `widget.setUICtx`/`widget.onTurnStart`.
188
- - `docs/architecture/architecture.md` — rewrite the Step 6 `- Change:` / `- Outcome:` bullets to the dissolve approach; add a `- Landed:` bullet and mark the heading `✅ Complete`.
189
- The findings-summary line (~915) describing the smell stays accurate and is left unchanged.
190
-
191
- No `.pi/skills/package-pi-subagents/SKILL.md` references to `runtime.widget`, `WidgetLike`, or the relay methods exist (grep-verified), so no skill update is required.
192
-
193
- ## Test Impact Analysis
194
-
195
- 1. **New tests enabled by the extraction.**
196
- Widget self-seeding (`seedFinishedAgents`) becomes directly unit-testable on `AgentWidget` via a stub manager whose `listAgents()` returns a completed record — previously the seeding path was only reachable through `NotificationManager.markFinished`.
197
- `NotificationManager` becomes testable without any widget stub at all.
198
- 2. **Tests simplified / made redundant.**
199
- The two `NotificationManager` assertions on `markFinished`/`updateWidget` are deleted (the behavior moved to the widget).
200
- The entire `SubagentRuntime` "widget delegation methods" describe block (5 trivial pass-through tests) and the `WidgetLike` stub plumbing are removed — pure relay tests with no remaining subject.
201
- 3. **Tests that must stay as-is.**
202
- The widget's render/linger/timer tests in `agent-widget.test.ts` (turn-aging via `onTurnStart`, `shouldShowFinished` thresholds, idle-path `clearWidget`) genuinely exercise the layer and are unchanged — the new self-seed test sits alongside them.
203
- `NotificationManager`'s nudge-scheduling and `sendMessage` tests are unchanged.
204
-
205
- ## Invariants at risk
206
-
207
- This step touches surfaces refactored by earlier Phase 17 steps; their documented outcomes must not regress:
208
-
209
- - **Step 1 (`#381`) — "every spawned agent has a `promise` at spawn; no forward-ref `prefer-const` eslint-disable."**
210
- Pinned by the limiter/manager tests in `test/lifecycle/`.
211
- Reintroducing a forward-ref `let widget` would regress this spirit — the chosen dissolve approach specifically avoids it.
212
- - **Step 5 (`#376`) — "`index.ts` < 170 lines; observer's three concerns unit-tested directly."**
213
- This step removes lines from `index.ts` (relay wiring + `runtime.widget =`), so the line budget is preserved or improved; `SubagentEventsObserver` is untouched.
214
- No new behavior is added to `index.ts`.
215
- - **Construct-complete (principle 8), the roadmap-wide Category-B/C invariant.**
216
- Pinned after this change by the absence of any `runtime.widget =` (grep-verifiable) and by `test/runtime.test.ts` no longer constructing a widget — add the grep check to the acceptance criteria.
217
-
218
- ## TDD Order
219
-
220
- 1. **Widget self-detects finished agents (prep — make it easy).**
221
- - Red: in `test/ui/agent-widget.test.ts`, assert `update()` seeds `finishedTurnAge` for a completed agent returned by `listAgents()` (rendered as finished), that it expires after `maxAge` turns via `onTurnStart`, and that an already-tracked agent is not re-seeded.
222
- - Green: add `private seedFinishedAgents(agents)` and call it at the top of `update()`.
223
- - Commit: `test:` then `feat:` — or a single `feat: self-seed finished agents in AgentWidget.update (#377)`.
224
- 2. **Drop widget callbacks from NotificationManager (prep — breaks the cycle).**
225
- - Red/Green: update `test/observation/notification.test.ts` to construct `NotificationManager` with two args and assert `agentActivity.delete` + nudge (drop the `markFinished`/`updateWidget` assertions); remove the two callbacks from the constructor and from `sendCompletion`/`cleanupCompleted`; update the `index.ts` construction site in the same commit (sole call site — the type checker requires it atomic).
226
- - Commit: `refactor: dissolve NotificationManager widget dependency (#377)`.
227
- - Outcome: the construction cycle is gone; `SubagentRuntime` still carries its relay methods (repo green).
228
- 3. **Inject the widget directly; remove relay methods and the widget field from SubagentRuntime (the easy change).**
229
- - This is one atomic commit: removing the `widget` field, the five relay methods, and narrowing `AgentToolRuntime` breaks `AgentTool`, `ToolStartHandler`, `index.ts`, and their tests at the type level simultaneously (export/field removal — per the workflow rule, fold all consumer and consumer-test updates into one step).
230
- - Changes: add `AgentToolWidget` + `widget` param to `AgentTool`; rename `ToolStartRuntime` → `ToolStartWidget` and accept the widget in `ToolStartHandler`; delete `WidgetLike` + `widget` + 5 relay methods from `runtime.ts`; construct `const widget` and reorder `index.ts` (remove the `runtime.widget =` write); update `make-deps.ts` (add `widget` stub, narrow `runtime`), `agent-tool.test.ts`, `tool-start.test.ts`, and `runtime.test.ts` (delete the delegation describe block + `WidgetLike` plumbing).
231
- - Commit: `refactor: inject widget directly, remove relay methods from SubagentRuntime (#377)`.
232
- - Acceptance: `rg 'runtime\.widget|\.widget =' src/` returns nothing; `rg 'WidgetLike' src/ test/` returns nothing; `pnpm run check && pnpm run lint && pnpm -r run test && pnpm fallow dead-code` clean.
233
- 4. **Update the architecture roadmap.**
234
- - Rewrite Step 6 `- Change:`/`- Outcome:` to the dissolve approach, add `- Landed:`, mark `✅ Complete`.
235
- - Commit: `docs: record Phase 17 Step 6 widget-delegation split (#377)`.
236
-
237
- ## Risks and Mitigations
238
-
239
- - **Risk: self-seed timing differs from the old immediate `markFinished`/`update`.**
240
- Mitigation: the timer is always running at a background completion (the agent was active); the seed lands ≤80ms later within the same turn; linger expiry is turn-based, so the rendered outcome is identical.
241
- The new widget test pins the seed-then-expire behavior.
242
- - **Risk: a completed agent never expires if it is never seeded.**
243
- Mitigation: `seedFinishedAgents` runs on every `update()` tick while the widget is live; the idle-path `clearWidget` only fires once nothing is active or finished, so a finished agent is always seeded before the widget can go idle.
244
- - **Risk: removing exports/fields breaks consumers mid-refactor.**
245
- Mitigation: Step 2 breaks the cycle while leaving the relay methods in place (repo green); Step 3 folds the export/field removal and all consumers + tests into one atomic commit.
246
- - **Risk: regressing an earlier Phase 17 invariant (forward-ref dance, `index.ts` budget).**
247
- Mitigation: the dissolve approach introduces no `let widget` and only removes lines from `index.ts`; the Invariants-at-risk grep checks are in the Step 3 acceptance criteria.
248
-
249
- ## Open Questions
250
-
251
- - Should `AgentTool` receive one `AgentToolWidget` and forward it, or should it receive the narrower `ForegroundWidgetDeps`/`BackgroundWidgetDeps` separately?
252
- Plan assumes a single `AgentToolWidget` superset (it also calls `setUICtx` itself) forwarded to the runner/spawner — revisit only if ISP pressure appears during implementation.
253
- - Whether foreground-runner's explicit `markFinished(fgId)` is now redundant given widget self-seeding.
254
- Plan keeps it (immediacy; idempotent) to hold scope tight; a follow-up could remove it if the self-seed proves sufficient.
@@ -1,243 +0,0 @@
1
- ---
2
- issue: 378
3
- issue_title: "Consolidate lifecycle test fixtures"
4
- ---
5
-
6
- # Consolidate lifecycle test fixtures
7
-
8
- ## Problem Statement
9
-
10
- `pnpm fallow:dupes` reports clone families concentrated in `test/lifecycle/`: repeated spawn-and-await arrangements and session-factory stubs copy-pasted across files and within them.
11
- This is Phase 17 Step 7 (core consolidation), sequenced after Steps 2–6 reshaped `Subagent` construction so the lifecycle tests had already churned.
12
- The roadmap Outcome is: lifecycle clone families 5 → ≤ 1; package test duplication below 600 lines.
13
-
14
- The issue body cites five families across six files (including `concurrency-queue.test.ts` and `subagent.test.ts` at 766 LOC).
15
- That snapshot predates Steps 1–6.
16
- The current state — measured with `fallow dupes -r packages/pi-subagents` against today's `main` — is **four** lifecycle clone families, because the queue was renamed to `concurrency-limiter.test.ts` ([#381]) and `subagent.test.ts`/`concurrency-limiter.test.ts` no longer report families after the Step 2–4 reshaping.
17
-
18
- ## Goals
19
-
20
- - Consolidate the four current lifecycle clone families into shared or file-local helpers so the lifecycle tree reports ≤ 1 family.
21
- - Bring package-wide test duplication below 600 lines (current baseline: 669 lines / 3.3% across 20 files; the four lifecycle families total ~122 lines).
22
- - Preserve every existing test assertion — the existing suite is the regression guard for this refactor.
23
- - Keep the change non-breaking: test-only, no production-code, public-surface, or behavior change.
24
-
25
- ## Non-Goals
26
-
27
- - Step 8 ([#379]) — UI and tools clone families (`agent-config-editor`, `agent-creation-wizard`, `ui-observer`, `foreground-runner`, `service-adapter`).
28
- These are separate families outside the lifecycle tree and are deferred to their own issue.
29
- - Step 9 ([#380]) — the cross-package settings-loader production clone.
30
- - Any production-code change in `src/`.
31
- - Adding new behavioral test cases — this is duplication removal, not coverage expansion (helper self-tests are the only new `it` blocks).
32
-
33
- ## Background
34
-
35
- The lifecycle suite already has substantial shared scaffolding under `test/helpers/`:
36
-
37
- - `subagent-session-io.ts` — `createSubagentSessionIO()`, `createAgentLookup()`, `createSubagentSessionDeps()`, `createChildLifecycleMock()`.
38
- The natural home for shared `createSubagentSession`-test scaffolding.
39
- - `manager-stubs.ts` — `createBlockingFactory()`, `createSessionFactory()`.
40
- - `mock-session.ts` — `createMockSession()`, `createSubagentSessionStub()`, `toSubagentSession()`, `toAgentSession()`.
41
- - `make-subagent.ts` — `createTestSubagent()`, `makeStubExecution()`.
42
-
43
- Convention: every shared helper module under `test/helpers/` has a companion `*.test.ts` (e.g. `subagent-session-io.test.ts`, `make-subagent.test.ts`).
44
- New or extended shared helpers must extend that companion test.
45
-
46
- The four current lifecycle clone families and the repeated blocks driving them:
47
-
48
- | File | Family | Repeated block |
49
- | ------------------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
50
- | `create-subagent-session.test.ts` | 3 groups, 31 lines | A file-local `createSession()` mock-session builder, then `io.createSession.mockResolvedValue({ session })` + `await createSubagentSession({ snapshot, type }, createSubagentSessionDeps({ io, exec, registry[, lifecycle] }))` |
51
- | `create-subagent-session-extension-tools.test.ts` | 3 groups, 29 lines | A near-identical file-local `createSessionWithExtensionToolRegistration()` builder, then the same `io.createSession.mockResolvedValue` + `createSubagentSession(...)` invoke block, plus post-bind `setActiveToolsByName` assertions |
52
- | `subagent-manager.test.ts` | 4 groups, 40 lines | `spawn(STUB_SNAPSHOT, "general-purpose", "test", { description, isBackground: true, parentSession: { toolCallId } })` and the "spawn two bg agents under limit 1, assert one queued" arrange |
53
- | `subagent-session.test.ts` | 3 groups, 22 lines | Repeated arrange blocks around the already-local `createSession(finalText)` / `makeSubagentSession()` / `emitTurnEnd()` helpers |
54
-
55
- The two `createSubagentSession`-test session builders (`createSession` and `createSessionWithExtensionToolRegistration`) build near-identical mock sessions — the same eight `vi.fn()` methods — differing only in that the extension-tools variant flips `getActiveToolNames` between a before-bind and after-bind set.
56
- That overlap is the one genuinely cross-file duplication and the strongest candidate for promotion to `test/helpers/`.
57
- The manager and `subagent-session` families are intra-file.
58
-
59
- AGENTS.md / testing-skill constraints that apply:
60
-
61
- - When consolidating duplicate test factories, diff the default values across all copies before writing the shared factory — different defaults cause cascading assertion failures during migration.
62
- - When a TDD step deletes a test helper, re-check the file's remaining imports for orphans — Biome's `noUnusedImports` is warning-level (exit 0), so it will not fail `pnpm run lint`.
63
- - Run the full suite (not just the touched file) after each shared-helper change.
64
-
65
- ## Design Overview
66
-
67
- The guiding distinction: **promote to `test/helpers/` only what is genuinely shared across files; keep intra-file families as file-local helpers.**
68
- Fallow's own recommendation for three of the four families is "extract shared function from `<file>`, `<file>`" (same file on both sides) — promoting those to `test/helpers/` would manufacture cross-file coupling that does not exist.
69
-
70
- ### 1. Shared: a mock-session builder for `createSubagentSession` tests
71
-
72
- Add `createFactorySession(overrides?)` to `test/helpers/subagent-session-io.ts`.
73
- It returns the eight-method mock session both `createSubagentSession` test files build by hand today, with `getActiveToolNames` supporting a static set or a before/after-bind flip:
74
-
75
- ```typescript
76
- export interface FactorySessionOptions {
77
- /** Tools active before bindExtensions(). Default ["read"]. */
78
- toolsBeforeBind?: string[];
79
- /** Tools active after bindExtensions(). Defaults to toolsBeforeBind (no extension registration). */
80
- toolsAfterBind?: string[];
81
- }
82
-
83
- export function createFactorySession(options: FactorySessionOptions = {}) {
84
- const before = options.toolsBeforeBind ?? ["read"];
85
- const after = options.toolsAfterBind ?? before;
86
- let bound = false;
87
- return {
88
- messages: [] as unknown[],
89
- subscribe: vi.fn(() => () => {}),
90
- prompt: vi.fn().mockResolvedValue(undefined),
91
- abort: vi.fn(),
92
- steer: vi.fn().mockResolvedValue(undefined),
93
- dispose: vi.fn(),
94
- getActiveToolNames: vi.fn(() => (bound ? after : before)),
95
- setActiveToolsByName: vi.fn(),
96
- bindExtensions: vi.fn(async () => { bound = true; }),
97
- };
98
- }
99
- ```
100
-
101
- Return type is deliberately unannotated so callers retain the `Mock<...>` methods (`mock.calls`, `mockResolvedValue`) — matching the existing `createSubagentSessionIO()` convention in this file.
102
-
103
- `create-subagent-session.test.ts` replaces `createSession()` with `createFactorySession()`; the existing static `getActiveToolNames: () => ["read"]` becomes the default branch (`bound === after === before === ["read"]`), so the lone `bindExtensions` flip is inert there — behavior-preserving.
104
- `create-subagent-session-extension-tools.test.ts` replaces `createSessionWithExtensionToolRegistration(before, after)` with `createFactorySession({ toolsBeforeBind: before, toolsAfterBind: after })`.
105
-
106
- Consumer call site (verifies Tell-Don't-Ask: the helper returns a ready mock; the test arranges and asserts on it):
107
-
108
- ```typescript
109
- const session = createFactorySession({ toolsBeforeBind: ["read"], toolsAfterBind: ["read", "extension_tool"] });
110
- io.createSession.mockResolvedValue({ session });
111
- await createSubagentSession({ snapshot: STUB_SNAPSHOT, type: "test-agent" }, createSubagentSessionDeps({ io, exec, registry }));
112
- expect(session.setActiveToolsByName.mock.calls[0][0]).toContain("extension_tool");
113
- ```
114
-
115
- The repeated `io.createSession.mockResolvedValue({ session })` + `createSubagentSession(...)` invoke pair is left in the tests rather than wrapped in an `invokeCreate()` helper: it is two lines, the `params` and `deps` overrides vary per test, and wrapping it would hide the system-under-test call behind a helper (procedure-splitting, not design improvement).
116
- Extracting only the session builder removes the bulk of both families because the builder is the larger half of each clone.
117
-
118
- ### 2. File-local: manager spawn / queued-pair arrangements
119
-
120
- `subagent-manager.test.ts` already has file-local `spawnBg`/`spawnFg`/`createManager`/`defaultFactory`.
121
- The remaining 40 lines are two intra-file patterns; add two file-local helpers:
122
-
123
- ```typescript
124
- /** Spawn a background agent carrying a parentSession.toolCallId (notification path). */
125
- function spawnBgWithToolCall(mgr: SubagentManager, toolCallId: string, prompt = "test") {
126
- return mgr.spawn(STUB_SNAPSHOT, "general-purpose", prompt, {
127
- description: prompt,
128
- isBackground: true,
129
- parentSession: { toolCallId },
130
- });
131
- }
132
-
133
- /** Arrange a manager at limit 1 with two bg agents: first runs, second queues. */
134
- function arrangeQueuedPair() {
135
- const { manager } = createManager({ createSubagentSession: createBlockingFactory(), getMaxConcurrent: () => 1 });
136
- const running = spawnBg(manager, "a");
137
- const queued = spawnBg(manager, "b");
138
- return { manager, running, queued };
139
- }
140
- ```
141
-
142
- These stay file-local: they reference manager-specific concerns and fallow scores the family as same-file.
143
-
144
- ### 3. File-local: `subagent-session.test.ts` arrange blocks
145
-
146
- The three small groups (22 lines) sit around the existing file-local `createSession(finalText)`, `makeSubagentSession()`, and `emitTurnEnd()` helpers.
147
- Fold the repeated "build session, build SubagentSession, run/emit" arrange into one file-local helper (e.g. `arrangeSession(finalText, metaOverrides?)` returning `{ session, listeners, sub }`), keeping the per-test assertions inline.
148
- No promotion to `test/helpers/` — this builder is specific to `subagent-session.test.ts`'s turn-loop assertions and is not used elsewhere.
149
-
150
- ## Module-Level Changes
151
-
152
- - `test/helpers/subagent-session-io.ts` — add `createFactorySession()` and `FactorySessionOptions`.
153
- - `test/helpers/subagent-session-io.test.ts` — add a `describe("createFactorySession")` block: default tool set, before/after-bind flip, all eight methods present.
154
- - `test/lifecycle/create-subagent-session.test.ts` — delete local `createSession()`; import and use `createFactorySession()`; re-check imports for orphans.
155
- - `test/lifecycle/create-subagent-session-extension-tools.test.ts` — delete local `createSessionWithExtensionToolRegistration()`; use `createFactorySession({ toolsBeforeBind, toolsAfterBind })`; re-check imports.
156
- - `test/lifecycle/subagent-manager.test.ts` — add file-local `spawnBgWithToolCall()` and `arrangeQueuedPair()`; route the cloned spawn/queued-pair sites through them.
157
- - `test/lifecycle/subagent-session.test.ts` — add a file-local `arrangeSession()` helper; route the cloned arrange blocks through it.
158
- - `docs/architecture/architecture.md` — mark Step 7 ✅ Complete and add a `Landed:` bullet (matching Steps 1–6), updating the families and duplication figures.
159
-
160
- No `src/` files change.
161
- No public surface changes, so `verify:public-types` is unaffected.
162
- The `package-pi-subagents` SKILL.md's test count line ("994 tests across 63 files as of Phase 17 Step 4") is a point-in-time figure, not a Step-7 reference, and is left untouched.
163
-
164
- ## Test Impact Analysis
165
-
166
- This is a test-refactoring issue, so the lens is inverted from a production extraction:
167
-
168
- 1. **New tests the change enables.**
169
- Only helper self-tests: a `createFactorySession` block in `subagent-session-io.test.ts` (per the `test/helpers/` companion-test convention).
170
- No new behavioral tests — the refactor must not change coverage.
171
- 2. **Existing tests that become redundant.**
172
- None are removed.
173
- The duplication being removed is *arrange-block* duplication inside `it()` bodies, not duplicate `it()` cases.
174
- Every existing assertion stays.
175
- 3. **Tests that must stay as-is.**
176
- All 228 lifecycle test cases keep their assertions; they are the regression guard.
177
- Migration is green-to-green: after each file's migration the full suite must stay passing.
178
- In particular, the post-bind `setActiveToolsByName` assertions in the extension-tools file and the turn-limit/`emitTurnEnd` assertions in `subagent-session.test.ts` are unchanged — only their session-construction arrange is swapped.
179
-
180
- ## Invariants at risk
181
-
182
- This step touches `subagent-manager.test.ts`, which pins invariants from earlier Phase 17 steps.
183
- The `arrangeQueuedPair()` helper must not swallow these:
184
-
185
- - **Step 1 ([#381]) / Step 3 ([#374]) — "every spawned agent has a `promise` at spawn, even while queued."**
186
- Pinned by `subagent-manager.test.ts` → `it("gives a queued agent an awaitable promise at spawn (before its slot opens)")`, which asserts `getRecord(queuedId)!.promise` is a `Promise` while status is `"queued"`.
187
- When this test adopts `arrangeQueuedPair()`, the helper must still return the queued id so the test can assert on `.promise` directly.
188
- - **Step 3 ([#374]) — "zero external writes to `Subagent.promise`/`.notification` outside `subagent.ts`" (grep-verifiable).**
189
- The helper migration must not reintroduce `record.promise =` or `record.notification =`.
190
- The existing `record.notification!.markConsumed()` sites are method calls, not writes, and stay as-is.
191
- Re-grep `test/lifecycle/` for `\.promise =` and `\.notification =` after the manager migration to confirm none were added.
192
-
193
- ## TDD Order
194
-
195
- This is a lift-and-shift refactor of tests; the suite is green at every step (no red phase — the existing assertions are the spec).
196
- Each migration step runs the full package suite before committing.
197
-
198
- 1. **Add shared `createFactorySession` + helper test.**
199
- Surface: `test/helpers/subagent-session-io.ts`, `test/helpers/subagent-session-io.test.ts`.
200
- Covers: default tool set, before/after-bind flip, method presence.
201
- Run `pnpm --filter @gotgenes/pi-subagents exec vitest run test/helpers/subagent-session-io.test.ts` then `pnpm run check`.
202
- Commit: `test: add shared createFactorySession mock-session builder (#378)`.
203
- 2. **Migrate `create-subagent-session.test.ts` to `createFactorySession`.**
204
- Delete local `createSession()`, re-check imports, run full suite green.
205
- Commit: `test: use shared factory session in create-subagent-session tests (#378)`.
206
- 3. **Migrate `create-subagent-session-extension-tools.test.ts` to `createFactorySession({ toolsBeforeBind, toolsAfterBind })`.**
207
- Delete local `createSessionWithExtensionToolRegistration()`, re-check imports, run full suite green.
208
- Commit: `test: use shared factory session in extension-tools tests (#378)`.
209
- 4. **Consolidate `subagent-manager.test.ts` spawn/queued-pair arrangements.**
210
- Add `spawnBgWithToolCall()` and `arrangeQueuedPair()`; route the cloned sites through them; re-grep for `.promise =`/`.notification =`; run full suite green.
211
- Commit: `test: consolidate manager spawn arrangements (#378)`.
212
- 5. **Consolidate `subagent-session.test.ts` arrange blocks.**
213
- Add `arrangeSession()`; route cloned sites; run full suite green.
214
- Commit: `test: consolidate subagent-session arrange blocks (#378)`.
215
- 6. **Verify and record outcome.**
216
- Run `pnpm exec fallow dupes -r packages/pi-subagents` and confirm lifecycle families ≤ 1 and package duplication < 600 lines; run `pnpm run check && pnpm run lint && pnpm fallow dead-code`.
217
- Update `docs/architecture/architecture.md` Step 7 to ✅ Complete with a `Landed:` bullet and refreshed figures.
218
- Commit: `docs: mark Phase 17 Step 7 complete (#378)`.
219
-
220
- Steps 2–5 are independent of each other and order-insensitive; each only depends on Step 1's helper.
221
-
222
- ## Risks and Mitigations
223
-
224
- - **Divergent defaults between the two session builders cause assertion failures.**
225
- Mitigation: the two builders are diffed in Background — they share eight identical methods and differ only in `getActiveToolNames`.
226
- `createFactorySession` defaults `toolsAfterBind` to `toolsBeforeBind` so the non-extension file's static behavior is preserved exactly.
227
- - **Over-extraction / procedure-splitting to chase the metric.**
228
- Mitigation: the invoke pair (`mockResolvedValue` + `createSubagentSession(...)`) and the per-test assertions are left inline; only value-returning builders are extracted.
229
- File-local families stay file-local rather than being force-promoted to `test/helpers/`.
230
- - **Orphaned imports after deleting local builders (Biome `noUnusedImports` is warning-level, exit 0).**
231
- Mitigation: re-check each migrated file's imports as part of its step; the Step 6 `pnpm run lint` + dead-code pass is the backstop.
232
- - **Regressing a cross-step invariant with a green suite (the Step 3 lesson).**
233
- Mitigation: the "Invariants at risk" section names the pinning tests and the grep checks folded into Step 4.
234
-
235
- ## Open Questions
236
-
237
- - Whether package-wide duplication lands below 600 lines from Step 7 alone or needs Step 8 ([#379]).
238
- Arithmetic says yes (669 − ~122 ≈ 547), but the Step 6 `fallow dupes` measurement is the authority; if it lands above 600, note it and confirm the lifecycle-families target (≤ 1) is still met rather than pulling Step 8 work forward.
239
-
240
- [#374]: https://github.com/gotgenes/pi-packages/issues/374
241
- [#379]: https://github.com/gotgenes/pi-packages/issues/379
242
- [#380]: https://github.com/gotgenes/pi-packages/issues/380
243
- [#381]: https://github.com/gotgenes/pi-packages/issues/381