@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,454 +0,0 @@
1
- ---
2
- issue: 100
3
- issue_title: "Replace callback threading with direct session-event subscription"
4
- ---
5
-
6
- # Replace callback threading with direct session-event subscription
7
-
8
- ## Problem Statement
9
-
10
- `SpawnOptions` carries 6 `on*` callback fields that thread through three layers: `agent-tool.ts` creates activity-tracking callbacks → `AgentManager.startAgent()` wraps each to update the record, then forwards → `runner.run()` subscribes to session events and translates them into callback invocations.
11
- The session already emits all of these events via `session.subscribe()`.
12
- Three layers reimplement what two independent subscriptions could provide.
13
-
14
- The same pattern repeats in `resume()`, which hand-rolls 3 of the 4 callback wrappers.
15
-
16
- ## Goals
17
-
18
- - Replace the 3-layer callback chain with two direct session subscribers: a **record observer** (AgentManager, updates record stats) and a **UI observer** (agent-tool, streams widget state).
19
- - Drop 5 `on*` callback fields from `SpawnOptions` (keep `onSessionCreated` as the session-delivery mechanism).
20
- - Drop 5 `on*` callback fields from `RunOptions`.
21
- - Drop 3 `on*` callback fields from `ResumeOptions`.
22
- - Simplify `startAgent()` from ~80 lines of callback wiring to ~20 lines of observer setup.
23
- - Eliminate duplicated callback wiring between `startAgent()` and `resume()`.
24
-
25
- ## Non-Goals
26
-
27
- - Extracting turn-limit enforcement from the runner — it stays as the runner's own subscription, just simplified.
28
- A follow-up could extract it into a `turn-limiter.ts` module.
29
- - Changing the public `SubagentsService` API in `service.ts` — the public `SpawnOptions` type is unaffected.
30
- - Removing `onSessionCreated` — it remains as the session-delivery bridge between the runner and external subscribers.
31
- - Changing `collectResponseText` or `forwardAbortSignal` in the runner — those are the runner's own concerns.
32
-
33
- ## Background
34
-
35
- ### Prerequisite status
36
-
37
- - Issue #98 (AgentRecord state machine) — **done**.
38
- `AgentRecord` is a class with encapsulated transition methods (`markRunning`, `markCompleted`, `markError`, etc.).
39
- The record observer uses public mutable fields (`toolUses`, `lifetimeUsage`, `compactionCount`) and the `addUsage` helper.
40
- - Issue #99 (ParentSnapshot) — **done**.
41
- `runner.run()` accepts `ParentSnapshot`, `SpawnArgs` stores a snapshot, `AgentManager` has `exec: ShellExec` injected.
42
-
43
- ### Current callback threading
44
-
45
- In `startAgent()`, AgentManager wraps 4 of the 6 callbacks to interleave record mutations:
46
-
47
- | Callback | Record mutation | Forwarding |
48
- | ------------------ | ------------------------------------------------------------- | ------------------------------------- |
49
- | `onToolActivity` | `record.toolUses++` on end | Forward to `options.onToolActivity` |
50
- | `onAssistantUsage` | `addUsage(record.lifetimeUsage, …)` | Forward to `options.onAssistantUsage` |
51
- | `onCompaction` | `record.compactionCount++` + `this.onCompact?.(record, info)` | Forward to `options.onCompaction` |
52
- | `onSessionCreated` | Store session, capture file, flush steers | Forward to `options.onSessionCreated` |
53
- | `onTextDelta` | None | Pass-through |
54
- | `onTurnEnd` | None | Pass-through |
55
-
56
- In `resume()`, 3 callbacks are wired manually (onToolActivity, onAssistantUsage, onCompaction) with no forwarding — only record mutations.
57
-
58
- The runner's big `session.subscribe()` block (lines 323–370 of `agent-runner.ts`) mixes turn-limit enforcement with event→callback translation for all 6 callback types.
59
-
60
- ### Code-design constraints
61
-
62
- - **Parameter relay** (code-design skill): the 5 UI callbacks thread through `SpawnOptions` → `startAgent()` wrapping → `RunOptions` → runner subscription.
63
- The intermediary (`startAgent`) only uses them to interleave record mutations.
64
- - **Scattered resets** (design-review): `resume()` hand-rolls 3 of the same 4 callback wrappers from `startAgent()`.
65
-
66
- ### Relevant modules
67
-
68
- | Module | Role in this change |
69
- | ------------------------- | -------------------------------------------------------------------- |
70
- | `src/agent-manager.ts` | Owns `SpawnOptions`, `startAgent()`, `resume()` — primary target |
71
- | `src/agent-runner.ts` | Owns `RunOptions`, `ResumeOptions`, runner subscription — simplifies |
72
- | `src/tools/agent-tool.ts` | Owns `createActivityTracker()` — replaced by UI observer |
73
- | `src/ui/agent-widget.ts` | Defines `AgentActivity` interface — unchanged |
74
- | `src/agent-record.ts` | `AgentRecord` class — unchanged (observer writes to public fields) |
75
- | `src/usage.ts` | `addUsage` helper — unchanged, used by record observer |
76
-
77
- ## Design Overview
78
-
79
- ### Two independent observers
80
-
81
- ```text
82
- session.subscribe()
83
-
84
- ┌─────────────┼─────────────┐
85
- │ │
86
- Record observer UI observer
87
- (accumulates stats on record) (updates widget state)
88
- managed by AgentManager managed by agent-tool
89
- subscribes in onSessionCreated subscribes in onSessionCreated
90
- unsubscribes in .then/.catch unsubscribes on completion
91
- ```
92
-
93
- Both subscribe to the same session but update independent state.
94
- Neither wraps or forwards the other's events.
95
-
96
- ### Record observer
97
-
98
- New module `src/record-observer.ts`:
99
-
100
- ```typescript
101
- export interface RecordObserverOptions {
102
- onCompact?: (record: AgentRecord, info: CompactionInfo) => void;
103
- }
104
-
105
- export function subscribeRecordObserver(
106
- session: AgentSession,
107
- record: AgentRecord,
108
- options?: RecordObserverOptions,
109
- ): () => void;
110
- ```
111
-
112
- Handles three event types:
113
-
114
- | Session event | Record mutation |
115
- | ------------------------------------------- | ---------------------------------------------------- |
116
- | `tool_execution_end` | `record.toolUses++` |
117
- | `message_end` (assistant, with usage) | `addUsage(record.lifetimeUsage, usage)` |
118
- | `compaction_end` (not aborted, with result) | `record.compactionCount++`, call `options.onCompact` |
119
-
120
- These are the exact mutations currently scattered across `startAgent()` and `resume()` callback wrappers.
121
-
122
- The returned function unsubscribes from the session.
123
-
124
- ### UI observer
125
-
126
- New module `src/ui/ui-observer.ts`:
127
-
128
- ```typescript
129
- export function subscribeUIObserver(
130
- session: AgentSession,
131
- state: AgentActivity,
132
- onUpdate?: () => void,
133
- ): () => void;
134
- ```
135
-
136
- Handles six event types — the same events currently translated by `createActivityTracker`'s callbacks:
137
-
138
- | Session event | State mutation |
139
- | ------------------------------------- | --------------------------------------------------- |
140
- | `tool_execution_start` | Add to `state.activeTools` |
141
- | `tool_execution_end` | Remove from `state.activeTools`, `state.toolUses++` |
142
- | `message_start` | Reset `state.responseText` |
143
- | `message_update` (text_delta) | Append to `state.responseText` |
144
- | `turn_end` | `state.turnCount++` |
145
- | `message_end` (assistant, with usage) | `addUsage(state.lifetimeUsage, usage)` |
146
-
147
- Calls `onUpdate?.()` after each mutation (matching current `onStreamUpdate` behavior for foreground rendering).
148
-
149
- The returned function unsubscribes from the session.
150
-
151
- ### SpawnOptions after the change
152
-
153
- ```typescript
154
- export interface SpawnOptions {
155
- description: string;
156
- model?: Model<any>;
157
- maxTurns?: number;
158
- isolated?: boolean;
159
- inheritContext?: boolean;
160
- thinkingLevel?: ThinkingLevel;
161
- isBackground?: boolean;
162
- bypassQueue?: boolean;
163
- isolation?: IsolationMode;
164
- invocation?: AgentInvocation;
165
- signal?: AbortSignal;
166
- parentSessionFile?: string;
167
- parentSessionId?: string;
168
- /** Called when the session is created — the one remaining callback. */
169
- onSessionCreated?: (session: AgentSession) => void;
170
- }
171
- ```
172
-
173
- Drops: `onToolActivity`, `onTextDelta`, `onTurnEnd`, `onAssistantUsage`, `onCompaction`.
174
-
175
- ### RunOptions after the change
176
-
177
- ```typescript
178
- export interface RunOptions {
179
- exec: ShellExec;
180
- model?: Model<any>;
181
- maxTurns?: number;
182
- signal?: AbortSignal;
183
- isolated?: boolean;
184
- thinkingLevel?: ThinkingLevel;
185
- cwd?: string;
186
- parentSessionFile?: string;
187
- parentSessionId?: string;
188
- defaultMaxTurns?: number;
189
- graceTurns?: number;
190
- /** Called once after session creation — session delivery mechanism. */
191
- onSessionCreated?: (session: AgentSession) => void;
192
- }
193
- ```
194
-
195
- Drops: `onToolActivity`, `onTextDelta`, `onTurnEnd`, `onAssistantUsage`, `onCompaction`.
196
-
197
- ### ResumeOptions after the change
198
-
199
- ```typescript
200
- export interface ResumeOptions {
201
- signal?: AbortSignal;
202
- }
203
- ```
204
-
205
- Drops: `onToolActivity`, `onAssistantUsage`, `onCompaction`.
206
-
207
- ### Runner simplification
208
-
209
- The runner's big `session.subscribe()` block in `runAgent()` (currently ~50 lines mixing turn-limit enforcement with callback forwarding) simplifies to turn-limit enforcement only (~15 lines):
210
-
211
- ```typescript
212
- const unsubTurns = session.subscribe((event) => {
213
- if (event.type === "turn_end") {
214
- turnCount++;
215
- if (maxTurns != null) {
216
- if (!softLimitReached && turnCount >= maxTurns) {
217
- softLimitReached = true;
218
- session.steer("...");
219
- } else if (softLimitReached && turnCount >= maxTurns + grace) {
220
- aborted = true;
221
- session.abort();
222
- }
223
- }
224
- }
225
- });
226
- ```
227
-
228
- `collectResponseText(session)` and `forwardAbortSignal(session, signal)` remain unchanged — they are the runner's own concerns.
229
-
230
- `resumeAgent()` drops its conditional event subscription entirely — only `collectResponseText` and `forwardAbortSignal` remain.
231
-
232
- ### Unsubscription strategy
233
-
234
- | Observer | Subscribe point | Unsubscribe point |
235
- | ------------------- | -------------------------- | ---------------------------- |
236
- | Record (startAgent) | `onSessionCreated` handler | `.then()` and `.catch()` |
237
- | Record (resume) | Before `runner.resume()` | `finally` block |
238
- | UI (foreground) | `onSessionCreated` handler | After `spawnAndWait` returns |
239
- | UI (background) | `onSessionCreated` handler | Session disposal on cleanup |
240
-
241
- ### createActivityTracker replacement
242
-
243
- `createActivityTracker()` in `agent-tool.ts` is replaced by:
244
-
245
- 1. Inline `AgentActivity` state construction (the state object is simple enough).
246
- 2. In the `onSessionCreated` callback: `subscribeUIObserver(session, state, onStreamUpdate)`.
247
-
248
- The `callbacks` spread pattern (`...bgCallbacks` / `...fgCallbacks`) disappears entirely.
249
-
250
- ## Module-Level Changes
251
-
252
- ### New files
253
-
254
- 1. `src/record-observer.ts` — `subscribeRecordObserver()` function.
255
- 2. `test/record-observer.test.ts` — unit tests with mock session.
256
- 3. `src/ui/ui-observer.ts` — `subscribeUIObserver()` function.
257
- 4. `test/ui/ui-observer.test.ts` — unit tests with mock session.
258
-
259
- ### Changed files (source)
260
-
261
- 1. `src/agent-manager.ts` — Wire record observer in `onSessionCreated`; simplify callback wrappers to pass-through (cycle 3); drop 5 `on*` fields from `SpawnOptions` (cycle 4).
262
- 2. `src/tools/agent-tool.ts` — Replace `createActivityTracker` with inline state + `subscribeUIObserver`; drop callback spread from spawn calls (cycle 4).
263
- 3. `src/agent-runner.ts` — Drop 5 `on*` fields from `RunOptions`; drop 3 `on*` fields from `ResumeOptions`; simplify `runAgent()` subscription to turn-limit only; remove conditional subscription from `resumeAgent()` (cycle 5).
264
-
265
- ### Changed files (tests)
266
-
267
- 1. `test/agent-manager.test.ts` — Upgrade `mockSession()` to support `subscribe()`; update 3 stat-verification tests to emit events through mock session instead of calling callbacks on `RunOptions` (cycle 3).
268
- 2. `test/agent-runner.test.ts` — Drop tests for callback forwarding (`onAssistantUsage` wiring, `onCompaction` forwarding); runner tests focus on turn-limit enforcement and session creation (cycle 5).
269
- 3. `test/agent-runner-extension-tools.test.ts` — Drop callback-related fields from `RunOptions` mock construction (cycle 5).
270
-
271
- ### Unchanged files
272
-
273
- - `src/service.ts` — public API unchanged (its `SpawnOptions` is a separate type).
274
- - `src/agent-record.ts` — public mutable fields used by observer, no changes needed.
275
- - `src/usage.ts` — `addUsage` helper used by observer, unchanged.
276
- - `src/ui/agent-widget.ts` — `AgentActivity` interface unchanged; widget reads state as before.
277
- - `src/session-config.ts`, `src/context.ts`, `src/env.ts` — unrelated.
278
- - `src/service-adapter.ts` — `AgentManagerLike.spawn` signature unchanged (onSessionCreated is already optional on SpawnOptions).
279
- - `test/tools/agent-tool.test.ts` — tests use mocked `deps.manager` and don't exercise callback wiring; spawn mock shape doesn't change.
280
- - `test/service-adapter.test.ts` — tests mock `AgentManagerLike`, unaffected by internal SpawnOptions changes.
281
-
282
- ## Test Impact Analysis
283
-
284
- ### New tests enabled by the extraction
285
-
286
- 1. `subscribeRecordObserver` tested in isolation with a mock session and real `AgentRecord`.
287
- Previously impossible — record stat updates were interleaved with callback wrapping inside `startAgent()`.
288
- 2. `subscribeUIObserver` tested in isolation with a mock session and `AgentActivity` state object.
289
- Previously impossible — UI state updates were buried in `createActivityTracker` closures that required a full spawn flow to exercise.
290
-
291
- ### Existing tests that simplify
292
-
293
- 1. `agent-manager.test.ts` stat tests — currently simulate callbacks by having the mock runner call `opts.onAssistantUsage?.(...)`.
294
- After the change, mock runners call `opts.onSessionCreated?.(session)` and tests emit events through the mock session.
295
- The pattern is more realistic (events drive state, not callbacks).
296
- 2. `agent-runner.test.ts` — callback forwarding tests (`onAssistantUsage wiring`, `onCompaction forwarding`) become unnecessary since the runner no longer translates events to callbacks.
297
- Turn-limit tests remain and simplify (no callback interleaving).
298
-
299
- ### Existing tests that stay as-is
300
-
301
- 1. All `agent-manager.test.ts` lifecycle tests (spawn, abort, queue drain, worktree, resume flow) — they verify AgentManager behavior and don't depend on callback wiring.
302
- Only the mock construction (`mockSession`) gains a `subscribe` method.
303
- 2. `agent-record.test.ts` — state-machine transitions are unrelated.
304
- 3. `agent-runner-extension-tools.test.ts` — tool filtering tests are unrelated to callbacks.
305
- 4. `test/tools/agent-tool.test.ts` — tests mock the manager; spawn/spawnAndWait call shapes don't change from the test's perspective.
306
-
307
- ## TDD Order
308
-
309
- ### Phase 1: Extract observers (additive, no breaking changes)
310
-
311
- #### Cycle 1: Record observer module
312
-
313
- Test surface: `test/record-observer.test.ts` (new).
314
-
315
- Tests:
316
-
317
- - `tool_execution_end` event increments `record.toolUses`.
318
- - `message_end` (assistant, with usage) accumulates into `record.lifetimeUsage`.
319
- - `compaction_end` (not aborted) increments `record.compactionCount` and calls `onCompact`.
320
- - `compaction_end` with `aborted: true` is ignored.
321
- - Returned function unsubscribes from session.
322
-
323
- Source changes:
324
-
325
- - `src/record-observer.ts`: `subscribeRecordObserver()` function.
326
-
327
- Commit: `feat: add record observer for direct session subscription (#100)`
328
-
329
- #### Cycle 2: UI observer module
330
-
331
- Test surface: `test/ui/ui-observer.test.ts` (new).
332
-
333
- Tests:
334
-
335
- - `tool_execution_start` adds to `state.activeTools`, calls `onUpdate`.
336
- - `tool_execution_end` removes from `state.activeTools`, increments `state.toolUses`, calls `onUpdate`.
337
- - `message_start` resets `state.responseText`.
338
- - `message_update` (text_delta) appends to `state.responseText`, calls `onUpdate`.
339
- - `turn_end` increments `state.turnCount`, calls `onUpdate`.
340
- - `message_end` (assistant, with usage) accumulates into `state.lifetimeUsage`, calls `onUpdate`.
341
- - Returned function unsubscribes from session.
342
-
343
- Source changes:
344
-
345
- - `src/ui/ui-observer.ts`: `subscribeUIObserver()` function.
346
-
347
- Commit: `feat: add UI observer for direct session subscription (#100)`
348
-
349
- ### Phase 2: AgentManager uses record observer
350
-
351
- #### Cycle 3: Wire record observer into startAgent and resume
352
-
353
- Test surface: `test/agent-manager.test.ts` (updated).
354
-
355
- This cycle replaces the record-mutation logic in `startAgent()` and `resume()` callback wrappers with `subscribeRecordObserver`.
356
- The 5 UI callbacks (`onToolActivity` through `onCompaction`) are still accepted on `SpawnOptions` and forwarded to `RunOptions` as pass-through (no wrapping).
357
- They are removed in cycle 4.
358
-
359
- Source changes:
360
-
361
- - `src/agent-manager.ts`:
362
- - Import `subscribeRecordObserver`.
363
- - In `startAgent()`: subscribe record observer inside `onSessionCreated` handler; remove `record.toolUses++` from `onToolActivity` wrapper (pass through `options.onToolActivity`); remove `addUsage` from `onAssistantUsage` wrapper (pass through); remove `record.compactionCount++` from `onCompaction` wrapper (pass through, `this.onCompact` moves to observer's `onCompact` option); capture unsubscribe function, call in `.then()` and `.catch()`.
364
- - In `resume()`: subscribe record observer to `record.session` before calling `runner.resume()`; drop `onToolActivity`, `onAssistantUsage`, `onCompaction` args from `runner.resume()` call; unsubscribe in `finally` block.
365
-
366
- Test changes:
367
-
368
- - `test/agent-manager.test.ts`:
369
- - Upgrade `mockSession()` to support `subscribe()` and provide a test helper `emit()` method.
370
- - Update the `onAssistantUsage` test: mock runner calls `opts.onSessionCreated?.(session)`, then emits `message_end` events through the mock session.
371
- - Update the `onCompaction` test: same pattern — emit `compaction_end` events through mock session.
372
- - Update the `resume()` accumulation test: mock session emits events during `runner.resume()`.
373
-
374
- Run: full test suite + `pnpm run check`.
375
-
376
- Commit: `refactor: AgentManager subscribes record observer directly (#100)`
377
-
378
- ### Phase 3: Agent-tool uses UI observer
379
-
380
- #### Cycle 4: Replace createActivityTracker with UI observer, drop SpawnOptions callbacks
381
-
382
- Test surface: `test/tools/agent-tool.test.ts` (verified unchanged), `test/agent-manager.test.ts` (verified compatible).
383
-
384
- This cycle replaces `createActivityTracker` with inline `AgentActivity` state construction and `subscribeUIObserver` subscription via `onSessionCreated`.
385
- `SpawnOptions` drops 5 `on*` fields.
386
-
387
- Source changes:
388
-
389
- - `src/tools/agent-tool.ts`:
390
- - Import `subscribeUIObserver` from `../ui/ui-observer.js`.
391
- - Remove `createActivityTracker` function.
392
- - Background path: construct `AgentActivity` state inline; pass `onSessionCreated` callback that subscribes UI observer; remove `...bgCallbacks` spread.
393
- - Foreground path: construct `AgentActivity` state inline; pass `onSessionCreated` callback that subscribes UI observer, registers activity in widget, and captures unsubscribe; remove `...fgCallbacks` spread; call UI unsubscribe after `spawnAndWait` returns.
394
- - `src/agent-manager.ts`:
395
- - `SpawnOptions`: remove `onToolActivity`, `onTextDelta`, `onTurnEnd`, `onAssistantUsage`, `onCompaction` fields.
396
- - `startAgent()`: remove pass-through forwarding of the 5 dropped callbacks to `RunOptions`.
397
-
398
- Run: full test suite + `pnpm run check`.
399
-
400
- Commit: `refactor: agent-tool subscribes UI observer, drop SpawnOptions callbacks (#100)`
401
-
402
- ### Phase 4: Runner drops callback forwarding
403
-
404
- #### Cycle 5: RunOptions and ResumeOptions drop callback fields, runner simplifies
405
-
406
- Test surface: `test/agent-runner.test.ts` (updated), `test/agent-runner-extension-tools.test.ts` (updated).
407
-
408
- Source changes:
409
-
410
- - `src/agent-runner.ts`:
411
- - `RunOptions`: remove `onToolActivity`, `onTextDelta`, `onTurnEnd`, `onAssistantUsage`, `onCompaction` fields.
412
- - `ResumeOptions`: remove `onToolActivity`, `onAssistantUsage`, `onCompaction` fields (keep `signal`).
413
- - `runAgent()`: simplify the big `session.subscribe()` block to turn-limit enforcement only (turn_end → count, steer, abort).
414
- Remove message_start/message_update/tool_execution_start/tool_execution_end/message_end/compaction_end handling.
415
- - `resumeAgent()`: remove the conditional `session.subscribe()` block entirely (no callbacks to forward).
416
- Keep `collectResponseText` and `forwardAbortSignal`.
417
- - Remove `ToolActivity` export if no longer needed externally (check consumers).
418
-
419
- Test changes:
420
-
421
- - `test/agent-runner.test.ts`:
422
- - Remove or simplify callback forwarding tests (onAssistantUsage wiring, onCompaction forwarding, tool activity forwarding).
423
- - Turn-limit tests remain, with simplified setup (no callback expectations).
424
- - `runAgent()` call sites drop the 5 callback fields.
425
- - `test/agent-runner-extension-tools.test.ts`:
426
- - Drop callback-related fields from `runAgent()` call construction.
427
-
428
- Run: full test suite + `pnpm run check`.
429
-
430
- Commit: `refactor: RunOptions and ResumeOptions drop callback fields (#100)`
431
-
432
- ## Risks and Mitigations
433
-
434
- | Risk | Mitigation |
435
- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
436
- | Double-counting during transition (old callbacks + new observer both update record) | Cycle 3 replaces record mutations atomically — old wrappers become pass-through in the same commit that adds the observer |
437
- | Mock session complexity increases (need `subscribe` + `emit` helper) | One-time investment; upgrade `mockSession()` once, reused across all tests; more realistic than simulating callbacks |
438
- | Event ordering between record observer and UI observer | Observers update independent state (record vs AgentActivity); no cross-dependency, ordering irrelevant |
439
- | `ToolActivity` type used outside this package | Grep before removing; if used, keep the export and add a deprecation note |
440
- | `createActivityTracker` exported but unused externally | Verified: only used within `agent-tool.ts`; safe to remove |
441
- | Turn-limit enforcement mixed with callback subscription in runner | Cycle 5 extracts turn-limit into its own subscription cleanly; `collectResponseText` stays separate |
442
- | Unsubscription missed on error paths | Record observer unsubscribe captured in closure, called in both `.then()` and `.catch()`; resume uses `finally` |
443
-
444
- ## Open Questions
445
-
446
- - Whether to extract turn-limit enforcement from the runner into a separate `turn-limiter.ts` module.
447
- This would improve testability but is orthogonal to callback elimination.
448
- Deferred — the runner's subscription simplifies enough in cycle 5.
449
- - Whether `ResumeOptions` should be renamed or inlined since it reduces to `{ signal?: AbortSignal }`.
450
- Keeping it as a named type is fine for extensibility.
451
- Deferred — cosmetic, can be done anytime.
452
- - Whether to introduce a `SessionLike` interface for the mock session pattern used in `record-observer.test.ts` and `ui-observer.test.ts`.
453
- Both tests need a minimal session with `subscribe()`.
454
- If the pattern proves reusable, extract it; otherwise keep the inline mock.
@@ -1,176 +0,0 @@
1
- ---
2
- issue: 102
3
- issue_title: "Consolidate test AgentRecord construction into a shared factory"
4
- ---
5
-
6
- # Consolidate test AgentRecord construction into a shared factory
7
-
8
- ## Problem Statement
9
-
10
- Eight test files independently construct `AgentRecord` objects using three different patterns: copy-pasted `makeRecord()`/`mockRecord()` factory functions (5 files), inline `const baseRecord: AgentRecord = { ... }` literals (2 files), and `as AgentRecord` casts.
11
- When issue #98 converts `AgentRecord` from an interface to a class, every object-literal construction site breaks.
12
- A shared factory confines that future breakage to a single file.
13
-
14
- ## Goals
15
-
16
- - Create a shared `createTestRecord()` factory in `test/helpers/make-record.ts`.
17
- - Migrate all 7 affected test files to import the shared factory.
18
- - No production code changes.
19
- - No behavior changes — purely mechanical.
20
-
21
- ## Non-Goals
22
-
23
- - Converting `AgentRecord` to a class — that is issue #98, which depends on this change.
24
- - Adding new test coverage — this is a refactoring of test infrastructure only.
25
- - Touching `test/agent-manager.test.ts` — it constructs records via `manager.spawn()`, not literals.
26
- - Consolidating other test helpers (mock sessions, mock TUI, etc.).
27
-
28
- ## Background
29
-
30
- ### Relevant modules
31
-
32
- | Module | Role |
33
- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------- |
34
- | `src/types.ts` | Defines the `AgentRecord` interface (20+ fields) |
35
- | `test/tools/agent-tool.test.ts` | `makeRecord()` factory — 12 default fields, status "completed" |
36
- | `test/tools/get-result-tool.test.ts` | `makeRecord()` factory — 10 default fields, status "completed" |
37
- | `test/tools/steer-tool.test.ts` | `makeRecord()` factory — 9 default fields, status "running", includes mock session, uses `as AgentRecord` cast |
38
- | `test/ui/agent-menu.test.ts` | `makeRecord()` factory — 10 default fields, status "completed" |
39
- | `test/conversation-viewer.test.ts` | `mockRecord()` factory — 6 default fields, status "running", uses `as AgentRecord` cast |
40
- | `test/notification.test.ts` | 4 inline `baseRecord` literals, status "completed" |
41
- | `test/service-adapter.test.ts` | 4 inline `baseRecord` / `minimal` literals, mixed statuses |
42
-
43
- ### Convention from sibling packages
44
-
45
- `packages/pi-autoformat/test/helpers/rpc.ts` is the only existing shared test helper in the monorepo.
46
- The pattern is a plain module under `test/helpers/` with named exports — no class, no framework.
47
-
48
- ### Relationship to issue #98
49
-
50
- Issue #98 plans to extract `MutableAgentRecord` as a class implementing the `AgentRecord` interface.
51
- That plan explicitly notes: "All test files that construct `AgentRecord` literals — they create interface-compatible objects, not class instances" and lists them as unchanged.
52
- Once this consolidation lands, issue #98's "unchanged" assumption becomes trivially true: only the shared factory needs updating if the construction API changes.
53
-
54
- ## Design Overview
55
-
56
- ### Shared factory: `createTestRecord()`
57
-
58
- A single function in `test/helpers/make-record.ts` with the `Partial<AgentRecord>` override pattern already used by 5 of the 7 files:
59
-
60
- ```typescript
61
- import type { AgentRecord } from "../../src/types.js";
62
-
63
- export function createTestRecord(
64
- overrides: Partial<AgentRecord> = {},
65
- ): AgentRecord {
66
- return {
67
- id: "agent-1",
68
- type: "general-purpose",
69
- description: "Test task",
70
- status: "completed",
71
- result: "All done.",
72
- toolUses: 3,
73
- startedAt: 1000,
74
- completedAt: 2000,
75
- compactionCount: 0,
76
- lifetimeUsage: { input: 500, output: 500, cacheWrite: 0 },
77
- ...overrides,
78
- };
79
- }
80
- ```
81
-
82
- ### Default-value decisions
83
-
84
- The defaults match the majority pattern (6 of 7 files default to a "completed" record).
85
- The two files that need "running" records (`steer-tool`, `conversation-viewer`) pass `{ status: "running" }` as overrides — a one-field change.
86
-
87
- The `as AgentRecord` cast used by `steer-tool.test.ts` and `conversation-viewer.test.ts` is no longer needed: the shared factory returns a full `AgentRecord` with all required fields populated, so TypeScript is satisfied without casting.
88
-
89
- ### Migration strategy for inline-literal files
90
-
91
- `notification.test.ts` and `service-adapter.test.ts` construct multiple distinct inline literals — they don't have a single factory.
92
- Each inline literal becomes a `createTestRecord({ ...specific overrides })` call.
93
- The `baseRecord` variable declared in each `describe` block is replaced with a call to `createTestRecord()`.
94
-
95
- For `service-adapter.test.ts`, the top-level `baseRecord` with custom values (`id: "abc-123"`, `type: "Explore"`, etc.) becomes `createTestRecord({ id: "abc-123", type: "Explore", ... })`.
96
-
97
- ## Module-Level Changes
98
-
99
- ### New files
100
-
101
- 1. `test/helpers/make-record.ts` — exports `createTestRecord()`.
102
-
103
- ### Changed files
104
-
105
- 1. `test/tools/agent-tool.test.ts` — remove local `makeRecord()`, import `createTestRecord` from helpers.
106
- 2. `test/tools/get-result-tool.test.ts` — remove local `makeRecord()`, import `createTestRecord` from helpers.
107
- 3. `test/tools/steer-tool.test.ts` — remove local `makeRecord()`, import `createTestRecord` from helpers.
108
- Replace default `status: "running"` and `session` with overrides in each call site.
109
- 4. `test/ui/agent-menu.test.ts` — remove local `makeRecord()`, import `createTestRecord` from helpers.
110
- 5. `test/conversation-viewer.test.ts` — remove local `mockRecord()`, import `createTestRecord` from helpers.
111
- Replace default `status: "running"` and `startedAt: Date.now()` with overrides in each call site.
112
- 6. `test/notification.test.ts` — replace 4 inline `baseRecord` literals with `createTestRecord()` calls.
113
- 7. `test/service-adapter.test.ts` — replace inline `baseRecord` / `minimal` / per-test literals with `createTestRecord()` calls.
114
-
115
- ### Unchanged files
116
-
117
- 1. `test/agent-manager.test.ts` — constructs records via `manager.spawn()`, not literals.
118
- 2. All production source files — no changes.
119
-
120
- ## Test Impact Analysis
121
-
122
- ### New tests enabled
123
-
124
- 1. A small sanity test in `test/helpers/make-record.test.ts` verifying that `createTestRecord()` returns a valid `AgentRecord` with expected defaults and that overrides are applied.
125
- This is optional — the factory is exercised transitively by every consumer — but it documents the contract for future maintainers (especially when #98 changes construction).
126
-
127
- ### Existing tests that become redundant
128
-
129
- None.
130
- This is a pure refactoring of test infrastructure; no production behavior changes.
131
-
132
- ### Existing tests that stay as-is
133
-
134
- All existing test assertions stay unchanged.
135
- Only the construction of `AgentRecord` objects in test setup code changes; the assertions that read those records are untouched.
136
-
137
- ## TDD Order
138
-
139
- 1. **Create shared factory and its test.**
140
- Add `test/helpers/make-record.ts` with `createTestRecord()`.
141
- Add `test/helpers/make-record.test.ts` verifying defaults and override behavior.
142
- Commit: `test: add shared createTestRecord factory (#102)`
143
-
144
- 2. **Migrate tool test files.**
145
- Update `agent-tool.test.ts`, `get-result-tool.test.ts`, `steer-tool.test.ts` to import `createTestRecord` and remove local `makeRecord()` functions.
146
- Run `pnpm vitest run test/tools/agent-tool.test.ts test/tools/get-result-tool.test.ts test/tools/steer-tool.test.ts` to verify.
147
- Commit: `test: migrate tool tests to shared createTestRecord (#102)`
148
-
149
- 3. **Migrate UI test files.**
150
- Update `agent-menu.test.ts` and `conversation-viewer.test.ts` to import `createTestRecord` and remove local `makeRecord()`/`mockRecord()` functions.
151
- Run `pnpm vitest run test/ui/agent-menu.test.ts test/conversation-viewer.test.ts` to verify.
152
- Commit: `test: migrate UI tests to shared createTestRecord (#102)`
153
-
154
- 4. **Migrate notification and service-adapter tests.**
155
- Update `notification.test.ts` and `service-adapter.test.ts` to replace inline literals with `createTestRecord()` calls.
156
- Run `pnpm vitest run test/notification.test.ts test/service-adapter.test.ts` to verify.
157
- Commit: `test: migrate notification and service-adapter tests to shared createTestRecord (#102)`
158
-
159
- 5. **Final verification.**
160
- Run full test suite (`pnpm vitest run`) and type check (`pnpm run check`) to confirm no regressions.
161
- Commit: not needed if steps 2–4 are clean; otherwise a fix-up commit.
162
-
163
- ## Risks and Mitigations
164
-
165
- | Risk | Mitigation |
166
- | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
167
- | Shared defaults don't match a test's assumptions, causing silent false-passes | Each migration step runs the affected test file immediately; review each test's overrides to ensure they still express the test's intent |
168
- | `steer-tool.test.ts` relies on `session: { fake: true }` in its factory default, which the shared factory omits | Pass `session` as an override at each call site; the mock session is test-specific and doesn't belong in shared defaults |
169
- | `conversation-viewer.test.ts` uses `startedAt: Date.now()` which the shared factory replaces with `1000` | Replace with `createTestRecord({ status: "running" })`; `startedAt` value is not asserted in any conversation-viewer test |
170
- | `service-adapter.test.ts` uses custom `id`, `type`, `description` values that carry semantic meaning in its assertions | Pass those values explicitly as overrides to `createTestRecord()` |
171
- | The `as AgentRecord` cast removal changes type-checking strictness | The shared factory returns a complete object satisfying all required fields, so removing the cast is strictly safer |
172
-
173
- ## Open Questions
174
-
175
- - The factory name `createTestRecord` vs `makeRecord` vs `makeAgentRecord`: the plan uses `createTestRecord` to distinguish it from the production `AgentRecord` constructor that #98 will introduce.
176
- If #98 names its constructor differently, this can be revisited.