@gotgenes/pi-subagents 18.0.1 → 18.0.3

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 (268) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/docs/architecture/architecture.md +165 -203
  3. package/docs/architecture/history/phase-19-implement-ui-decisions.md +282 -0
  4. package/package.json +6 -7
  5. package/src/config/default-agents.ts +3 -0
  6. package/src/lifecycle/subagent.ts +25 -14
  7. package/src/observation/notification.ts +23 -10
  8. package/src/observation/subagent-events-observer.ts +1 -3
  9. package/src/runtime.ts +2 -1
  10. package/src/service/service-adapter.ts +20 -17
  11. package/src/session/model-resolver.ts +31 -30
  12. package/src/tools/agent-tool.ts +17 -13
  13. package/src/tools/get-result-report.ts +60 -0
  14. package/src/tools/get-result-tool.ts +25 -40
  15. package/src/tools/helpers.ts +21 -5
  16. package/src/tools/spawn-config.ts +5 -5
  17. package/src/tools/steer-tool.ts +37 -27
  18. package/src/types.ts +4 -1
  19. package/src/ui/session-navigator.ts +0 -2
  20. package/.prettierignore +0 -5
  21. package/AGENTS.md +0 -7
  22. package/docs/plans/0048-implement-subagents-api.md +0 -304
  23. package/docs/plans/0049-remove-group-join-output-file-rpc.md +0 -180
  24. package/docs/plans/0051-update-adr-0001-hard-fork.md +0 -77
  25. package/docs/plans/0052-remove-scheduled-subagents.md +0 -133
  26. package/docs/plans/0053-extract-model-resolution-from-execute.md +0 -181
  27. package/docs/plans/0054-decompose-index-into-modules.md +0 -302
  28. package/docs/plans/0057-structured-debug-logging.md +0 -154
  29. package/docs/plans/0061-session-format-transcript.md +0 -284
  30. package/docs/plans/0066-replace-as-any-with-sdk-types.md +0 -215
  31. package/docs/plans/0069-create-subagent-runtime.md +0 -345
  32. package/docs/plans/0070-extract-event-handlers.md +0 -306
  33. package/docs/plans/0071-extract-session-config-assembler.md +0 -362
  34. package/docs/plans/0072-inject-agent-manager-collaborators.md +0 -329
  35. package/docs/plans/0076-inject-cwd-into-agent-manager.md +0 -102
  36. package/docs/plans/0077-inject-project-agents-dir.md +0 -115
  37. package/docs/plans/0080-consolidate-agent-config-lookup.md +0 -247
  38. package/docs/plans/0084-extract-git-worktree-manager.md +0 -142
  39. package/docs/plans/0087-evolve-subagent-runtime-methods.md +0 -240
  40. package/docs/plans/0098-extract-agent-record-state-machine.md +0 -435
  41. package/docs/plans/0099-replace-ctx-with-parent-snapshot.md +0 -488
  42. package/docs/plans/0100-replace-callback-threading-with-session-subscription.md +0 -454
  43. package/docs/plans/0102-consolidate-test-record-factory.md +0 -176
  44. package/docs/plans/0108-extract-agent-type-registry.md +0 -322
  45. package/docs/plans/0109-extract-settings-manager.md +0 -276
  46. package/docs/plans/0110-agent-activity-tracker.md +0 -297
  47. package/docs/plans/0111-split-agent-record-lifecycle.md +0 -582
  48. package/docs/plans/0112-replace-agent-manager-callbacks.md +0 -241
  49. package/docs/plans/0113-disambiguate-spawn-options.md +0 -155
  50. package/docs/plans/0114-narrow-agent-tool-menu-deps.md +0 -279
  51. package/docs/plans/0115-decompose-agent-tool.md +0 -337
  52. package/docs/plans/0116-type-housekeeping.md +0 -351
  53. package/docs/plans/0118-settings-manager-apply-methods.md +0 -271
  54. package/docs/plans/0123-remove-vi-fn-cast-smell.md +0 -179
  55. package/docs/plans/0131-consolidate-shared-test-fixtures.md +0 -207
  56. package/docs/plans/0132-inject-io-into-session-config.md +0 -219
  57. package/docs/plans/0133-inject-sdk-boundary-into-agent-runner.md +0 -373
  58. package/docs/plans/0134-reduce-as-any-casts.md +0 -366
  59. package/docs/plans/0135-extract-display-helpers.md +0 -182
  60. package/docs/plans/0136-decompose-agent-menu.md +0 -300
  61. package/docs/plans/0144-consolidate-observation-model.md +0 -263
  62. package/docs/plans/0145-decompose-execute-push-ctx-to-boundary.md +0 -290
  63. package/docs/plans/0146-narrow-ui-context.md +0 -319
  64. package/docs/plans/0147-inject-wrap-text-into-conversation-viewer.md +0 -166
  65. package/docs/plans/0148-split-agent-widget-rendering.md +0 -255
  66. package/docs/plans/0152-add-prompt-snippet.md +0 -91
  67. package/docs/plans/0164-reorganize-into-domain-directories.md +0 -409
  68. package/docs/plans/0165-decompose-resolved-spawn-config.md +0 -157
  69. package/docs/plans/0166-extract-parent-session-info.md +0 -231
  70. package/docs/plans/0167-narrow-runner-io.md +0 -150
  71. package/docs/plans/0168-extract-tool-filter-config.md +0 -173
  72. package/docs/plans/0169-extract-run-context-from-run-options.md +0 -194
  73. package/docs/plans/0170-reduce-build-content-lines-complexity.md +0 -225
  74. package/docs/plans/0171-reduce-render-result-complexity.md +0 -200
  75. package/docs/plans/0172-extract-turn-formatting.md +0 -206
  76. package/docs/plans/0180-reorder-append-prompt-for-kv-cache.md +0 -100
  77. package/docs/plans/0185-remove-persistent-agent-memory.md +0 -161
  78. package/docs/plans/0188-replace-any-casts-with-sdk-types.md +0 -162
  79. package/docs/plans/0192-define-session-context-interface.md +0 -107
  80. package/docs/plans/0193-runtime-owns-context-queries.md +0 -245
  81. package/docs/plans/0194-align-tool-interfaces-for-structural-typing.md +0 -139
  82. package/docs/plans/0195-convert-tool-factories-to-classes.md +0 -264
  83. package/docs/plans/0196-convert-runner-menu-to-classes.md +0 -268
  84. package/docs/plans/0205-decompose-render-widget-lines.md +0 -140
  85. package/docs/plans/0206-decompose-show-agent-detail.md +0 -211
  86. package/docs/plans/0207-decompose-agent-widget-update.md +0 -242
  87. package/docs/plans/0208-extract-shared-test-fixtures.md +0 -298
  88. package/docs/plans/0214-convert-remaining-closure-factories-to-classes.md +0 -261
  89. package/docs/plans/0215-decompose-build-parent-context.md +0 -166
  90. package/docs/plans/0216-decompose-start-agent.md +0 -255
  91. package/docs/plans/0217-extract-overwrite-guard.md +0 -176
  92. package/docs/plans/0218-push-sdk-boundary-in-settings.md +0 -172
  93. package/docs/plans/0219-reduce-test-duplication-top-3-clone-families.md +0 -162
  94. package/docs/plans/0227-evolve-agent-record-into-agent.md +0 -322
  95. package/docs/plans/0228-async-start-agent-dissolve-run-handle.md +0 -288
  96. package/docs/plans/0229-agent-born-complete.md +0 -564
  97. package/docs/plans/0230-extract-concurrency-queue.md +0 -265
  98. package/docs/plans/0231-push-exec-registry-to-runner.md +0 -245
  99. package/docs/plans/0232-agent-resume-internal-observer-lifecycle.md +0 -180
  100. package/docs/plans/0237-remove-disallowed-tools.md +0 -180
  101. package/docs/plans/0238-remove-extensions-filtering.md +0 -191
  102. package/docs/plans/0239-collapse-filter-active-tools.md +0 -217
  103. package/docs/plans/0242-rename-agent-tool-to-subagent.md +0 -165
  104. package/docs/plans/0256-extract-worktree-isolation.md +0 -256
  105. package/docs/plans/0257-extract-child-session-factory.md +0 -285
  106. package/docs/plans/0262-add-workspace-provider-seam.md +0 -264
  107. package/docs/plans/0264-remove-extension-lifecycle-control.md +0 -277
  108. package/docs/plans/0265-born-complete-subagent-session.md +0 -333
  109. package/docs/plans/0270-type-consumable-public-surface.md +0 -204
  110. package/docs/plans/0272-export-workspace-collaborator-types.md +0 -147
  111. package/docs/plans/0277-encapsulate-agent-session.md +0 -304
  112. package/docs/plans/0280-rename-agent-to-subagent.md +0 -197
  113. package/docs/plans/0360-custom-agents-default-append.md +0 -132
  114. package/docs/plans/0373-extract-subagent-state.md +0 -250
  115. package/docs/plans/0374-encapsulate-subagent-start-notification.md +0 -268
  116. package/docs/plans/0375-extract-run-listener-workspace-bracket.md +0 -300
  117. package/docs/plans/0376-extract-manager-observer.md +0 -232
  118. package/docs/plans/0377-split-widget-delegation-from-runtime.md +0 -254
  119. package/docs/plans/0378-consolidate-lifecycle-test-fixtures.md +0 -243
  120. package/docs/plans/0379-consolidate-ui-tools-test-fixtures.md +0 -335
  121. package/docs/plans/0380-extract-layered-settings-loader.md +0 -223
  122. package/docs/plans/0381-replace-concurrency-queue-with-limiter.md +0 -267
  123. package/docs/plans/0400-include-parent-prompt-in-replace-mode.md +0 -199
  124. package/docs/plans/0403-abort-subagents-on-interrupt.md +0 -180
  125. package/docs/plans/0412-unify-session-mock-builders.md +0 -146
  126. package/docs/plans/0420-fold-run-metrics-live-activity-onto-record.md +0 -228
  127. package/docs/plans/0421-migrate-activity-readers-to-record-getters.md +0 -309
  128. package/docs/plans/0422-delete-activity-tracker-ui-observer.md +0 -204
  129. package/docs/plans/0423-widget-self-drive-from-lifecycle.md +0 -264
  130. package/docs/plans/0424-drop-widget-dep-from-subagent-tool.md +0 -166
  131. package/docs/plans/0425-reconcile-subagent-events-contract.md +0 -144
  132. package/docs/plans/0426-consolidate-residual-test-clone-families.md +0 -235
  133. package/docs/plans/0427-reconsider-ui-direction.md +0 -212
  134. package/docs/plans/0441-remove-agent-definition-management-subtree.md +0 -180
  135. package/docs/plans/0442-dissolve-agents-conversation-viewer.md +0 -195
  136. package/docs/plans/0443-consolidate-remaining-test-clone-families.md +0 -268
  137. package/docs/plans/0444-shrink-widget-to-background-agents.md +0 -156
  138. package/docs/plans/0445-native-session-navigation.md +0 -365
  139. package/docs/plans/0446-spike-session-navigation-entry-criteria.md +0 -171
  140. package/docs/plans/0447-extract-subagents-settings-command.md +0 -220
  141. package/docs/plans/0448-block-disabled-agent-spawn.md +0 -207
  142. package/docs/plans/0462-navigation-renderer-tui-components.md +0 -338
  143. package/docs/plans/0463-file-snapshot-source-evicted-agents.md +0 -306
  144. package/docs/plans/0470-refresh-readme-command-surface.md +0 -159
  145. package/docs/retro/0048-implement-subagents-api.md +0 -44
  146. package/docs/retro/0049-remove-group-join-output-file-rpc.md +0 -49
  147. package/docs/retro/0051-update-adr-0001-hard-fork.md +0 -39
  148. package/docs/retro/0053-extract-model-resolution-from-execute.md +0 -40
  149. package/docs/retro/0054-decompose-index-into-modules.md +0 -53
  150. package/docs/retro/0057-structured-debug-logging.md +0 -77
  151. package/docs/retro/0061-session-format-transcript.md +0 -41
  152. package/docs/retro/0066-replace-as-any-with-sdk-types.md +0 -44
  153. package/docs/retro/0069-create-subagent-runtime.md +0 -43
  154. package/docs/retro/0070-extract-event-handlers.md +0 -49
  155. package/docs/retro/0071-extract-session-config-assembler.md +0 -60
  156. package/docs/retro/0072-inject-agent-manager-collaborators.md +0 -46
  157. package/docs/retro/0076-inject-cwd-into-agent-manager.md +0 -35
  158. package/docs/retro/0077-inject-project-agents-dir.md +0 -33
  159. package/docs/retro/0080-consolidate-agent-config-lookup.md +0 -35
  160. package/docs/retro/0084-extract-git-worktree-manager.md +0 -37
  161. package/docs/retro/0087-evolve-subagent-runtime-methods.md +0 -42
  162. package/docs/retro/0098-extract-agent-record-state-machine.md +0 -46
  163. package/docs/retro/0099-replace-ctx-with-parent-snapshot.md +0 -37
  164. package/docs/retro/0100-replace-callback-threading-with-session-subscription.md +0 -36
  165. package/docs/retro/0102-consolidate-test-record-factory.md +0 -30
  166. package/docs/retro/0108-extract-agent-type-registry.md +0 -41
  167. package/docs/retro/0109-extract-settings-manager.md +0 -55
  168. package/docs/retro/0110-agent-activity-tracker.md +0 -44
  169. package/docs/retro/0111-split-agent-record-lifecycle.md +0 -61
  170. package/docs/retro/0112-replace-agent-manager-callbacks.md +0 -35
  171. package/docs/retro/0113-disambiguate-spawn-options.md +0 -29
  172. package/docs/retro/0114-narrow-agent-tool-menu-deps.md +0 -38
  173. package/docs/retro/0115-decompose-agent-tool.md +0 -51
  174. package/docs/retro/0116-type-housekeeping.md +0 -42
  175. package/docs/retro/0118-settings-manager-apply-methods.md +0 -40
  176. package/docs/retro/0123-remove-vi-fn-cast-smell.md +0 -49
  177. package/docs/retro/0131-consolidate-shared-test-fixtures.md +0 -46
  178. package/docs/retro/0132-inject-io-into-session-config.md +0 -33
  179. package/docs/retro/0133-inject-sdk-boundary-into-agent-runner.md +0 -45
  180. package/docs/retro/0134-reduce-as-any-casts.md +0 -56
  181. package/docs/retro/0135-extract-display-helpers.md +0 -38
  182. package/docs/retro/0136-decompose-agent-menu.md +0 -43
  183. package/docs/retro/0144-consolidate-observation-model.md +0 -39
  184. package/docs/retro/0145-decompose-execute-push-ctx-to-boundary.md +0 -56
  185. package/docs/retro/0146-narrow-ui-context.md +0 -70
  186. package/docs/retro/0147-inject-wrap-text-into-conversation-viewer.md +0 -90
  187. package/docs/retro/0148-split-agent-widget-rendering.md +0 -39
  188. package/docs/retro/0152-add-prompt-snippet.md +0 -34
  189. package/docs/retro/0164-reorganize-into-domain-directories.md +0 -82
  190. package/docs/retro/0165-decompose-resolved-spawn-config.md +0 -78
  191. package/docs/retro/0166-extract-parent-session-info.md +0 -66
  192. package/docs/retro/0167-narrow-runner-io.md +0 -63
  193. package/docs/retro/0168-extract-tool-filter-config.md +0 -76
  194. package/docs/retro/0169-extract-run-context-from-run-options.md +0 -68
  195. package/docs/retro/0170-reduce-build-content-lines-complexity.md +0 -72
  196. package/docs/retro/0171-reduce-render-result-complexity.md +0 -77
  197. package/docs/retro/0172-extract-turn-formatting.md +0 -80
  198. package/docs/retro/0180-reorder-append-prompt-for-kv-cache.md +0 -62
  199. package/docs/retro/0185-remove-persistent-agent-memory.md +0 -73
  200. package/docs/retro/0188-replace-any-casts-with-sdk-types.md +0 -69
  201. package/docs/retro/0192-define-session-context-interface.md +0 -59
  202. package/docs/retro/0193-runtime-owns-context-queries.md +0 -75
  203. package/docs/retro/0194-align-tool-interfaces-for-structural-typing.md +0 -62
  204. package/docs/retro/0195-convert-tool-factories-to-classes.md +0 -84
  205. package/docs/retro/0196-convert-runner-menu-to-classes.md +0 -73
  206. package/docs/retro/0205-decompose-render-widget-lines.md +0 -64
  207. package/docs/retro/0206-decompose-show-agent-detail.md +0 -67
  208. package/docs/retro/0207-decompose-agent-widget-update.md +0 -92
  209. package/docs/retro/0208-extract-shared-test-fixtures.md +0 -102
  210. package/docs/retro/0214-convert-remaining-closure-factories-to-classes.md +0 -66
  211. package/docs/retro/0215-decompose-build-parent-context.md +0 -61
  212. package/docs/retro/0216-decompose-start-agent.md +0 -80
  213. package/docs/retro/0217-extract-overwrite-guard.md +0 -63
  214. package/docs/retro/0218-push-sdk-boundary-in-settings.md +0 -72
  215. package/docs/retro/0219-reduce-test-duplication-top-3-clone-families.md +0 -64
  216. package/docs/retro/0227-evolve-agent-record-into-agent.md +0 -80
  217. package/docs/retro/0228-async-start-agent-dissolve-run-handle.md +0 -80
  218. package/docs/retro/0229-agent-born-complete.md +0 -89
  219. package/docs/retro/0230-extract-concurrency-queue.md +0 -38
  220. package/docs/retro/0231-push-exec-registry-to-runner.md +0 -71
  221. package/docs/retro/0232-agent-resume-internal-observer-lifecycle.md +0 -109
  222. package/docs/retro/0237-remove-disallowed-tools.md +0 -78
  223. package/docs/retro/0238-remove-extensions-filtering.md +0 -72
  224. package/docs/retro/0239-collapse-filter-active-tools.md +0 -70
  225. package/docs/retro/0242-rename-agent-tool-to-subagent.md +0 -82
  226. package/docs/retro/0256-extract-worktree-isolation.md +0 -89
  227. package/docs/retro/0257-extract-child-session-factory.md +0 -33
  228. package/docs/retro/0262-add-workspace-provider-seam.md +0 -89
  229. package/docs/retro/0264-remove-extension-lifecycle-control.md +0 -91
  230. package/docs/retro/0265-born-complete-subagent-session.md +0 -97
  231. package/docs/retro/0270-type-consumable-public-surface.md +0 -108
  232. package/docs/retro/0272-export-workspace-collaborator-types.md +0 -38
  233. package/docs/retro/0277-encapsulate-agent-session.md +0 -80
  234. package/docs/retro/0280-rename-agent-to-subagent.md +0 -96
  235. package/docs/retro/0360-custom-agents-default-append.md +0 -81
  236. package/docs/retro/0373-extract-subagent-state.md +0 -94
  237. package/docs/retro/0374-encapsulate-subagent-start-notification.md +0 -171
  238. package/docs/retro/0375-extract-run-listener-workspace-bracket.md +0 -104
  239. package/docs/retro/0376-extract-manager-observer.md +0 -81
  240. package/docs/retro/0377-split-widget-delegation-from-runtime.md +0 -105
  241. package/docs/retro/0378-consolidate-lifecycle-test-fixtures.md +0 -100
  242. package/docs/retro/0379-consolidate-ui-tools-test-fixtures.md +0 -52
  243. package/docs/retro/0380-extract-layered-settings-loader.md +0 -97
  244. package/docs/retro/0381-replace-concurrency-queue-with-limiter.md +0 -95
  245. package/docs/retro/0400-include-parent-prompt-in-replace-mode.md +0 -84
  246. package/docs/retro/0403-abort-subagents-on-interrupt.md +0 -90
  247. package/docs/retro/0412-unify-session-mock-builders.md +0 -80
  248. package/docs/retro/0420-fold-run-metrics-live-activity-onto-record.md +0 -90
  249. package/docs/retro/0421-migrate-activity-readers-to-record-getters.md +0 -105
  250. package/docs/retro/0422-delete-activity-tracker-ui-observer.md +0 -97
  251. package/docs/retro/0423-widget-self-drive-from-lifecycle.md +0 -112
  252. package/docs/retro/0424-drop-widget-dep-from-subagent-tool.md +0 -89
  253. package/docs/retro/0425-reconcile-subagent-events-contract.md +0 -101
  254. package/docs/retro/0426-consolidate-residual-test-clone-families.md +0 -100
  255. package/docs/retro/0427-reconsider-ui-direction.md +0 -102
  256. package/docs/retro/0441-remove-agent-definition-management-subtree.md +0 -97
  257. package/docs/retro/0442-dissolve-agents-conversation-viewer.md +0 -108
  258. package/docs/retro/0443-consolidate-remaining-test-clone-families.md +0 -28
  259. package/docs/retro/0444-shrink-widget-to-background-agents.md +0 -81
  260. package/docs/retro/0445-native-session-navigation.md +0 -122
  261. package/docs/retro/0446-spike-session-navigation-entry-criteria.md +0 -99
  262. package/docs/retro/0447-extract-subagents-settings-command.md +0 -91
  263. package/docs/retro/0448-block-disabled-agent-spawn.md +0 -75
  264. package/docs/retro/0462-navigation-renderer-tui-components.md +0 -105
  265. package/docs/retro/0463-file-snapshot-source-evicted-agents.md +0 -103
  266. package/docs/retro/0470-refresh-readme-command-surface.md +0 -40
  267. package/src/observation/notification-state.ts +0 -27
  268. 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.