@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,300 +0,0 @@
1
- ---
2
- issue: 136
3
- issue_title: "Decompose `agent-menu.ts`"
4
- ---
5
-
6
- # Decompose agent-menu into config editor, creation wizard, and file-ops abstraction
7
-
8
- ## Problem Statement
9
-
10
- `agent-menu.ts` (668 lines) has 8 distinct responsibilities: menu FSM orchestration, agent listing, config editing, agent ejection, two creation wizards, running-agent viewer, and settings form.
11
- Filesystem operations (`readFileSync`, `writeFileSync`, `unlinkSync`, `existsSync`, `mkdirSync`) are scattered across 10+ call sites with no abstraction layer, forcing tests to use `vi.mock("node:fs")` rather than injecting stubs.
12
-
13
- ## Goals
14
-
15
- - Extract an `AgentFileOps` interface abstracting all filesystem calls, with a production `FsAgentFileOps` implementation.
16
- - Extract `ui/agent-config-editor.ts` (~170 lines) containing `showAgentDetail` with eject/disable/enable/edit/delete/reset transitions.
17
- - Extract `ui/agent-creation-wizard.ts` (~200 lines) containing both the AI-generation and manual-form creation paths.
18
- - Leave menu FSM, agent listing, running-agent viewer, and settings form in `agent-menu.ts` (~280 lines).
19
- - Each extracted module receives dependencies via injection — no direct `node:fs` imports outside `FsAgentFileOps`.
20
- - Enable unit testing of config editor and creation wizard without `vi.mock("node:fs")`.
21
-
22
- ## Non-Goals
23
-
24
- - Refactoring the settings form or running-agent viewer — they stay in `agent-menu.ts`.
25
- - Changing any user-facing behavior or menu flow.
26
- - Extracting `showAllAgentsList` — it is menu orchestration (presents the list, delegates to editor for detail).
27
- - Deduplicating the YAML frontmatter builders in eject and manual wizard (structurally different content shapes).
28
-
29
- ## Background
30
-
31
- ### Prerequisite
32
-
33
- Issue #135 (extract display helpers) is **implemented** — `ui/display.ts` exists and provides `formatDuration`, `getDisplayName`, and other formatters.
34
- Extracted menu sub-modules can import display helpers without pulling in the widget.
35
-
36
- ### Existing IO-injection convention
37
-
38
- The codebase uses injectable IO interfaces to decouple domain logic from `node:fs` and the Pi SDK:
39
-
40
- - `AssemblerIO` in `session-config.ts` — 4 methods for prompt assembly IO.
41
- - `RunnerIO` in `agent-runner.ts` — 7 methods for session creation IO.
42
-
43
- Both follow the same pattern: interface defined in the module that uses it, production implementation wired at the edge (`index.ts`), test stubs injected directly.
44
-
45
- ### Current fs call inventory in agent-menu.ts
46
-
47
- | Function | fs calls |
48
- | -------------------------------- | ------------------------------------------------------------- |
49
- | `findAgentFile` | `existsSync` ×2 |
50
- | `showAgentDetail` (edit) | `readFileSync`, `writeFileSync` |
51
- | `showAgentDetail` (delete/reset) | `unlinkSync` |
52
- | `ejectAgent` | `mkdirSync`, `existsSync`, `writeFileSync` |
53
- | `disableAgent` | `readFileSync`, `writeFileSync`, `mkdirSync`, `writeFileSync` |
54
- | `enableAgent` | `readFileSync`, `writeFileSync`, `unlinkSync` |
55
- | `showGenerateWizard` | `mkdirSync`, `existsSync` ×2 |
56
- | `showManualWizard` | `mkdirSync`, `existsSync`, `writeFileSync` |
57
-
58
- ### Current test file
59
-
60
- `agent-menu.test.ts` (212 lines) uses `vi.mock("node:fs")` with `vi.hoisted` stubs.
61
- Only 7 tests exist — they cover the top-level menu, agent listing, projectAgentsDir injection, and settings delegation.
62
- No tests exercise config-editor transitions (edit/delete/eject/disable/enable) or creation wizards.
63
-
64
- ## Design Overview
65
-
66
- ### AgentFileOps interface
67
-
68
- A narrow interface abstracting all agent `.md` file operations:
69
-
70
- ```typescript
71
- interface AgentFileOps {
72
- exists(filePath: string): boolean;
73
- read(filePath: string): string | undefined;
74
- write(filePath: string, content: string): void;
75
- remove(filePath: string): void;
76
- ensureDir(dirPath: string): void;
77
- findAgentFile(name: string, dirs: string[]): string | undefined;
78
- }
79
- ```
80
-
81
- Design notes:
82
-
83
- - `read` returns `undefined` when the file does not exist (wraps `readFileSync` with a try/catch).
84
- - `write` ensures parent directories exist before writing (internalizes `mkdirSync`).
85
- - `ensureDir` is needed separately because `showGenerateWizard` creates the directory before spawning an agent that writes via Pi's tool (not via `AgentFileOps.write`).
86
- - `findAgentFile` takes an ordered list of directories and returns the first matching path.
87
- The current code returns `{ path, location }` but only `showAgentDetail` uses `location` (for a confirmation dialog) — the full path already conveys the location to the user, so a plain `string | undefined` return suffices.
88
-
89
- ### FsAgentFileOps production implementation
90
-
91
- Thin wrapper over `node:fs` synchronous APIs:
92
-
93
- ```typescript
94
- class FsAgentFileOps implements AgentFileOps {
95
- exists(filePath: string): boolean {
96
- return existsSync(filePath);
97
- }
98
- read(filePath: string): string | undefined {
99
- try { return readFileSync(filePath, "utf-8"); }
100
- catch { return undefined; }
101
- }
102
- write(filePath: string, content: string): void {
103
- mkdirSync(dirname(filePath), { recursive: true });
104
- writeFileSync(filePath, content, "utf-8");
105
- }
106
- remove(filePath: string): void {
107
- unlinkSync(filePath);
108
- }
109
- ensureDir(dirPath: string): void {
110
- mkdirSync(dirPath, { recursive: true });
111
- }
112
- findAgentFile(name: string, dirs: string[]): string | undefined {
113
- for (const dir of dirs) {
114
- const p = join(dir, `${name}.md`);
115
- if (existsSync(p)) return p;
116
- }
117
- return undefined;
118
- }
119
- }
120
- ```
121
-
122
- ### Extracted module patterns
123
-
124
- Both extracted modules follow the existing factory-function pattern (`createAgentsMenuHandler(deps)`) with narrow deps interfaces per ISP:
125
-
126
- Config editor call-site sketch (from orchestrator):
127
-
128
- ```typescript
129
- const editor = createAgentConfigEditor({
130
- fileOps: deps.fileOps,
131
- registry: deps.registry,
132
- personalAgentsDir: deps.personalAgentsDir,
133
- projectAgentsDir: deps.projectAgentsDir,
134
- });
135
- // ...
136
- await editor.showAgentDetail(ctx, agentName);
137
- ```
138
-
139
- Creation wizard call-site sketch (from orchestrator):
140
-
141
- ```typescript
142
- const wizard = createAgentCreationWizard({
143
- fileOps: deps.fileOps,
144
- manager: deps.manager,
145
- registry: deps.registry,
146
- personalAgentsDir: deps.personalAgentsDir,
147
- projectAgentsDir: deps.projectAgentsDir,
148
- });
149
- // ...
150
- await wizard.showCreateWizard(ctx);
151
- ```
152
-
153
- ### Deps interfaces (ISP-compliant)
154
-
155
- ```typescript
156
- // agent-config-editor.ts
157
- interface AgentConfigEditorDeps {
158
- fileOps: AgentFileOps;
159
- registry: AgentTypeRegistry;
160
- personalAgentsDir: string;
161
- projectAgentsDir: string;
162
- }
163
-
164
- // agent-creation-wizard.ts
165
- interface AgentCreationWizardDeps {
166
- fileOps: AgentFileOps;
167
- manager: AgentMenuManager;
168
- registry: AgentTypeRegistry;
169
- personalAgentsDir: string;
170
- projectAgentsDir: string;
171
- }
172
- ```
173
-
174
- The config editor does not need `manager` (no agent spawning).
175
- The creation wizard does not need `agentActivity`, `getModelLabel`, or `settings`.
176
-
177
- ### Updated AgentMenuDeps
178
-
179
- ```typescript
180
- interface AgentMenuDeps {
181
- manager: AgentMenuManager;
182
- registry: AgentTypeRegistry;
183
- agentActivity: AgentActivityReader;
184
- getModelLabel: (type: string, registry?: ModelRegistry) => string;
185
- settings: AgentMenuSettings;
186
- fileOps: AgentFileOps; // ← new
187
- personalAgentsDir: string;
188
- projectAgentsDir: string;
189
- }
190
- ```
191
-
192
- The single new field (`fileOps`) replaces all direct `node:fs` imports.
193
-
194
- ## Module-Level Changes
195
-
196
- ### New files
197
-
198
- 1. `src/ui/agent-file-ops.ts` — `AgentFileOps` interface + `FsAgentFileOps` class.
199
- 2. `src/ui/agent-config-editor.ts` — `AgentConfigEditorDeps` interface, `createAgentConfigEditor` factory.
200
- Moves in: `showAgentDetail`, `ejectAgent`, `disableAgent`, `enableAgent`.
201
- 3. `src/ui/agent-creation-wizard.ts` — `AgentCreationWizardDeps` interface, `createAgentCreationWizard` factory.
202
- Moves in: `showCreateWizard`, `showGenerateWizard`, `showManualWizard`.
203
- 4. `test/ui/agent-file-ops.test.ts` — unit tests for `FsAgentFileOps`.
204
- 5. `test/ui/agent-config-editor.test.ts` — unit tests for config editor transitions with injected `AgentFileOps` stubs.
205
- 6. `test/ui/agent-creation-wizard.test.ts` — unit tests for wizard paths with injected stubs.
206
-
207
- ### Modified files
208
-
209
- 1. `src/ui/agent-menu.ts`:
210
- - Remove `import { existsSync, mkdirSync, readFileSync, unlinkSync } from "node:fs"`.
211
- - Remove `findAgentFile`, `showAgentDetail`, `ejectAgent`, `disableAgent`, `enableAgent`, `showCreateWizard`, `showGenerateWizard`, `showManualWizard` (~375 lines removed).
212
- - Add `fileOps: AgentFileOps` to `AgentMenuDeps`.
213
- - Import and instantiate `createAgentConfigEditor` and `createAgentCreationWizard`.
214
- - Update `showAllAgentsList` to call `editor.showAgentDetail(ctx, agentName)`.
215
- - Update `showAgentsMenu` to call `wizard.showCreateWizard(ctx)`.
216
- - Re-export `AgentMenuManager` (still needed by the wizard deps).
217
- - Net result: ~280 lines (orchestration, listing, running agents, settings).
218
- 2. `src/index.ts`:
219
- - Import `FsAgentFileOps` from `./ui/agent-file-ops.js`.
220
- - Construct `new FsAgentFileOps()` and pass as `fileOps` in the `createAgentsMenuHandler` call.
221
- 3. `test/ui/agent-menu.test.ts`:
222
- - Remove `vi.mock("node:fs")` and `vi.hoisted` stubs entirely.
223
- - Add `fileOps` stub to `makeDeps` factory.
224
- - Rewrite the "projectAgentsDir injection" test to verify that the orchestrator delegates to the editor (or move to `agent-config-editor.test.ts`).
225
-
226
- ### Removed symbols
227
-
228
- Grep confirms these functions are only referenced within `agent-menu.ts` (closures inside `createAgentsMenuHandler`) — no external consumers:
229
-
230
- - `findAgentFile` — moves to `agent-config-editor.ts` (reimplemented via `AgentFileOps.findAgentFile`).
231
- - `showAgentDetail`, `ejectAgent`, `disableAgent`, `enableAgent` — move to `agent-config-editor.ts`.
232
- - `showCreateWizard`, `showGenerateWizard`, `showManualWizard` — move to `agent-creation-wizard.ts`.
233
-
234
- ## Test Impact Analysis
235
-
236
- ### New unit tests enabled by extraction
237
-
238
- 1. **Config editor transitions** — `showAgentDetail` has 6 menu paths (edit, delete, reset, eject, disable, enable) with sub-branches (confirm/cancel, file exists/not-exists, default/custom agent).
239
- Currently untestable in isolation because the functions are closures inside `createAgentsMenuHandler`.
240
- After extraction, each transition is testable with injected `AgentFileOps` stubs — no `vi.mock("node:fs")` needed.
241
- 2. **Creation wizard flows** — generate wizard (spawn + check result) and manual wizard (multi-step form → write file) are currently untested.
242
- After extraction, both are testable with injected stubs for `AgentFileOps` and `AgentMenuManager.spawnAndWait`.
243
- 3. **FsAgentFileOps** — thin tests verifying the production fs wrapper (read returns undefined on missing file, write ensures parent dirs, findAgentFile checks directories in order).
244
-
245
- ### Existing tests that become redundant
246
-
247
- The "projectAgentsDir injection" test (`agent-menu.test.ts`) navigates through the main menu → agent types → agent detail, then asserts `mockExistsSync` was called with the correct path.
248
- After extraction, this end-to-end path tests orchestration + editor together; a focused test in `agent-config-editor.test.ts` replaces it.
249
- The orchestrator test can be simplified to verify it delegates to the editor.
250
-
251
- ### Existing tests that stay as-is
252
-
253
- - Menu structure tests (shows options, reload, running agents) — these test the orchestrator directly.
254
- - Settings delegation tests — these test code that remains in `agent-menu.ts`.
255
-
256
- ## TDD Order
257
-
258
- ### Cycle 1: AgentFileOps interface and FsAgentFileOps
259
-
260
- 1. `test:` write `test/ui/agent-file-ops.test.ts` — tests for `read` (existing file, missing file), `write` (ensures parent dir), `remove`, `exists`, `ensureDir`, `findAgentFile` (first-match ordering, no match).
261
- 2. `feat:` implement `src/ui/agent-file-ops.ts` — interface + `FsAgentFileOps` class.
262
- 3. Commit: `feat: add AgentFileOps interface and FsAgentFileOps (#136)`.
263
-
264
- ### Cycle 2: Extract agent-config-editor
265
-
266
- 1. `test:` write `test/ui/agent-config-editor.test.ts` — tests for `showAgentDetail` transitions: edit (save/cancel), delete (confirm/cancel), reset-to-default (confirm/cancel), eject (project/personal location, overwrite check), disable (existing file toggle, new disable-only file), enable (remove enabled:false line, remove empty override file).
267
- All tests inject stub `AgentFileOps` — no `vi.mock`.
268
- 2. `refactor:` create `src/ui/agent-config-editor.ts` — move `showAgentDetail`, `ejectAgent`, `disableAgent`, `enableAgent` from `agent-menu.ts`.
269
- Replace direct fs calls with `deps.fileOps.*` calls.
270
- Replace the closure `findAgentFile` with `deps.fileOps.findAgentFile(name, [deps.projectAgentsDir, deps.personalAgentsDir])`.
271
- 3. `refactor:` update `src/ui/agent-menu.ts` — add `fileOps` to `AgentMenuDeps`, import `createAgentConfigEditor`, wire into `showAllAgentsList`.
272
- 4. `refactor:` update `src/index.ts` — import `FsAgentFileOps`, pass `fileOps: new FsAgentFileOps()` in deps.
273
- 5. `test:` update `test/ui/agent-menu.test.ts` — add `fileOps` stub to `makeDeps`, rewrite the "projectAgentsDir injection" test to verify orchestrator→editor delegation.
274
- 6. Run `pnpm run check` (interface change).
275
- 7. Commit: `refactor: extract agent-config-editor from agent-menu (#136)`.
276
-
277
- ### Cycle 3: Extract agent-creation-wizard
278
-
279
- 1. `test:` write `test/ui/agent-creation-wizard.test.ts` — tests for `showCreateWizard` (location + method selection), `showGenerateWizard` (spawn success, spawn error, overwrite check), `showManualWizard` (full form flow, overwrite check, tool/model/thinking selections).
280
- All tests inject stub `AgentFileOps` + `AgentMenuManager` — no `vi.mock`.
281
- 2. `refactor:` create `src/ui/agent-creation-wizard.ts` — move `showCreateWizard`, `showGenerateWizard`, `showManualWizard` from `agent-menu.ts`.
282
- Replace direct fs calls with `deps.fileOps.*` calls.
283
- 3. `refactor:` update `src/ui/agent-menu.ts` — import `createAgentCreationWizard`, wire into `showAgentsMenu`.
284
- Remove the `import { ... } from "node:fs"` line (no longer needed).
285
- 4. `test:` remove `vi.mock("node:fs")` and `vi.hoisted` stubs from `test/ui/agent-menu.test.ts` entirely.
286
- 5. Run `pnpm run check`.
287
- 6. Commit: `refactor: extract agent-creation-wizard from agent-menu (#136)`.
288
-
289
- ## Risks and Mitigations
290
-
291
- | Risk | Mitigation |
292
- | --------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
293
- | Circular dependency between orchestrator and editor (editor calling back to menu) | The editor's `showAgentDetail` does not recurse to the agent list — the orchestrator does (`showAllAgentsList` calls editor then recurses itself). No circular dependency. |
294
- | `FsAgentFileOps.write` silently creates parent directories when callers expect the directory to not exist | `write` mirrors current behavior — every call site already calls `mkdirSync` before `writeFileSync`. The consolidation only changes where the `mkdirSync` happens, not whether it runs. |
295
- | Adding `fileOps` to `AgentMenuDeps` breaks existing test factory | Cycle 2 updates `makeDeps` in the same commit — the interface change and call-site update land together per the testing skill's single-call-site rule. |
296
- | Generate wizard spawns an agent that writes via Pi tools, not via `AgentFileOps.write` | The wizard uses `fileOps.ensureDir` for directory creation and `fileOps.exists` for the post-spawn success check. The spawned agent's file write is outside the menu's control and is not abstracted. |
297
-
298
- ## Open Questions
299
-
300
- - None — the issue's proposed changes section is unambiguous and the prerequisite (#135) is already implemented.
@@ -1,263 +0,0 @@
1
- ---
2
- issue: 144
3
- issue_title: "Consolidate observation model (Phase 9, Step L)"
4
- ---
5
-
6
- # Consolidate observation model
7
-
8
- ## Problem Statement
9
-
10
- `record-observer.ts` and `ui-observer.ts` independently count tool uses and accumulate lifetime usage from the same session events.
11
- `AgentRecord` owns `_toolUses` and `_lifetimeUsage` (accumulated by the record observer), while `AgentActivityTracker` maintains its own `_toolUses` and `_lifetimeUsage` (accumulated by the UI observer).
12
- Consumers use `activity?.toolUses ?? record.toolUses` fallbacks to paper over the ambiguity.
13
-
14
- Separately, 15+ callsites navigate `record.execution?.session` and `record.execution?.outputFile`, leaking the `ExecutionState` structure to every consumer.
15
-
16
- Finally, `NotificationDeps` (4 fields) is a dependency bag on a class where every method uses every field — plain constructor parameters are simpler.
17
-
18
- ## Goals
19
-
20
- - Remove `_toolUses` and `_lifetimeUsage` from `AgentActivityTracker` so stats have a single source of truth on `AgentRecord`.
21
- - Update UI consumers (widget, conversation viewer, notification, foreground runner) to read stats from `AgentRecord` instead of the tracker.
22
- - Remove the `onToolEnd` counter and `onUsageUpdate` accumulator from `AgentActivityTracker`, keeping only live UI state (active tools, response text, turn count).
23
- - Stop the UI observer from duplicating `tool_execution_end` counting and `message_end` usage accumulation.
24
- - Add `session` and `outputFile` convenience getters on `AgentRecord` to hide the `execution?.` traversal.
25
- - Replace `NotificationDeps` interface with plain parameters on `NotificationManager` constructor.
26
-
27
- ## Non-Goals
28
-
29
- - Splitting `AgentWidget` rendering into pure functions (Step P / #148 — depends on this work).
30
- - Narrowing `ExtensionContext` for menu handlers (Step N / #146 — independent track).
31
- - Injecting text wrapping into `ConversationViewer` (Step O / #147 — independent track).
32
-
33
- ## Background
34
-
35
- ### Dual counting
36
-
37
- Both observers subscribe to the same session events:
38
-
39
- | Event | `record-observer.ts` | `ui-observer.ts` |
40
- | --------------------- | ---------------------------- | ------------------------------------- |
41
- | `tool_execution_end` | `record.incrementToolUses()` | `tracker.onToolEnd()` → `_toolUses++` |
42
- | `message_end` (usage) | `record.addUsage(delta)` | `tracker.onUsageUpdate(delta)` |
43
-
44
- The widget reads `bg?.toolUses ?? a.toolUses` and the conversation viewer reads `this.activity?.toolUses ?? this.record.toolUses`, preferring the tracker when alive and falling back to the record.
45
- After this change, there is one source: `AgentRecord`.
46
-
47
- ### `execution?.` traversal
48
-
49
- `ExecutionState` holds `session` and `outputFile`.
50
- These are set once when the agent session is created (`agent-manager.ts` line 276).
51
- Consumers reach through `record.execution?.session` and `record.execution?.outputFile` in 12 distinct locations across 7 files.
52
- Convenience getters on `AgentRecord` eliminate the traversal and hide the `ExecutionState` structure.
53
-
54
- ### `NotificationDeps` bag
55
-
56
- `NotificationManager` receives 4 fields via `NotificationDeps`: `sendMessage`, `agentActivity`, `markFinished`, `updateWidget`.
57
- Every method on the class uses every field.
58
- Per code-design convention (dependency width), a 4-field interface where every consumer uses every field is fine as a bag, but the architecture doc explicitly calls for plain parameters here.
59
- This is a mechanical change.
60
-
61
- ## Design Overview
62
-
63
- ### AgentActivityTracker changes
64
-
65
- Remove `_toolUses`, `_lifetimeUsage`, `onToolEnd`, `onUsageUpdate`, `toolUses` getter, and `lifetimeUsage` getter.
66
- Retain: `_activeTools`, `_toolKeySeq`, `_responseText`, `_session`, `_turnCount`, `_maxTurns`, and their associated transition methods and getters.
67
-
68
- `onToolStart` stays (tracks active tools for the widget spinner).
69
- A new `onToolDone(toolName)` method replaces `onToolEnd` — it removes the tool from `_activeTools` without incrementing any counter.
70
- This rename clarifies that the method only manages the active-tool display set.
71
-
72
- ### UI observer changes
73
-
74
- The UI observer stops calling counter/accumulator methods:
75
-
76
- - `tool_execution_end` handler: calls `tracker.onToolDone(name)` (active-tool tracking only).
77
- - `message_end` handler: removed entirely (no more usage accumulation).
78
- The `onUpdate?.()` call for usage re-render moves to the record observer path via the existing widget update flow.
79
-
80
- ### AgentRecord convenience getters
81
-
82
- ```typescript
83
- get session(): AgentSession | undefined {
84
- return this.execution?.session;
85
- }
86
-
87
- get outputFile(): string | undefined {
88
- return this.execution?.outputFile;
89
- }
90
- ```
91
-
92
- Callers change from `record.execution?.session` to `record.session`.
93
- The `execution` field remains writable (set by `AgentManager`), but consumers no longer need to know about `ExecutionState`.
94
-
95
- ### Consumer migration
96
-
97
- | File | Before | After |
98
- | -------------------------- | -------------------------------------------------- | ------------------------------------------------ |
99
- | `agent-widget.ts` | `bg?.toolUses ?? a.toolUses` | `a.toolUses` |
100
- | `agent-widget.ts` | `getLifetimeTotal(bg?.lifetimeUsage)` | `getLifetimeTotal(a.lifetimeUsage)` |
101
- | `agent-widget.ts` | `getSessionContextPercent(bg?.session)` | `getSessionContextPercent(a.session)` |
102
- | `conversation-viewer.ts` | `this.activity?.toolUses ?? this.record.toolUses` | `this.record.toolUses` |
103
- | `conversation-viewer.ts` | `getLifetimeTotal(this.activity?.lifetimeUsage)` | `getLifetimeTotal(this.record.lifetimeUsage)` |
104
- | `conversation-viewer.ts` | `getSessionContextPercent(this.activity?.session)` | `getSessionContextPercent(this.record.session)` |
105
- | `notification.ts` | `record.execution?.session` | `record.session` |
106
- | `notification.ts` | `record.execution?.outputFile` | `record.outputFile` |
107
- | `tools/get-result-tool.ts` | `record.execution?.session` | `record.session` |
108
- | `tools/steer-tool.ts` | `record.execution?.session` | `record.session` |
109
- | `agent-menu.ts` | `record.execution?.session` | `record.session` |
110
- | `service-adapter.ts` | `record.execution?.session` | `record.session` |
111
- | `agent-manager.ts` | `record.execution?.session` | `record.session` (in dispose paths) |
112
- | `foreground-runner.ts` | `fgState.toolUses` | `record.toolUses` (in final result) |
113
- | `foreground-runner.ts` | `formatLifetimeTokens(fgState)` | `formatLifetimeTokens(record)` (in final result) |
114
-
115
- ### NotificationManager refactoring
116
-
117
- Replace:
118
-
119
- ```typescript
120
- export interface NotificationDeps { … }
121
- constructor(private deps: NotificationDeps) {}
122
- ```
123
-
124
- With:
125
-
126
- ```typescript
127
- constructor(
128
- private sendMessage: NotificationDeps["sendMessage"],
129
- private agentActivity: Map<string, AgentActivityTracker>,
130
- private markFinished: (id: string) => void,
131
- private updateWidget: () => void,
132
- ) {}
133
- ```
134
-
135
- Internal references change from `this.deps.sendMessage(…)` to `this.sendMessage(…)`.
136
- The `NotificationDeps` interface is removed.
137
-
138
- ### Foreground runner streaming
139
-
140
- During foreground execution, `streamUpdate` reads `fgState.toolUses` and `formatLifetimeTokens(fgState)` for live spinner updates.
141
- After removing these from the tracker, `streamUpdate` must read from the record instead.
142
- The record is available only after `spawnAndWait` returns the record reference via `onSessionCreated`.
143
- Before that callback fires, tool uses and usage are both zero — the tracker never had meaningful data at that point either.
144
- `streamUpdate` will capture a `let recordRef: AgentRecord | undefined` and read `recordRef?.toolUses ?? 0` during the spinner phase.
145
- After `onSessionCreated`, `recordRef` is set and subsequent spinner ticks read live values from the record.
146
-
147
- ## Module-Level Changes
148
-
149
- ### Modified files
150
-
151
- 1. `src/ui/agent-activity-tracker.ts` — Remove `_toolUses`, `_lifetimeUsage`, `onToolEnd`, `onUsageUpdate`, `toolUses` getter, `lifetimeUsage` getter.
152
- Rename remaining tool-end logic to `onToolDone`.
153
- Remove `addUsage` and `UsageDelta` imports.
154
- 2. `src/ui/ui-observer.ts` — Call `tracker.onToolDone` instead of `tracker.onToolEnd`.
155
- Remove `message_end` usage accumulation block.
156
- 3. `src/agent-record.ts` — Add `get session()` and `get outputFile()` convenience getters.
157
- Import `AgentSession` type for the return type.
158
- 4. `src/notification.ts` — Remove `NotificationDeps` interface.
159
- Change `NotificationManager` constructor to plain parameters.
160
- Replace `this.deps.*` with `this.*`.
161
- Change `record.execution?.session` → `record.session`, `record.execution?.outputFile` → `record.outputFile`.
162
- 5. `src/ui/agent-widget.ts` — Read `a.toolUses`, `a.lifetimeUsage`, `a.session` instead of `bg?.toolUses ?? a.toolUses` etc.
163
- 6. `src/ui/conversation-viewer.ts` — Read `this.record.toolUses`, `this.record.lifetimeUsage`, `this.record.session` instead of fallback pattern.
164
- 7. `src/tools/get-result-tool.ts` — `record.execution?.session` → `record.session`.
165
- 8. `src/tools/steer-tool.ts` — `record.execution?.session` → `record.session`.
166
- 9. `src/ui/agent-menu.ts` — `record.execution?.session` → `record.session`.
167
- 10. `src/service-adapter.ts` — `record.execution?.session` → `record.session`.
168
- 11. `src/agent-manager.ts` — `record.execution?.session` → `record.session` in dispose paths.
169
- Keep the `record.execution = { session, outputFile }` assignment unchanged (that's the write path).
170
- 12. `src/tools/foreground-runner.ts` — Read `record.toolUses` and `formatLifetimeTokens(record)` for final result.
171
- Capture `recordRef` for streaming phase.
172
- 13. `src/tools/helpers.ts` — `buildDetails`: read `record.toolUses` and `record.lifetimeUsage` (already does); remove optional `activity` parameter's usage of `toolUses`/`lifetimeUsage` if present (verify).
173
- 14. `src/index.ts` — Update `NotificationManager` construction from bag to plain parameters.
174
- 15. `src/tools/background-spawner.ts` — `record?.execution?.outputFile` → `record?.outputFile`.
175
-
176
- ### Test files modified
177
-
178
- 1. `test/ui/agent-activity-tracker.test.ts` — Remove tests for `toolUses`, `lifetimeUsage`, `onToolEnd`, `onUsageUpdate`.
179
- Add tests for `onToolDone` (active-tool removal without counting).
180
- 2. `test/ui/ui-observer.test.ts` — Update `tool_execution_end` expectations (calls `onToolDone` not `onToolEnd`).
181
- Remove `message_end` usage accumulation tests.
182
- 3. `test/notification.test.ts` — Update `makeDeps()` factory to use plain parameters.
183
- Or update `NotificationManager` construction call to match new signature.
184
- 4. `test/agent-record.test.ts` — Add tests for `session` and `outputFile` getters.
185
- 5. `test/conversation-viewer.test.ts` — Remove mock activity tracker usage for stats; tests use record stats directly.
186
-
187
- ## Test Impact Analysis
188
-
189
- ### New unit tests enabled
190
-
191
- 1. `AgentRecord.session` and `AgentRecord.outputFile` getters — straightforward getter tests on the record class.
192
- 2. `AgentActivityTracker.onToolDone` — verifies active-tool removal without side effects on a counter.
193
-
194
- ### Existing tests that become simpler
195
-
196
- 1. `agent-activity-tracker.test.ts` — 4 test blocks for `toolUses`, `lifetimeUsage`, `onToolEnd`, `onUsageUpdate` can be removed.
197
- The `onToolDone` replacement needs fewer assertions (no counter check).
198
- 2. `ui-observer.test.ts` — The `message_end` usage accumulation tests can be removed.
199
- The `tool_execution_end` test simplifies (verifies active-tool removal only).
200
- 3. `notification.test.ts` — The `makeDeps()` helper changes shape but stays the same size.
201
- 4. `conversation-viewer.test.ts` — Tests that mock `activity.toolUses` and `activity.lifetimeUsage` can simplify to reading from the record.
202
-
203
- ### Existing tests that must stay
204
-
205
- 1. `record-observer.test.ts` — All tests remain as-is.
206
- The record observer is the sole source of `incrementToolUses` and `addUsage` calls now.
207
- 2. `agent-record.test.ts` — All existing tests for `incrementToolUses`, `addUsage`, `incrementCompactions` remain.
208
- 3. `notification.test.ts` — Pure helper tests (`escapeXml`, `getStatusLabel`, `formatTaskNotification`, `buildNotificationDetails`, `buildEventData`) remain unchanged.
209
-
210
- ## TDD Order
211
-
212
- 1. **Red→Green: `AgentRecord` convenience getters.**
213
- Add tests for `record.session` and `record.outputFile` (returns `undefined` when no execution, delegates when set).
214
- Implement the getters.
215
- Commit: `feat: add session and outputFile convenience getters to AgentRecord (#144)`
216
-
217
- 2. **Green→Green: Migrate callsites from `execution?.` to convenience getters.**
218
- Update all 12 callsites across 7 files (`notification.ts`, `agent-widget.ts`, `agent-menu.ts`, `get-result-tool.ts`, `steer-tool.ts`, `service-adapter.ts`, `agent-manager.ts`, `background-spawner.ts`).
219
- No test changes needed — existing tests pass.
220
- Run `pnpm run check` to verify.
221
- Commit: `refactor: use AgentRecord.session and .outputFile convenience getters (#144)`
222
-
223
- 3. **Red→Green: Rename `onToolEnd` to `onToolDone` and remove counter.**
224
- Update `agent-activity-tracker.test.ts`: remove `onToolEnd` counter tests, add `onToolDone` tests (active-tool removal only, no `toolUses` assertion).
225
- Implement: rename method, remove `_toolUses++`.
226
- Update `ui-observer.ts` to call `onToolDone`.
227
- Update `ui-observer.test.ts` expectations.
228
- Commit: `refactor: rename AgentActivityTracker.onToolEnd to onToolDone (#144)`
229
-
230
- 4. **Red→Green: Remove `_toolUses` and `_lifetimeUsage` from tracker.**
231
- Update `agent-activity-tracker.test.ts`: remove `toolUses` getter, `lifetimeUsage` getter, and `onUsageUpdate` test blocks.
232
- Implement: remove `_toolUses`, `_lifetimeUsage`, `toolUses` getter, `lifetimeUsage` getter, `onUsageUpdate` method, `UsageDelta` type, `addUsage` import.
233
- Update `ui-observer.ts`: remove `message_end` usage accumulation block.
234
- Update `ui-observer.test.ts`: remove `message_end` usage tests.
235
- Run `pnpm run check` to catch any remaining references.
236
- Commit: `refactor: remove duplicate stats from AgentActivityTracker (#144)`
237
-
238
- 5. **Green→Green: Migrate UI consumers to read stats from `AgentRecord`.**
239
- Update `agent-widget.ts`: replace `bg?.toolUses ?? a.toolUses` → `a.toolUses`, `bg?.lifetimeUsage` → `a.lifetimeUsage`, `bg?.session` → `a.session`.
240
- Update `conversation-viewer.ts`: replace `activity?.toolUses ?? record.toolUses` → `record.toolUses`, `activity?.lifetimeUsage` → `record.lifetimeUsage`, `activity?.session` → `record.session`.
241
- Update `foreground-runner.ts`: read stats from record instead of tracker for final result.
242
- Capture `recordRef` for streaming phase.
243
- Update `conversation-viewer.test.ts` if any tests mock activity stats.
244
- Commit: `refactor: read stats from AgentRecord in UI consumers (#144)`
245
-
246
- 6. **Red→Green: Replace `NotificationDeps` with plain parameters.**
247
- Update `notification.test.ts`: change `makeDeps()` → individual parameters in `NotificationManager` constructor calls.
248
- Implement: remove `NotificationDeps` interface, change constructor to plain parameters, replace `this.deps.*` with `this.*`.
249
- Update `index.ts`: change `NotificationManager` construction to pass individual arguments.
250
- Commit: `refactor: dissolve NotificationDeps into plain constructor parameters (#144)`
251
-
252
- ## Risks and Mitigations
253
-
254
- | Risk | Mitigation |
255
- | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
256
- | Foreground streaming reads stale `recordRef` before `onSessionCreated` fires | Before `onSessionCreated`, both tracker and record have zero stats — behavior is unchanged. Capture `recordRef` as `undefined` initially and guard with `??`. |
257
- | Widget reads `a.toolUses` but `a` is the snapshot from `listAgents()` — might be stale | `listAgents()` returns live `AgentRecord` references (not copies), so `a.toolUses` reflects the latest record-observer increment. |
258
- | Removing `lifetimeUsage` from tracker breaks `formatLifetimeTokens(fgState)` in foreground runner | Step 5 explicitly migrates this callsite to `formatLifetimeTokens(record)`. The TDD order places the removal (step 4) before the migration (step 5), but step 4's `pnpm run check` will catch the type error and both steps can be merged if needed. |
259
- | `buildDetails` in `helpers.ts` accepts an `activity` parameter for `turnCount`/`maxTurns` | `turnCount` and `maxTurns` remain on the tracker — only `toolUses` and `lifetimeUsage` are removed. `buildDetails` reads `record.toolUses` and `record.lifetimeUsage` already; it reads `activity?.turnCount` and `activity?.maxTurns` which remain valid. |
260
-
261
- ## Open Questions
262
-
263
- - None — the architecture doc prescribes the exact changes and the design is straightforward.