@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,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.