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