@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,435 +0,0 @@
1
- ---
2
- issue: 98
3
- issue_title: "Extract AgentRecord state machine from scattered status transitions"
4
- ---
5
-
6
- # Extract AgentRecord state machine
7
-
8
- ## Problem Statement
9
-
10
- `AgentRecord` status transitions are scattered across 6 locations in `agent-manager.ts`: `startAgent()` `.then()`, `startAgent()` `.catch()`, `resume()`, `abort()`, `abortAll()`, and `drainQueue()` catch.
11
- Each site sets `record.status` plus associated fields (`completedAt`, `result`, `error`) in ad-hoc combinations, with repeated guards like `if (record.status !== "stopped")`.
12
-
13
- There are 15 direct `record.status = ...` writes and 17 associated field writes (`result`, `error`, `completedAt`, `startedAt`) — ~32 scattered mutation sites total.
14
- `startAgent()` is ~130 lines partly because it manages these transitions inline.
15
-
16
- ## Goals
17
-
18
- - Convert `AgentRecord` from a plain interface to a class that owns and encapsulates its transition state.
19
- - Extract status-transition methods that centralize the "don't overwrite stopped" guard.
20
- - Reduce scattered field writes in `agent-manager.ts` to method calls.
21
- - Encapsulate transition fields (`status`, `result`, `error`, `startedAt`, `completedAt`) behind private backing fields with getters — external code can read but not write.
22
- - Use lift-and-shift to migrate incrementally: introduce the class alongside the interface, migrate construction sites, switch the re-export, replace writes, then encapsulate.
23
-
24
- ## Non-Goals
25
-
26
- - Parent snapshot extraction (architecture.md Step 2) — separate issue.
27
- - Session-event observation / callback threading removal (architecture.md Step 3) — separate issue.
28
- - Stat accumulation methods (`toolUses++`, `addUsage()`, `compactionCount++`) — these are running counters, not status transitions.
29
- They could become methods in a follow-up but are out of scope here.
30
- - Encapsulating non-transition field writes (`session`, `outputFile`, `worktree`, `worktreeResult`, `promise`, `toolCallId`, `resultConsumed`, `pendingSteers`) — these are data capture, not status transitions.
31
-
32
- ## Prerequisites
33
-
34
- - Issue #102 (shared test record factory) — shipped.
35
- All 8 test files construct `AgentRecord` objects through `createTestRecord()` in `test/helpers/make-record.ts`.
36
- Converting the interface to a class requires updating only this one factory (plus `agent-manager.ts` construction).
37
-
38
- ## Background
39
-
40
- ### Relevant modules
41
-
42
- | Module | Role |
43
- | ----------------------------- | ------------------------------------------------------------------------------ |
44
- | `src/types.ts` | Defines `AgentRecord` interface — 20+ consumers read from it |
45
- | `src/agent-manager.ts` | Only file that writes status-transition fields |
46
- | `src/service.ts` | Defines `SubagentRecord` (serializable snapshot) — unchanged |
47
- | `src/service-adapter.ts` | Converts `AgentRecord` → `SubagentRecord` via `toSubagentRecord()` — unchanged |
48
- | `test/helpers/make-record.ts` | Shared test factory — single construction site for all tests (#102) |
49
-
50
- ### External writes to `AgentRecord` (not status transitions)
51
-
52
- Several files outside `agent-manager.ts` write non-transition fields:
53
-
54
- - `tools/get-result-tool.ts` — `record.resultConsumed = true` (2 sites)
55
- - `tools/steer-tool.ts` / `service-adapter.ts` — `record.pendingSteers` (push messages)
56
- - `tools/agent-tool.ts` — `record.toolCallId = toolCallId`
57
-
58
- These are data-capture writes, not status transitions, and remain public fields.
59
-
60
- ### Code-style constraints
61
-
62
- The code-style skill's "output arguments" rule applies directly: "If a function sets a field on a received object, it is doing work that belongs inside the owning object.
63
- Encapsulate the mutation behind a method."
64
- The "scattered resets" rule also applies: "When the same set of fields is reset to the same values in multiple places, extract a single method."
65
-
66
- ## Design Overview
67
-
68
- ### `AgentRecord` becomes a class
69
-
70
- `AgentRecord` moves from an interface in `types.ts` to a class in a new `src/agent-record.ts` module.
71
- `types.ts` re-exports the class so existing `import type { AgentRecord } from "./types.js"` across the codebase continues to work with no import-path changes.
72
-
73
- The class encapsulates the 5 transition fields behind private backing fields with getters.
74
- All other fields remain public (identity fields are `readonly`; non-transition mutable fields stay public).
75
-
76
- ```typescript
77
- export type AgentRecordStatus =
78
- | "queued" | "running" | "completed" | "steered" | "aborted" | "stopped" | "error";
79
-
80
- export class AgentRecord {
81
- // Identity — readonly, set once at construction
82
- readonly id: string;
83
- readonly type: SubagentType;
84
- readonly description: string;
85
- readonly invocation?: AgentInvocation;
86
-
87
- // Transition state — private backing fields, public getters
88
- private _status: AgentRecordStatus;
89
- get status(): AgentRecordStatus { return this._status; }
90
-
91
- private _result?: string;
92
- get result(): string | undefined { return this._result; }
93
-
94
- private _error?: string;
95
- get error(): string | undefined { return this._error; }
96
-
97
- private _startedAt: number;
98
- get startedAt(): number { return this._startedAt; }
99
-
100
- private _completedAt?: number;
101
- get completedAt(): number | undefined { return this._completedAt; }
102
-
103
- // Non-transition mutable state — public fields
104
- toolUses: number;
105
- lifetimeUsage: LifetimeUsage;
106
- compactionCount: number;
107
- session?: AgentSession;
108
- abortController?: AbortController;
109
- promise?: Promise<string>;
110
- resultConsumed?: boolean;
111
- pendingSteers?: string[];
112
- worktree?: { path: string; branch: string };
113
- worktreeResult?: { hasChanges: boolean; branch?: string };
114
- toolCallId?: string;
115
- outputFile?: string;
116
-
117
- constructor(init: AgentRecordInit) { /* ... */ }
118
-
119
- // Transition methods — see below
120
- }
121
- ```
122
-
123
- ### Constructor and `AgentRecordInit`
124
-
125
- The constructor accepts a wide init bag that covers all fields.
126
- Required fields: `id`, `type`, `description`.
127
- All others are optional with sensible defaults (`status` defaults to `"queued"`, `toolUses` to 0, etc.).
128
-
129
- This wide init bag serves two purposes:
130
-
131
- 1. Production use (`agent-manager.ts`): passes the 7 fields it needs at spawn time.
132
- 2. Test use (`createTestRecord`): sets arbitrary state (e.g., `status: "completed"`, `result: "done"`) without going through transition methods.
133
-
134
- ```typescript
135
- export interface AgentRecordInit {
136
- id: string;
137
- type: SubagentType;
138
- description: string;
139
- status?: AgentRecordStatus;
140
- startedAt?: number;
141
- completedAt?: number;
142
- result?: string;
143
- error?: string;
144
- toolUses?: number;
145
- lifetimeUsage?: LifetimeUsage;
146
- compactionCount?: number;
147
- abortController?: AbortController;
148
- invocation?: AgentInvocation;
149
- session?: AgentSession;
150
- promise?: Promise<string>;
151
- resultConsumed?: boolean;
152
- pendingSteers?: string[];
153
- worktree?: { path: string; branch: string };
154
- worktreeResult?: { hasChanges: boolean; branch?: string };
155
- toolCallId?: string;
156
- outputFile?: string;
157
- }
158
- ```
159
-
160
- ### Transition methods
161
-
162
- 7 methods covering all 6 transition sites plus the resume-reset:
163
-
164
- #### `markRunning(startedAt: number): void`
165
-
166
- Sets `status = "running"` and `startedAt`.
167
- Called in `startAgent()` when transitioning from queued to running.
168
-
169
- #### `markCompleted(result: string, completedAt?: number): void`
170
-
171
- Always sets `result` and `completedAt` (via `??=`).
172
- Only changes `status` to `"completed"` if not already `"stopped"`.
173
- Called in the `.then()` success path and in `resume()` try block.
174
-
175
- #### `markAborted(result: string, completedAt?: number): void`
176
-
177
- Same guard as `markCompleted` — preserves stopped status.
178
- Sets `status = "aborted"`.
179
- Called in `.then()` when runner returns `aborted: true`.
180
-
181
- #### `markSteered(result: string, completedAt?: number): void`
182
-
183
- Same guard as `markCompleted` — preserves stopped status.
184
- Sets `status = "steered"`.
185
- Called in `.then()` when runner returns `steered: true`.
186
-
187
- #### `markError(error: unknown, completedAt?: number): void`
188
-
189
- Always sets `error` (formatted: `Error` → `.message`, otherwise `String(...)`) and `completedAt` (via `??=`).
190
- Only changes `status` to `"error"` if not already `"stopped"`.
191
- Called in `.catch()`, `resume()` catch block, and `drainQueue()` catch.
192
-
193
- #### `markStopped(completedAt?: number): void`
194
-
195
- Always sets `status = "stopped"` and `completedAt`.
196
- No guard — stopping is always valid.
197
- Called in `abort()` (2 sites) and `abortAll()` (2 sites).
198
-
199
- #### `resetForResume(startedAt: number): void`
200
-
201
- Sets `status = "running"`, `startedAt`.
202
- Clears `completedAt`, `result`, `error` to `undefined`.
203
- Called in `resume()` before re-running.
204
-
205
- ### Guard semantics: preserve current behavior
206
-
207
- The current `.then()` and `.catch()` blocks guard only the `status` field but always set `result`/`error` and `completedAt`:
208
-
209
- ```typescript
210
- // Current .then() — result and completedAt are set even when stopped
211
- if (record.status !== "stopped") {
212
- record.status = aborted ? "aborted" : steered ? "steered" : "completed";
213
- }
214
- record.result = responseText;
215
- record.completedAt ??= Date.now();
216
- ```
217
-
218
- This is intentional — `get-result-tool.ts` reads `record.result` from stopped records.
219
- The transition methods preserve this behavior: data fields are always set, status is only changed when not stopped.
220
- The `??=` on `completedAt` preserves the abort timestamp when `markStopped()` fires before `.then()`.
221
-
222
- ### Worktree result-append: reorder, don't add a method
223
-
224
- The `.then()` block currently appends worktree branch text to `record.result` after the transition.
225
- With encapsulated fields, direct writes to `result` are forbidden.
226
-
227
- Instead of adding an `appendToResult()` method, reorder the `.then()` block to compute the final result (including worktree text) before calling the transition method:
228
-
229
- ```typescript
230
- .then(({ responseText, session, aborted, steered, sessionFile }) => {
231
- // Worktree cleanup first — compute final result
232
- let finalResult = responseText;
233
- if (record.worktree) {
234
- const wtResult = this.worktrees.cleanup(record.worktree, options.description);
235
- record.worktreeResult = wtResult;
236
- if (wtResult.hasChanges && wtResult.branch) {
237
- finalResult += `\n\n---\nChanges saved to branch ...`;
238
- }
239
- }
240
-
241
- // Transition with complete result
242
- if (aborted) record.markAborted(finalResult);
243
- else if (steered) record.markSteered(finalResult);
244
- else record.markCompleted(finalResult);
245
-
246
- record.session = session;
247
- if (sessionFile) record.outputFile = sessionFile;
248
-
249
- detach();
250
- // ...
251
- })
252
- ```
253
-
254
- The worktree cleanup reads `record.worktree` (a public field set before the promise) and writes `record.worktreeResult` (a public field).
255
- It does not depend on `record.status` or `record.result`, so the reorder is safe.
256
-
257
- ### Correctness improvement in `resume()`
258
-
259
- The current `resume()` method lacks the "don't overwrite stopped" guard:
260
-
261
- ```typescript
262
- // Current — no guard against abort race
263
- record.status = "completed";
264
- record.result = responseText;
265
- record.completedAt = Date.now();
266
- ```
267
-
268
- After migration, `record.markCompleted(responseText)` includes the guard.
269
- This closes a latent race where `abort()` sets status to `"stopped"` between `resetForResume()` and the runner returning.
270
- The current code would overwrite `"stopped"` back to `"completed"` — the method call does not.
271
-
272
- ### Circular import avoidance
273
-
274
- `agent-record.ts` imports types from `types.ts` (`SubagentType`, `AgentInvocation`).
275
- `types.ts` re-exports the class from `agent-record.ts`.
276
-
277
- This is safe because `agent-record.ts` uses `import type` for its `types.ts` imports — these are erased at runtime, so no runtime circular dependency exists.
278
-
279
- ## Module-Level Changes
280
-
281
- ### New files
282
-
283
- 1. `src/agent-record.ts` — `AgentRecord` class, `AgentRecordInit` interface, `AgentRecordStatus` type.
284
- 2. `test/agent-record.test.ts` — unit tests for constructor and all 7 transition methods.
285
-
286
- ### Changed files
287
-
288
- 1. `src/types.ts` — remove `AgentRecord` interface; add re-export: `export { AgentRecord, type AgentRecordInit, type AgentRecordStatus } from "./agent-record.js"`.
289
- 2. `src/agent-manager.ts` — import `AgentRecord` as a value from `./agent-record.js`; replace object literal construction with `new AgentRecord(...)`; replace ~32 scattered field writes with 11 transition method calls.
290
- 3. `test/helpers/make-record.ts` — import `AgentRecord` from `../../src/agent-record.js`; construct with `new AgentRecord(...)` instead of plain object.
291
-
292
- ### Unchanged files
293
-
294
- All other source and test files — they use `import type { AgentRecord } from "./types.js"` and only read fields.
295
- The re-export from `types.ts` means zero import-path changes.
296
-
297
- ## Test Impact Analysis
298
-
299
- ### New tests enabled by the extraction
300
-
301
- The state machine is currently untestable in isolation — transitions are buried inside `agent-manager.ts`.
302
- After extraction:
303
-
304
- - Guard logic tested directly (e.g., `markCompleted` on a stopped record preserves status but sets result).
305
- - Invalid transition sequences tested without mocking the runner.
306
- - Error formatting (`Error` vs string) tested in isolation.
307
- - `resetForResume` field clearing tested without the full resume flow.
308
-
309
- ### Existing tests that stay as-is
310
-
311
- `agent-manager.test.ts` tests verify end-to-end behavior (spawn, complete, abort, resume, queue drain, worktree).
312
- They remain unchanged — they verify the wiring between `AgentManager` and the `AgentRecord` class.
313
-
314
- ### No test files need updating (except the shared factory)
315
-
316
- Issue #102 consolidated all test record construction into `createTestRecord()`.
317
- Only that one factory changes.
318
- All 8 consumer test files are untouched.
319
-
320
- ## TDD Order
321
-
322
- ### Cycle 1: AgentRecord class tests
323
-
324
- Test surface: `test/agent-record.test.ts` (new file).
325
-
326
- Tests cover:
327
-
328
- - Constructor: defaults (`status` → `"queued"`, `toolUses` → 0, `lifetimeUsage` → zeros, `compactionCount` → 0), passthrough of init values (including optional transition fields like `result`, `completedAt`).
329
- - `markRunning`: sets status and startedAt.
330
- - `markCompleted`: sets status/result/completedAt when not stopped; preserves status but still sets result/completedAt when stopped; `completedAt` uses `??=` semantics (does not overwrite existing value).
331
- - `markAborted` and `markSteered`: same guard behavior as `markCompleted`.
332
- - `markError`: sets status/error/completedAt when not stopped; preserves status but still sets error/completedAt when stopped; formats `Error` objects to `.message`, non-Error to `String(...)`.
333
- - `markStopped`: always sets status and completedAt, no guard.
334
- - `resetForResume`: sets status to `"running"` and startedAt; clears completedAt, result, error.
335
-
336
- Commit: `test: add AgentRecord class tests (#98)`
337
-
338
- ### Cycle 2: AgentRecord class implementation
339
-
340
- Source: `src/agent-record.ts` (new file).
341
- Exports: `AgentRecord` class, `AgentRecordInit` interface, `AgentRecordStatus` type.
342
- All fields public initially (encapsulation deferred to cycle 6).
343
-
344
- All cycle 1 tests pass.
345
-
346
- Commit: `feat: create AgentRecord class with transition methods (#98)`
347
-
348
- ### Cycle 3: Lift — migrate construction sites
349
-
350
- Changed files: `test/helpers/make-record.ts`, `src/agent-manager.ts`.
351
-
352
- `createTestRecord`: import `AgentRecord` from `agent-record.ts`; construct with `new AgentRecord(...)`.
353
- `agent-manager.ts`: import `AgentRecord` from `agent-record.ts` (value import); replace object literal in `spawn()` with `new AgentRecord(...)`.
354
-
355
- At this point, both the interface (in `types.ts`) and the class (in `agent-record.ts`) coexist.
356
- `agent-manager.ts` uses the class; all other consumers use the interface.
357
- Scattered field writes still work because fields are public.
358
-
359
- Run: full test suite + `pnpm run check`.
360
-
361
- Commit: `refactor: construct AgentRecord class in agent-manager and test factory (#98)`
362
-
363
- ### Cycle 4: Shift — switch `types.ts` from interface to re-export
364
-
365
- Changed file: `src/types.ts`.
366
-
367
- Remove the `AgentRecord` interface definition.
368
- Add re-exports:
369
-
370
- ```typescript
371
- export { AgentRecord } from "./agent-record.js";
372
- export type { AgentRecordInit, AgentRecordStatus } from "./agent-record.js";
373
- ```
374
-
375
- All `import type { AgentRecord } from "./types.js"` across the codebase now resolve to the class.
376
- No consumer constructs `AgentRecord` as a plain object (construction was migrated in cycle 3).
377
-
378
- Run: `pnpm run check` (catches any structural incompatibilities).
379
-
380
- Commit: `refactor: replace AgentRecord interface with class re-export in types.ts (#98)`
381
-
382
- ### Cycle 5: Replace scattered field writes with transition methods
383
-
384
- Changed file: `src/agent-manager.ts`.
385
-
386
- Replace all 11 transition sites:
387
-
388
- | Location | Before | After |
389
- | ---------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------- |
390
- | `startAgent()` running | `record.status = "running"; record.startedAt = ...` | `record.markRunning(Date.now())` |
391
- | `.then()` success | `if (!stopped) status = ...; result = ...; completedAt ??= ...` | `record.markCompleted(finalResult)` / `markAborted` / `markSteered` |
392
- | `.catch()` error | `if (!stopped) status = "error"; error = ...; completedAt ??= ...` | `record.markError(err)` |
393
- | `resume()` reset | `status = ...; startedAt = ...; clear 3 fields` | `record.resetForResume(Date.now())` |
394
- | `resume()` try | `status = "completed"; result = ...; completedAt = ...` | `record.markCompleted(responseText)` |
395
- | `resume()` catch | `status = "error"; error = ...; completedAt = ...` | `record.markError(err)` |
396
- | `abort()` queued | `status = "stopped"; completedAt = ...` | `record.markStopped()` |
397
- | `abort()` running | `status = "stopped"; completedAt = ...` | `record.markStopped()` |
398
- | `abortAll()` queued | `status = "stopped"; completedAt = ...` | `record.markStopped()` |
399
- | `abortAll()` running | `status = "stopped"; completedAt = ...` | `record.markStopped()` |
400
- | `drainQueue()` catch | `status = "error"; error = ...; completedAt = ...` | `record.markError(err)` |
401
-
402
- Also reorder the `.then()` block: worktree cleanup moves before the transition call so the final result includes worktree branch text (see Design Overview).
403
-
404
- Run: full test suite.
405
-
406
- Commit: `refactor: replace scattered status transitions with AgentRecord methods (#98)`
407
-
408
- ### Cycle 6: Encapsulate transition fields
409
-
410
- Changed file: `src/agent-record.ts`.
411
-
412
- Make `status`, `result`, `error`, `startedAt`, `completedAt` private (`_status`, `_result`, etc.) with public getters.
413
- Make `id`, `type`, `description`, `invocation` readonly.
414
-
415
- Run: `pnpm run check` — verifies no remaining direct writes to encapsulated fields anywhere in `src/` or `test/`.
416
- Run: full test suite.
417
-
418
- Commit: `refactor: encapsulate AgentRecord transition state (#98)`
419
-
420
- ## Risks and Mitigations
421
-
422
- | Risk | Mitigation |
423
- | ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
424
- | Guard semantics drift: transition methods might not exactly match scattered guards | Each method's guard logic has dedicated tests (cycle 1); full agent-manager test suite runs after each cycle |
425
- | Data fields on stopped records: `markCompleted` etc. must still set `result`/`completedAt` even when status is guarded | Explicit "stopped guard" test cases verify data fields are set; mirrors current `.then()` / `.catch()` behavior |
426
- | Worktree result-append after encapsulation: direct `record.result = ...` no longer compiles | `.then()` block reordered to compute full result before calling transition method (cycle 5) |
427
- | `types.ts` re-export introduces type-only circular dependency | `agent-record.ts` → `types.ts` imports are `import type` only (erased at runtime); no runtime cycle |
428
- | `resume()` gains a "stopped" guard it didn't have before | This is a correctness improvement: closes a latent abort race; noted explicitly in design |
429
-
430
- ## Open Questions
431
-
432
- - The `markCompleted`/`markAborted`/`markSteered` split is not in the original issue's 5-method list (which only shows `markCompleted`).
433
- The split is needed because the `.then()` block dispatches to 3 distinct terminal statuses.
434
- - Stat accumulation (`toolUses++`, `addUsage(lifetimeUsage, ...)`, `compactionCount++`) is left as direct field writes on public fields.
435
- These could become methods in a follow-up if the callback-threading removal (architecture.md Step 3) makes it natural.