@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
@@ -114,13 +114,13 @@ classDiagram
114
114
  +toolUses: number
115
115
  +lifetimeUsage: LifetimeUsage
116
116
  +subagentSession?: SubagentSession
117
- +notification?: NotificationState
117
+ +toolCallId?: string
118
118
  +markRunning() delegates
119
119
  +markCompleted() delegates
120
120
  +run()
121
121
  +resume(prompt, signal)
122
122
  +abort(): boolean
123
- +steer(message): Promise~boolean~
123
+ +steer(message): Promise~SteerOutcome~
124
124
  +isSessionReady(): boolean
125
125
  +getConversation(): string | undefined
126
126
  +getContextPercent(): number | null
@@ -318,8 +318,7 @@ src/
318
318
 
319
319
  ├── observation/ progress tracking and notification
320
320
  │ ├── record-observer.ts session-event stats observer
321
- │ ├── notification.ts completion nudges
322
- │ ├── notification-state.ts per-agent notification tracking
321
+ │ ├── notification.ts completion nudges + per-agent consumed-result tracking
323
322
  │ ├── renderer.ts notification TUI component
324
323
  │ ├── composite-subagent-observer.ts fans manager notifications out to multiple observers
325
324
  │ └── subagent-events-observer.ts manager lifecycle observer (event emission + persistence + notification)
@@ -335,6 +334,7 @@ src/
335
334
  │ ├── foreground-runner.ts foreground execution loop
336
335
  │ ├── background-spawner.ts background spawn setup
337
336
  │ ├── get-result-tool.ts get_subagent_result tool
337
+ │ ├── get-result-report.ts pure get_subagent_result report formatter
338
338
  │ ├── steer-tool.ts steer_subagent tool
339
339
  │ └── helpers.ts shared tool utilities
340
340
 
@@ -645,17 +645,17 @@ That method — testability friction as a boundary probe, with its limits — is
645
645
 
646
646
  ### Health metrics
647
647
 
648
- | Metric | Value |
649
- | -------------------------- | -------------------------------------------- |
650
- | Health score | 78/100 (B) |
651
- | Total LOC | 7,751 (62 files, end of Phase 17) |
652
- | Dead code | 0 files, 0 exports |
653
- | Maintainability index | 90.8 (good) |
654
- | Avg cyclomatic complexity | 1.4 |
655
- | P90 cyclomatic complexity | 2 |
656
- | Production duplication | 11 lines (1 internal clone group) |
657
- | Test duplication | 28 clone groups, 503 lines (end of Phase 17) |
658
- | Fallow refactoring targets | 0 |
648
+ | Metric | Value |
649
+ | -------------------------- | --------------------------- |
650
+ | Health score | 78/100 (B), end of Phase 19 |
651
+ | Total LOC | 7,068 (57 files) |
652
+ | Dead code | 0 files, 0 exports |
653
+ | Maintainability index | 91.0 (good) |
654
+ | Avg cyclomatic complexity | 1.4 |
655
+ | P90 cyclomatic complexity | 2 |
656
+ | Production duplication | 0 lines |
657
+ | Test duplication | 9 clone groups, 81 lines |
658
+ | Fallow refactoring targets | 0 |
659
659
 
660
660
  ### Dependency bag inventory
661
661
 
@@ -674,8 +674,6 @@ Bags with 10+ fields are the highest priority for decomposition.
674
674
  | `SubagentSessionIO` | split → `EnvironmentIO` (3) + `SessionFactoryIO` (5+1) | create-subagent-session | ✓ done |
675
675
  | `CreateSessionOptions` | 9 | create-subagent-session (SDK bridge) | Low (SDK) |
676
676
  | `AgentToolDeps` | 8 | agent-tool | ✓ done |
677
- | `AgentMenuDeps` | 8 | agent-menu | ✓ done |
678
- | `ConversationViewerOptions` | 8 | conversation-viewer | Low |
679
677
  | `SubagentInit` | 5 (id, type, description, invocation, execution, state) | subagent (one production site) | ✓ done |
680
678
  | `SubagentExecution` | 12 (4 mandatory: factory, snapshot, prompt, baseCwd) | subagent (mandatory collaborator) | ✓ done |
681
679
 
@@ -689,23 +687,22 @@ No functions remain above the critical threshold — all hotspots resolved in Ph
689
687
 
690
688
  Files with highest commit frequency × complexity:
691
689
 
692
- | Score | File | Commits | Trend |
693
- | ----- | --------------------------- | ------- | -------------- |
694
- | 65.0 | `index.ts` | 128 | accelerating |
695
- | 9.1 | `ui/agent-widget.ts` | 13 | ▼ cooling |
696
- | 8.4 | `ui/conversation-viewer.ts` | 11 | stable |
697
- | 6.4 | `runtime.ts` | 12 | stable |
698
- | 3.3 | `settings.ts` | 4 | stable |
699
- | 2.9 | `handlers/lifecycle.ts` | 11 | stable |
690
+ | Score | File | Commits | Trend |
691
+ | ----- | ----------------------------- | ------- | -------------- |
692
+ | 27.1 | `index.ts` | 109 | cooling |
693
+ | 9.6 | `tools/agent-tool.ts` | 56 | ▼ cooling |
694
+ | 8.8 | `ui/agent-widget.ts` | 22 | cooling |
695
+ | 7.3 | `tools/foreground-runner.ts` | 22 | cooling |
696
+ | 6.6 | `service/service-adapter.ts` | 15 | accelerating |
697
+ | 6.3 | `config/custom-agents.ts` | 13 | cooling |
700
698
 
701
- Most files have cooled to stable after 13 phases of structural work.
702
- `index.ts` remains the sole accelerating hotspot — expected as the wiring entry point for each refactoring phase.
699
+ `index.ts` remains the top churn hotspot but has cooled after the Phase 19 terminal cut removed its four `/agents`-wiring blocks; `service-adapter.ts` is now the sole accelerating file.
703
700
 
704
701
  ### Production duplication
705
702
 
706
703
  The prior clone group between `agent-runner.ts` and `message-formatters.ts` was resolved in #172.
707
704
  The 20-line clone group between `agent-config-editor.ts` and `agent-creation-wizard.ts` was resolved in #217 — extracted into `ui/agent-file-writer.ts` (`writeAgentFile`).
708
- One 11-line internal clone group remains within `agent-config-editor.ts` (lines 135–145 / 173–183).
705
+ The final 11-line internal clone group within `agent-config-editor.ts` was eliminated in Phase 19 Step 6 ([#441]) when the file itself was deleted; production duplication is 0 lines.
709
706
 
710
707
  ### Session encapsulation debt (Law of Demeter) — resolved by [#277] ✔️
711
708
 
@@ -905,262 +902,214 @@ Source LOC decreased from 7,751 (62 files) to 7,650 (61 files); tests grew from
905
902
  All eight steps are closed: [#420], [#421], [#422], [#423], [#424], [#425], [#426], [#427].
906
903
  See [phase-18-reconsider-ui.md](history/phase-18-reconsider-ui.md) for the full findings, step outcomes, dependency diagram, and tracks.
907
904
 
908
- ## Phase 19 (proposed)
905
+ ## Phase 19 (complete)
909
906
 
910
- Phase 19 implements the per-component UI decisions recorded in [ADR-0004]: shrink the widget to background-only, replace the bespoke conversation viewer with native session navigation, dissolve the monolithic `/agents` menu, and keep the surviving UI in-core.
907
+ Phase 19 implemented the per-component UI decisions recorded in [ADR-0004]: it shrank the widget to background-only agents, replaced the bespoke conversation viewer with native session navigation dual-sourced by liveness (live record or persisted-file snapshot) and rendered through Pi's own per-entry TUI components, extracted subagent settings to a focused `/subagents-settings` command, and dissolved the monolithic `/agents` menu in a two-commit terminal cut (hub first, then the orphaned agent-definition-management subtree).
908
+ Production duplication dropped to 0 lines; test clone groups dropped from 16 to 9 (target: ≤ 10); source shrank from 7,650 LOC (61 files) to 7,068 LOC (57 files).
909
+ All nine steps are closed: [#446], [#447], [#444], [#445], [#462], [#463], [#442], [#441], [#443].
910
+ A follow-on issue, [#470] (README staleness after the terminal cut), was filed and closed independently.
911
+ See [phase-19-implement-ui-decisions.md](history/phase-19-implement-ui-decisions.md) for the full findings, step outcomes, dependency diagram, and tracks.
911
912
 
912
- The sequencing follows Kent Beck's "make the change easy, then make the easy change."
913
- The end state deletes `agent-menu.ts` — the god-command that bundles four unrelated jobs — and everything reachable only from it.
914
- Rather than surgically mutate that doomed module (and the #1 churn hotspot `index.ts`) once per option, Phase 19 first stands up the replacement surfaces additively, then removes the now-orphaned subtree in a single terminal cut.
915
- This keeps every responsibility's old surface live until its replacement exists (ADR-0004's no-interim-regression invariant), turns the three replacement steps into genuinely parallel work (none touch `agent-menu.ts`), and reduces `index.ts` edits from four surgical removals to one deregistration.
913
+ ## Phase 20 improvement roadmap
916
914
 
917
- Seven numbered steps in three phases, plus two follow-ups (Steps 4a–4b) carved from the #445 slice:
915
+ Phase 20 realizes the last un-extracted domain from the [first-principles refinement](#first-principles-refinement-and-the-deeper-target) **result delivery** — and clears the residual boundary and complexity debt discovery surfaced around it.
918
916
 
919
- - **Phase A — stand up replacements (additive):** spike, settings command, background widget, native session navigation and its renderer/source follow-ups (Steps 1–4, 4a–4b).
920
- - **Phase B — dissolve `/agents` (terminal cut):** delete the orphaned subtree in two deletion commits, one per subtree (Steps 5–6).
921
- - **Phase C — test health:** consolidate the test clones that survive the cut (Step 7).
917
+ Discovery findings (fallow + entry-point trace + test-constructibility audit, 2026-07-03):
922
918
 
923
- ### Health metrics (Phase 18 Phase 19 target)
919
+ 1. `NotificationState` (`toolCallId`, `resultConsumed`) still lives on `Subagent`; `get-result-tool` reaches through `record.notification?.markConsumed()` twice, always paired with `notifications.cancelNudge(id)` — the doc's own "homeless field" (result-delivery domain) plus a scattered two-step reset.
920
+ 2. Both `steer-tool` and `service-adapter` pre-check `status !== "running"` before calling `record.steer()` — ask-then-tell, contradicting the target's "tell by id, with outcomes" rule.
921
+ 3. Five file-level eslint-disable headers (`agent-tool` disables 6 rules; `spawn-config` and `agent-widget` 4 each; `model-resolver` 2; `index` 1) and `model: unknown`/`Model<any>`/`any` threading through 8 files mark the SDK type boundary as the largest remaining `any` surface.
922
+ 4. Three src functions carry HIGH CRAP scores (notification renderer arrow 79.4, `service-adapter.spawn` 71.3, `get-result-tool.execute` 63.6 — resolved by Step 2); `subagents-settings.handle` (13 cyclomatic, 24 cognitive) is three copy-pasted select→input→validate→apply branches; `service-adapter.ts` is the sole accelerating churn file.
923
+ 5. `createTestSubagent` is the most complex function in the workspace (19 cyclomatic, 25 cognitive) because `SubagentStateInit` accepts only transition fields, forcing mutation loops to seed metrics — a Category D "shared factory complexity" signal pointing at the production init surface.
924
+ 6. Test duplication sits at 9 in-package clone groups (81 lines), at the ≤ 10 target but with two consolidatable clone families.
924
925
 
925
- | Metric | Phase 18 (current) | Phase 19 target |
926
- | ---------------------- | ------------------------ | -------------------- |
927
- | Health score | 78/100 (B) | 83/100 (B+) |
928
- | Source LOC | 7,650 (61 files) | ~6,780 (~55 files) |
929
- | Production duplication | 11 lines (1 group) | 0 lines |
930
- | Test clone groups | 16 | ≤ 10 |
931
- | Top churn hotspot | `index.ts` (103 commits) | `index.ts` (cooling) |
926
+ No finding scores 20 on the priority scale (Impact × (6 − Risk)); the phase is a should-fix band (10–15) consolidation.
927
+ Directory organization is healthy (seven domain directories, six root files) — no reorg this phase.
932
928
 
933
- ### Step 1 — Spike: resolve ADR-0004 entry criteria ([#446])
929
+ ### Health metrics (Phase 20)
934
930
 
935
- Smell: Category C (coupling boundary) four open decisions block the session-navigation implementation.
936
- Target: `docs/decisions/0004-reconsider-ui-direction.md` addendum.
931
+ | Metric | Phase 19 (end) | Phase 20 target |
932
+ | ------------------------------------------------ | ---------------- | ---------------------- |
933
+ | Health score | 78/100 (B) | ≥ 78 (B) |
934
+ | Source LOC | 7,068 (57 files) | ~7,050 (no net growth) |
935
+ | `record.notification?.` reach-throughs | 4 sites | 0 |
936
+ | Steer status pre-checks outside `Subagent.steer` | 2 sites | 0 |
937
+ | src functions with CRAP ≥ 60 | 3 | 0 |
938
+ | File-level eslint-disable headers | 5 | ≤ 2 |
939
+ | `createTestSubagent` cyclomatic | 19 | ≤ 8 |
940
+ | Test clone groups (in-package) | 9 (81 lines) | ≤ 5 (≤ 40 lines) |
937
941
 
938
- The four entry criteria from ADR-0004:
942
+ ### Steps
939
943
 
940
- 1. **Root-continuity:** Does the root's in-flight turn survive `ctx.switchSession()` and a return gesture?
941
- 2. **View-only vs interactive:** `switchSession` (full interactive takeover) or `loadEntriesFromFile` (read-only transcript built from JSONL)?
942
- 3. **Parallel-agent navigation:** Operator gesture to select which of N background agents to view (from the widget, a command, or both).
943
- 4. **Settings command name:** `/subagents-settings`, `/agents-settings`, or another form consistent with sibling packages?
944
+ #### Step 1 Extract result delivery from `Subagent` ([#535])
944
945
 
945
- Produce a minimal spike (observed test or PoC against a real session) that answers each question, then record the answers as an addendum to ADR-0004.
946
- No production source files change; the spike closes when the ADR addendum is merged.
946
+ Smell: Category C (anemic domain / misplaced state, Law of Demeter, scattered resets) the result-delivery domain named in the first-principles refinement is still fused into the execution record.
947
+ Target files:
947
948
 
948
- Outcome: ADR-0004 updated with all four entry-criteria answers; Step 4 unblocked.
949
+ - `src/lifecycle/subagent.ts` drop `_notification` / `notification`; stop constructing `NotificationState` from `parentSession.toolCallId`.
950
+ - `src/observation/notification.ts` — `NotificationManager` owns consumed-state keyed by agent id behind a single tell operation (e.g. `consume(id)`) that also cancels the pending nudge.
951
+ - `src/observation/notification-state.ts` — dissolve into the manager or move wholly into the observation domain.
952
+ - `src/observation/subagent-events-observer.ts`, `src/tools/get-result-tool.ts` — call the new delivery interface instead of reaching through the record.
949
953
 
950
- `Release: independent`
954
+ The `toolCallId` needed by `formatTaskNotification` already travels on `execution.parentSession`; expose it without routing through a notification object.
955
+ The pre-await consumption ordering (the "Bug 1" race tests in `test/lifecycle/subagent-manager.test.ts`) is a preserved invariant — consuming before awaiting must still suppress the completion nudge.
956
+
957
+ Outcome: zero `record.notification?.` reach-throughs in `src/`; `Subagent` carries no notification field; delivery state lives in the observation domain.
951
958
 
952
- ### Step 2 Extract settings to a focused `/subagents-settings` command ([#447])
959
+ Landed: `notification-state.ts` deleted; `Subagent.toolCallId` getter added over `execution.parentSession`; `NotificationManager` owns `consumed: Set<string>` behind one `consume(id)` tell that adds to the set and cancels the pending nudge atomically.
960
+ Collapsing the old two-step reset (`markConsumed()` + `cancelNudge()`) into one atomic operation structurally eliminates the historical "Bug 1" race rather than just reordering it — `consume()` now suppresses the nudge regardless of whether it runs before or after the completion promise resolves, as long as it runs within the 200 ms hold window.
953
961
 
954
- Smell: Category E (naming/organization) — settings are buried inside the monolithic `/agents` command per ADR-0004 Decision C. This step is purely additive: it stands up the new surface without touching `agent-menu.ts`.
962
+ `Release: batch "result-delivery"`
963
+
964
+ #### ✅ Step 2 — Decompose `get-result-tool.execute` ([#536])
965
+
966
+ Smell: Category B (oversized function) — 61 lines, 15 cyclomatic, CRAP 63.6; mixes wait/consume policy, stats formatting, and output assembly.
955
967
  Target files:
956
968
 
957
- - New `src/ui/subagents-settings.ts` — `SubagentsSettingsHandler` lifted from `AgentsMenuHandler.showSettings`, carrying its own narrow `SubagentsSettingsManager` interface (the three `apply*` methods and three readonly accessors only).
958
- - `src/index.ts` — register the new command (name confirmed by Step 1); pass `settings` directly.
959
- - New `test/ui/subagents-settings.test.ts` — unit tests for the extracted handler.
969
+ - `src/tools/get-result-tool.ts` — extract a pure report formatter (status line, stats parts, body selection) alongside the existing `result-renderer.ts` pattern; consume via the Step 1 delivery interface.
970
+ - `test/tools/get-result-tool.test.ts` — unit-test the pure formatter directly.
960
971
 
961
- `showSettings` depends only on `this.settings` (the self-contained `AgentMenuSettings` shape), so the extraction copies that logic into a new file with zero coupling to the wizard, editor, or viewer.
962
- The old in-menu Settings option keeps working until the terminal cut deletes `agent-menu.ts` wholesale — there is no surgical removal of `showSettings` or `AgentMenuSettings` from the doomed file.
972
+ Outcome: `execute` 30 lines with cyclomatic < 10; off the fallow high-complexity list.
963
973
 
964
- Outcome: new `subagents-settings.ts` (~80 LOC) and focused command registered; `agent-menu.ts` untouched.
974
+ Landed: `src/tools/get-result-report.ts` added `AgentReport` value object plus `renderStatsParts` / `renderReportBody` / `formatAgentReport` pure functions, unit-tested directly in `test/tools/get-result-report.test.ts`.
975
+ `GetResultTool.execute` now owns only record lookup and the wait/consume policy (13 lines), delegating report assembly to a private `buildReport` + `formatAgentReport`; output is byte-identical.
976
+ `get-result-tool.execute` is off the HIGH-CRAP list (3 → 2 remaining: `service-adapter.spawn`, the notification renderer arrow).
965
977
 
966
- `Release: independent`
978
+ `Release: batch "result-delivery"`
967
979
 
968
- ### ✅ Step 3 — Shrink widget to background agents only ([#444])
980
+ #### ✅ Step 3 — `Subagent.steer` returns an outcome ([#537])
969
981
 
970
- Smell: Category C (coupling) — the widget shows all agents including foreground ones, duplicating the `subagent` tool's inline `onUpdate` stream for foreground runs.
982
+ Smell: Category C (ask-then-tell) — coordinators pre-check status before telling.
971
983
  Target files:
972
984
 
973
- - `src/ui/agent-widget.ts` — funnel both `manager.listAgents()` call sites (`update()` and `renderWidget()`) through a single private accessor, then flip that accessor to background-only via `record.invocation?.runInBackground === true`.
974
- - `src/ui/widget-renderer.ts` — verify no foreground-specific rendering path survives.
975
- - `test/ui/agent-widget.test.ts` — add background-only filtering tests; update assertions.
985
+ - `src/lifecycle/subagent.ts` — `steer` owns the non-running rejection and returns a discriminated outcome (`delivered` / `buffered` / `rejected` with the observed status).
986
+ - `src/tools/steer-tool.ts`, `src/service/service-adapter.ts`drop the status pre-checks and switch on the outcome; the adapter maps the outcome to the public `SubagentsService.steer` boolean, so the published contract is unchanged.
976
987
 
977
- The widget calls `listAgents()` at two sites today — `update()` (feeding `seedFinishedAgents`, `assembleWidgetState`, and `clearWidget`) and `renderWidget()` (the tree map).
978
- Filtering at only one site leaves the other rendering foreground agents, so the enabling move is to route both through one accessor and apply the predicate once at the source.
979
- `Subagent.invocation.runInBackground` is the reliable signal: set by `spawn-config.ts` → `AgentInvocation.runInBackground` → stored on `Subagent.invocation`.
980
- ADR-0004 Decision A: foreground runs suppress the widget; the inline `onUpdate` stream is authoritative there.
988
+ Outcome: zero steer status pre-checks outside `Subagent.steer`; `steer-tool.execute` cyclomatic drops below 10.
981
989
 
982
- Outcome: widget shows only background agents; foreground/widget duplication eliminated; the background predicate lives at a single funnel.
990
+ Landed: `Subagent.steer` returns a discriminated `SteerOutcome` (`delivered` / `buffered` / `rejected` with the observed status) and owns the non-running rejection as its first guard; `SteerOutcome` is exported from `subagent.ts` and re-exported via `types.ts`.
991
+ `SteerTool.execute` and `SubagentsServiceAdapter.steer` dropped their `status !== "running"` pre-checks and switch on the outcome — the adapter maps `outcome.kind !== "rejected"` to the unchanged public boolean, and the tool's delivered-path stats moved into a private `renderDelivered` helper.
992
+ Zero steer status pre-checks remain outside `Subagent.steer`.
983
993
 
984
994
  `Release: independent`
985
995
 
986
- ### ✅ Step 4 — Implement native session navigation ([#445])
996
+ #### ✅ Step 4 — Type the model boundary ([#538])
987
997
 
988
- Smell: Category C (coupling) — the bespoke `ConversationViewer` re-implements session-transcript rendering when Pi's own machinery targets the already-persisted child session JSONL.
989
- This step adds the new surface alongside the existing viewer; it does not touch `agent-menu.ts`.
998
+ Smell: Category C (platform type threading) — `ModelRegistry.find/getAll/getAvailable` return `any`, forcing `any`/`unknown` model threading through `model-resolver`, `spawn-config`, `service-adapter`, and `parent-snapshot`.
990
999
  Target files:
991
1000
 
992
- - New `src/ui/session-navigator.ts` — a flat command that lists any subagent with a live record or a persisted session file (foreground included, never background-filtered), lets the operator pick one, and renders that child's transcript read-only.
993
- - New typed accessor on `Subagent`/`SubagentSession` returning `record.messages` as `AgentMessage[]` (the boundary currently widens it to `readonly unknown[]`).
994
- - `src/index.ts` — register the new command; the background widget ([#444]) is an optional secondary selection gesture, not a dependency.
1001
+ - `src/session/model-resolver.ts` — type the registry against `Model<any>` from `@earendil-works/pi-ai` (already imported elsewhere); remove the file-level eslint-disable; extract the fuzzy-scoring loop as a named helper if `resolveModel` (17 cyclomatic, 60 lines) still trips the threshold.
1002
+ - `src/service/service-adapter.ts` type the resolved model in `spawn` (16 cyclomatic, CRAP 71.3, sole accelerating churn file) and extract the model-resolution branch.
1003
+ - `src/tools/spawn-config.ts` — shrink the 4-rule file-level disable to line-level or remove it.
995
1004
 
996
- ADR-0004 Decision B: "Tell-Don't-Ask hand Pi the session path; Pi owns the viewer."
997
- Mechanism (confirmed by the Step 1 spike and revised by [ADR-0004] Addendum 2): a **read-only** (non-interactive) transcript **dual-sourced by liveness**, rendered through Pi's own public entry components (no bespoke renderer).
1005
+ Outcome: `model-resolver.ts` file-level eslint-disable removed; `service-adapter.spawn` off the HIGH CRAP list; `any` model returns eliminated from the resolver.
998
1006
 
999
- - **Tracked agent** (still in `manager.listAgents()`) render live from the in-memory record: `record.messages` for history, `record.subscribeToUpdates()` to re-render on streaming updates, and `record.activeTools` / `record.responseText` for the running-agent streaming indicator.
1000
- - **Evicted / untracked agent** render from the file snapshot: `parseSessionEntries(readFileSync(record.outputFile, "utf8"))` drop the `SessionHeader` `buildSessionContext(...).messages`.
1007
+ Landed: `ModelRegistry.find/getAll/getAvailable` and `resolveModel`'s return are typed against `Model<any>` from `@earendil-works/pi-ai`; the file-level eslint-disable headers on `model-resolver.ts` (2 rules) and `spawn-config.ts` (4 rules) are both removed — running disable-header tally 5 3 (`agent-tool` 6, `agent-widget` 4, `index` 1 remain, all Step 5 scope).
1008
+ `resolveModel`'s fuzzy-scoring loop was extracted to a private `findBestFuzzyMatch` helper, dropping `resolveModel` off the complexity list entirely.
1009
+ `service-adapter.spawn`'s model-resolution branch was extracted to a private `resolveModelOption`, dropping `spawn` from 16 cyclomatic / CRAP 71.3 (HIGH) to 13 cyclomatic / CRAP 49.5 (moderate) — off the HIGH-CRAP list; running HIGH-CRAP tally 2 → 1 remaining (the notification renderer arrow, untouched — Step 7 scope).
1010
+ `resolveInvocationModel` gained a `registry: ModelRegistry | undefined` guard (typed error instead of a crash when a model override is requested with no registry present); the residual `unknown` thread through `ParentSnapshot.model` / `SessionContext.model` is a separate SDK-boundary gap, deferred.
1001
1011
 
1002
- Both sources yield `AgentMessage[]`, so one Pi-component renderer serves both: Pi's public entry components (`AssistantMessageComponent` / `ToolExecutionComponent` / …) or `serializeConversation` (see the [ADR-0004] addendum, Findings 0 and 1).
1003
- Neither `switchSession` (a full takeover that invalidates the root's in-flight turn) nor `loadEntriesFromFile` (a test-only export the package's public barrel does not re-export, in both `0.79.1` and `0.79.8`) is used.
1004
- `Subagent.outputFile` already exposes the persisted child session JSONL path via `subagentSession?.outputFile` — no new SDK dependency.
1005
- The new surface stands up while the old `viewAgentConversation`/`ConversationViewer` path still works; the bespoke viewer is removed only by the terminal cut (Step 5).
1006
-
1007
- Outcome: operator views any subagent's session through Pi's native machinery — live for a running agent, a file snapshot for an evicted one; the new surface coexists with the old viewer until Step 5.
1008
-
1009
- Landed ([#445], sliced): #445 shipped the first releasable vertical slice — the `/subagent-sessions` command (`src/ui/session-navigator.ts`), the pure selection/sourcing/text-render core (`src/ui/session-navigation.ts`), and the typed `agentMessages` accessor (`SessionMessage` on `SubagentSession`/`Subagent`).
1010
- It is **live-source only** behind a renderer-agnostic `TranscriptSource` seam, rendered via Pi's `serializeConversation` text.
1011
- With the `manager.listAgents()`-only candidate set, no listed record is ever session-disposed (dispose-and-delete are atomic), so the file-snapshot branch has no reachable caller and was deferred to keep `fallow dead-code` clean.
1012
- Step 4 (#445, the slice) is complete and released (`pi-subagents` v17.3.0); the remaining work is now tracked as two follow-up steps behind the same seam: Step 4a ([#462]) upgrades the renderer from `serializeConversation` text to Pi's per-entry TUI components (gates Step 5 for rendering parity); Step 4b ([#463]) broadens the candidate set to evicted agents and adds the file-snapshot source (`parseSessionEntries` → `buildSessionContext`, independent).
1013
-
1014
- `Release: independent` (spike-gated)
1012
+ `Release: independent`
1015
1013
 
1016
- ### Step 4aUpgrade native-navigation renderer to Pi TUI components ([#462])
1014
+ #### Step 5Narrow `tui`/`theme` render interfaces ([#539])
1017
1015
 
1018
- Smell: Category C (coupling) the #445 slice renders the transcript as `serializeConversation` plain text, while the bespoke `ConversationViewer` it replaces renders richer per-message formatting.
1019
- Until the native renderer reaches parity, the terminal cut (Step 5) cannot delete the bespoke viewer without a fidelity regression.
1020
- This step swaps the renderer behind the existing `TranscriptSource` seam (`src/ui/session-navigation.ts` / `session-navigator.ts`) for Pi's per-entry components (`AssistantMessageComponent` / `ToolExecutionComponent` / …); selection and sourcing are untouched.
1016
+ Smell: Category C/D (platform type threading; wide `any` params in render callbacks).
1017
+ Target files:
1021
1018
 
1022
- Gates Step 5: per [ADR-0004]'s no-interim-regression invariant, the native navigator must reach rendering parity with the bespoke viewer before Step 5 deletes it.
1019
+ - `src/ui/agent-widget.ts` replace `tui: any` with a lean local interface (`terminal.columns`, `requestRender()`); shrink the 4-rule file-level disable.
1020
+ - `src/tools/agent-tool.ts` — type `renderCall`/`renderResult` params (`theme`, `result`) with lean local interfaces; shrink the 6-rule file-level disable to the genuinely SDK-gapped lines.
1021
+ - `src/tools/foreground-runner.ts` — retire the line-level `details as any` cast if the SDK surface allows.
1023
1022
 
1024
- Outcome: native session navigation renders at parity with the removed `ConversationViewer`; Step 5 can delete the bespoke viewer with no fidelity regression.
1023
+ Some disables are irreducible SDK export gaps; the goal is line-level precision, not zero.
1025
1024
 
1026
- Landed ([#462]): the renderer now mounts Pi's per-entry components (`AssistantMessageComponent` / `ToolExecutionComponent` / `BashExecutionComponent` / `UserMessageComponent` / `CompactionSummaryMessageComponent` / `BranchSummaryMessageComponent` / `SkillInvocationMessageComponent`) into a `Container`, mirroring Pi's own `renderSessionContext` mapping.
1027
- The `TranscriptOverlay` caches that `Container` and rebuilds it on source change only (Pi's `rebuildChatFromMessages` path), keeping the lightweight `◍` streaming indicator.
1028
- Tool calls render with their real `ToolDefinition`, resolved through a dependency-safe `getToolDefinition` read accessor on the record (mirroring `agentMessages`) surfaced on the `TranscriptSource` seam — no inbound call into the core.
1029
- The pure `session-navigation.ts` sheds `renderTranscriptLines`/`serializeConversation`; rendering now lives in the SDK/TUI `session-navigator.ts`, which threads `cwd` from the command context.
1030
- `custom`-role messages are skipped (the bespoke viewer never rendered them either).
1031
- Selection and sourcing are untouched; native navigation now renders at parity, unblocking Step 5 for rendering fidelity.
1025
+ Outcome: file-level eslint-disable headers 5 2; remaining suppressions are line-level with named rules.
1032
1026
 
1033
1027
  `Release: independent`
1034
1028
 
1035
- ### Step 4bFile-snapshot source for evicted agents ([#463])
1029
+ #### Step 6Table-driven settings handler ([#540])
1036
1030
 
1037
- Smell: Category C (coupling) the #445 slice sources transcripts live from `manager.listAgents()` only; an agent evicted by the 10-minute cleanup sweep has a persisted session JSONL but no live record, so it is unreachable.
1038
- This step adds the file-snapshot `TranscriptSource` branch (`parseSessionEntries(readFile(outputFile))` → drop the `SessionHeader` → `buildSessionContext(...).messages`) and broadens the candidate set to evicted agents, behind the same seam; the renderer is untouched.
1039
-
1040
- Independent: this is a new capability the bespoke viewer never had, so it gates nothing and is not a Step 5 prerequisite.
1041
- Best sequenced after Step 4a (shared renderer), but carries no hard dependency.
1031
+ Smell: Category B (function duplication inside one function) `subagents-settings.handle` (13 cyclomatic, 24 cognitive, 52 lines) repeats the select→input→parse→validate→apply→notify flow three times.
1032
+ Target files:
1042
1033
 
1043
- Outcome: the operator can view a fully-evicted agent's transcript from its persisted session file; the dual-source design recorded in [ADR-0004] Addendum 2 is fully realized.
1034
+ - `src/ui/subagents-settings.ts` describe each numeric setting as a descriptor (label, prompt, minimum, validation message, apply method) and drive one loop over the table.
1035
+ - `test/ui/subagents-settings.test.ts` — assert per-descriptor behavior.
1044
1036
 
1045
- Landed ([#463]): `fileSnapshotSource(outputFile, readFile)` lands in the pure `session-navigation.ts` (`parseSessionEntries` drop the `SessionHeader` → `buildSessionContext(...).messages`; a static no-subscribe, no-streaming source).
1046
- The candidate set is broadened via **manager-retained descriptors**, not a directory scan: the persisted child session carries no subagent `type`/`description` (those live only on the in-memory record), so a scan would yield degraded labels and parse every file per open.
1047
- Instead `SubagentManager.cleanup()` stashes a lightweight `EvictedSubagent` descriptor (label fields + `outputFile`, no messages) before disposing a record with a persisted file, exposed via `listEvicted()` and cleared by `clearCompleted()`/`dispose()`.
1048
- `NavigationEntry` became a `live | evicted` discriminated union; the handler selects `liveSource` vs `fileSnapshotSource` by kind inside a `try/catch` (an unreadable file notifies and skips), and `index.ts` injects `readFileSync`.
1049
- Evicted entries carry an `· evicted (snapshot)` label marker.
1050
- Coverage is in-session evictions (the sweep's only targets); old-session orphan files — the ones a scan would surface with degraded labels — are out of scope.
1037
+ Outcome: `handle` cyclomatic 6 and cognitive 10; off the fallow high-complexity list.
1051
1038
 
1052
1039
  `Release: independent`
1053
1040
 
1054
- ### Step 5Dissolve `/agents` and remove the conversation-viewer subtree ([#442])
1041
+ #### Step 7Decompose the notification renderer ([#541])
1055
1042
 
1056
- Smell: Category A (dead subsystem) plus Category B (oversized) — once Steps 2–4 re-home all four menu responsibilities, the `/agents` command and everything reachable only from `agent-menu.ts` is an unreferenced subtree.
1057
- This is the first of two deletion commits (split by subtree).
1058
- The hub `agent-menu.ts` is deleted here, not surgically narrowed, and deleting it is what orphans the leaf subtrees — so it must precede the definition-management deletion (Step 6), because `agent-menu.ts` statically imports the wizard, editor, and file-ops, and dynamically imports the viewer.
1043
+ Smell: Category B/D (oversized arrow, untested complexity) — the renderer arrow in `src/observation/renderer.ts` is fallow's top triage concern (17 cyclomatic, CRAP 79.4).
1059
1044
  Target files:
1060
1045
 
1061
- - `src/index.ts` — remove the `registerCommand("agents", …)` block, the `AgentsMenuHandler` construction and import, and the `FsAgentFileOps` import/construction (its only use is wiring the menu).
1062
- - Delete `src/ui/agent-menu.ts` (331 LOC) and `test/ui/agent-menu.test.ts` (185 LOC).
1063
- - Delete `src/ui/conversation-viewer.ts` (241 LOC) and `test/conversation-viewer.test.ts` (239 LOC) — its only consumer is `agent-menu.ts`'s dynamic import, gone with the hub.
1064
- - Delete `src/ui/message-formatters.ts` (195 LOC) and `test/message-formatters.test.ts` (388 LOC, the largest test function by LOC) — its only consumer is `ConversationViewer`.
1065
-
1066
- Running-agent visibility is now owned by the background widget (Step 3); session navigation replaces the bespoke overlay (Step 4); settings live in `/subagents-settings` (Step 2).
1067
- Deleting the hub in one move avoids any surgical edit to the doomed file and leaves the definition-management leaves orphaned for Step 6.
1046
+ - `src/observation/renderer.ts` — extract pure line-assembly helpers (status→icon/label selection, stats-parts assembly, preview truncation) that are unit-testable without `Text` or a theme; the arrow becomes a thin wrapper.
1047
+ - `test/observation/renderer.test.ts` test the pure helpers directly.
1068
1048
 
1069
- Actual approach (vs. original plan): a tidy-first preparatory commit first extracted `MenuUI` from `agent-menu.ts` into a new `src/ui/menu-ui.ts` module, breaking the bidirectional type cycle — the hub imported the wizard/editor classes while the leaves imported back the `MenuUI` type.
1070
- Without the prep commit, deleting either subtree first would have left the other half referencing a deleted module.
1071
- `index.ts` also shed the now-dead `join` (node:path) and `buildParentSnapshot` imports in addition to the two module imports.
1072
- The `menu-ui.ts` module is transient and is removed with the wizard/editor in Step 6.
1049
+ Soft ordering: land after Step 1 so the notification-domain files settle first.
1073
1050
 
1074
- Outcome: `/agents` dissolved; −767 LOC source (menu hub + viewer + formatters); −812 LOC test; largest test function eliminated; `index.ts` dewired.
1051
+ Outcome: renderer arrow cyclomatic < 10; `renderer.ts` off the top of the fallow triage list.
1075
1052
 
1076
- `Release: batch "dissolve-agents"`
1053
+ `Release: independent`
1077
1054
 
1078
- ### Step 6Remove the orphaned agent-definition management subtree ([#441])
1055
+ #### Step 8Full-value `SubagentStateInit` ([#542])
1079
1056
 
1080
- Smell: Category A (dead subsystem) the creation wizard and config editor are removed per ADR-0004 Decision C; after Step 5 deletes their only importer (`agent-menu.ts`), they and their file-ops helpers are pure orphans.
1081
- This is the second deletion commit (split by subtree).
1057
+ Smell: Category D (shared factory complexity narrow/complete the production init surface) `createTestSubagent` (19 cyclomatic, 25 cognitive) seeds metrics via mutation loops because `SubagentStateInit` accepts only transition fields.
1082
1058
  Target files:
1083
1059
 
1084
- - Delete `src/ui/agent-creation-wizard.ts` (233 LOC) and `test/ui/agent-creation-wizard.test.ts` (296 LOC).
1085
- - Delete `src/ui/agent-config-editor.ts` (199 LOC) and `test/ui/agent-config-editor.test.ts` (392 LOC) eliminates the 11-line internal production clone in `disableAgent`/`ejectAgent`, the package's only remaining production duplication.
1086
- - Delete `src/ui/agent-file-ops.ts` (59 LOC) and `test/ui/agent-file-ops.test.ts` (112 LOC) — only consumers were wizard + editor.
1087
- - Delete `src/ui/agent-file-writer.ts` (55 LOC) and `test/ui/agent-file-writer.test.ts` (148 LOC) — only consumers were wizard + editor.
1088
- - `test/helpers/ui-stubs.ts` — delete `makeFileOps`, `createTestSubagentConfig`, and `spawnAndWait` from `makeMenuManager` if no surviving consumer remains; delete the file outright once all consumers are gone.
1089
-
1090
- An operator generates a new agent `.md` by asking a Pi session directly (more capable than a fixed wizard) or by writing the file in an editor; viewing and editing definitions is served by opening the `.md` files in an editor or IDE.
1091
- These files are orphaned by Step 5, so this is a pure `git rm` with no surviving references and no edit to any doomed file.
1092
-
1093
- Outcome: −546 LOC source (wizard + editor + file-ops + file-writer); −948 LOC test; production duplication → 0 lines; 1 production and 1 test clone group eliminated.
1060
+ - `src/lifecycle/subagent-state.ts` extend `SubagentStateInit` to optionally seed the full value (toolUses, lifetimeUsage, compactionCount, turnCount, activeTools, responseText); a value object is legitimately constructible at any point in its value space.
1061
+ - `test/helpers/make-subagent.ts` — collapse the mutation loops into direct init.
1094
1062
 
1095
- Landed ([#441]): deleted `agent-creation-wizard.ts`, `agent-config-editor.ts`, `agent-file-ops.ts`, `agent-file-writer.ts`, and `menu-ui.ts` (orphaned transient) from `src/ui/`, plus their four test files.
1096
- `test/helpers/ui-stubs.ts` pruned to `makeMenuUI` only (still consumed by `subagents-settings.test.ts`); `makeFileOps`, `makeMenuManager`, and `createTestSubagentConfig` removed with their `ui-stubs.test.ts` describe blocks.
1097
- Production duplication: 0 lines (confirmed by `fallow dupes`); `fallow dead-code`: clean. 16 test clone groups remain — Step 7 consolidation target.
1063
+ Outcome: `createTestSubagent` cyclomatic 8; off the fallow complexity list; no production behavior change.
1098
1064
 
1099
- `Release: batch "dissolve-agents"`
1065
+ `Release: independent`
1100
1066
 
1101
- ### Step 7 — Consolidate remaining test clone families ([#443])
1067
+ #### Step 9 — Consolidate remaining test clone families ([#543])
1102
1068
 
1103
- Smell: Category D (testability) — 16 clone groups at Phase 18 end; the terminal cut (Steps 5–6) removes ~4 groups; remaining groups are extraction targets.
1104
- Run after the cut so no helper is extracted into a file the cut then deletes.
1105
- Target files:
1069
+ Smell: Category D (test duplication) — two clone families (`spawn-config.test.ts`: 2 groups / 21 lines; `subagent-manager.test.ts`: 2 groups / 15 lines) plus the `session-config.test.ts` pair (16 lines).
1070
+ Target files: the three test files and `test/helpers/` as needed.
1106
1071
 
1107
- - `test/lifecycle/subagent-manager.test.ts`extract a shared assertion helper for 3 clone families (23 lines across groups at :92/:109, :282/:330, and :323 shared with `subagent.test.ts`).
1108
- - `test/ui/agent-widget.test.ts` — merge the duplicate `makeWidget` helper defined twice across two `describe` blocks (14-line clone at :225/:284).
1109
- - `test/session/session-config.test.ts` — extract a shared fixture for the 16-line internal clone (lines 131–146 / 151–166).
1110
- - `test/lifecycle/concurrency-limiter.test.ts` — extract shared setup for the 10-line clone (lines 21–30 / 148–155).
1111
- - `test/tools/spawn-config.test.ts` — extract a shared fixture for the 9-line clone (lines 22–30 / 35–43).
1072
+ Runs lastSteps 1–3 and 8 rewrite portions of these suites, so consolidating first would churn twice.
1112
1073
 
1113
- Outcome: test clone groups 10 (from 16); `subagent-manager.test.ts` uses shared factory helpers.
1074
+ Outcome: in-package clone groups 9 5; duplicated lines 81 ≤ 40.
1114
1075
 
1115
1076
  `Release: independent`
1116
1077
 
1117
- ### Step dependency diagram
1078
+ ### Step dependencies
1118
1079
 
1119
1080
  ```mermaid
1120
1081
  flowchart LR
1121
- S1["✅ Step 1 - Spike (#446)"]
1122
- S2["Step 2 - Settings command (#447)"]
1123
- S3["✅ Step 3 - Background widget (#444)"]
1124
- S4["✅ Step 4 - Native session nav slice (#445)"]
1125
- S4a["Step 4a - Renderer to TUI components (#462)"]
1126
- S4b["Step 4b - File-snapshot source (#463)"]
1127
- S5["Step 5 - Dissolve /agents + viewer (#442)"]
1128
- S6["✅ Step 6 - Remove definition mgmt (#441)"]
1129
- S7["Step 7 - Test clones (#443)"]
1130
-
1131
- S1 --> S4
1132
- S4 --> S4a
1133
- S4 --> S4b
1134
- S2 --> S5
1135
- S3 --> S5
1136
- S4a --> S5
1137
- S5 --> S6
1138
- S6 --> S7
1082
+ S1["✅ Step 1 (#535)<br/>Result delivery off Subagent"] --> S2["✅ Step 2 (#536)<br/>Decompose get-result-tool"]
1083
+ S1 -.soft.-> S7["Step 7 (#541)<br/>Decompose notification renderer"]
1084
+ S3["✅ Step 3 (#537)<br/>Steer returns an outcome"]
1085
+ S4["✅ Step 4 (#538)<br/>Type the model boundary"]
1086
+ S5["Step 5 (#539)<br/>Narrow tui/theme interfaces"]
1087
+ S6["Step 6 (#540)<br/>Table-driven settings handler"]
1088
+ S8["Step 8 (#542)<br/>Full-value SubagentStateInit"] --> S9["Step 9 (#543)<br/>Consolidate test clones"]
1089
+ S2 --> S9
1090
+ S3 --> S9
1139
1091
  ```
1140
1092
 
1141
- The terminal cut (Step 5) depends on all three replacements — settings (Step 2), widget (Step 3), and session navigation **at rendering parity** (Step 4a, which completes the #445 slice) — because each of the four `/agents` options must have its responsibility re-homed, and the viewer replacement at parity, before its branch can die.
1142
- Step 4b (file-snapshot source) is a new capability and gates nothing.
1143
- The old `S1 → S6 → S7` chain hid the widget dependency; this diagram makes it explicit.
1144
-
1145
1093
  ### Parallel tracks
1146
1094
 
1147
- - **Track A — Replacements (Steps 1–4):** the spike gates session navigation (Step 1 → Step 4); settings (Step 2) and the background widget (Step 3) are independent of the spike and of each other.
1148
- None of these steps edits `agent-menu.ts`, so they carry no shared-file collision on the menu — genuinely parallelizable, unlike the prior plan's Steps 2/3/5, which all collided on `agent-menu.ts` and `index.ts`.
1149
- Steps 2 and 4 each append a command registration to `index.ts` (additive, low-conflict).
1150
- Steps 4a (renderer parity) and 4b (file-snapshot source) complete the #445 slice behind its `TranscriptSource` seam; Step 4a gates Step 5, Step 4b is independent.
1151
- - **Track BDissolution (Steps 5 6):** the terminal cut, gated on all of Track A landing.
1152
- Hub-first ordering is forced: Step 5 deletes `agent-menu.ts` (orphaning the leaves), then Step 6 `git rm`s the now-orphaned definition-management subtree.
1153
- - **Track C Test health (Step 7):** clone consolidation, run after the cut so no surviving helper is extracted into a doomed file.
1095
+ - **Track A — Result delivery:** Steps 1 → 2, then 7 (soft).
1096
+ - **Track B Tell-don't-ask:** Step 3.
1097
+ - **Track C SDK boundary:** Steps 4, 5 (independent of each other).
1098
+ - **Track D UI polish:** Step 6.
1099
+ - **Track ETest health:** Step 8, then 9 (9 also waits on Tracks A/B test churn).
1100
+
1101
+ Tracks A–D can proceed in parallel; only Step 9 serializes behind the rest.
1154
1102
 
1155
1103
  ### Release batches
1156
1104
 
1157
- - **Batch "dissolve-agents":** Steps 5, 6 (ship together; tail = Step 6).
1158
- Depends on Steps 2, 3, 4 already merged.
1159
- - Independently releasable: Steps 1, 2, 3, 4, 4a, 4b, 7.
1105
+ - **Batch "result-delivery":** Steps 1, 2 (ship together; tail = Step 2).
1106
+ - Independently releasable: Steps 3, 4, 5, 6, 7, 8, 9.
1107
+
1108
+ Every step lands as a `refactor:`/`test:` commit — hidden changelog types that cut no release on their own; the work auto-batches into the next unhidden release.
1160
1109
 
1161
1110
  ## Refactoring history
1162
1111
 
1163
- Phases 1–5, 7–18 are complete.
1112
+ Phases 1–5, 7–19 are complete.
1164
1113
  Phase 6 (UI extraction to a separate package) was deferred to Phase 18; its intent was resolved by [ADR-0004] (Phase 18 Step 8).
1165
1114
  Detailed records are preserved in per-phase history files:
1166
1115
 
@@ -1184,6 +1133,7 @@ Detailed records are preserved in per-phase history files:
1184
1133
  | 16 | Invert dependencies (extensions on a minimal core) | Complete | [phase-16-invert-dependencies.md](history/phase-16-invert-dependencies.md) |
1185
1134
  | 17 | Core consolidation | Complete | [phase-17-core-consolidation.md](history/phase-17-core-consolidation.md) |
1186
1135
  | 18 | Reconsider UI (first principles) | Complete | [phase-18-reconsider-ui.md](history/phase-18-reconsider-ui.md) |
1136
+ | 19 | Implement ADR-0004 UI decisions | Complete | [phase-19-implement-ui-decisions.md](history/phase-19-implement-ui-decisions.md) |
1187
1137
 
1188
1138
  ### Structural refactoring issues
1189
1139
 
@@ -1208,6 +1158,8 @@ Detailed records are preserved in per-phase history files:
1208
1158
  | Phase 17 | #381, #373, #374, #375, #376, #377, #378, #379, #380 | ConcurrencyLimiter, SubagentState, run-start encapsulation, run collaborators, events observer, widget decoupling, lifecycle test fixtures, UI/tools test fixtures, settings-loader extraction |
1209
1159
  | Phase 17 (follow-on) | #412, #415 | Session-mock builder unification, worktrees settings-helper migration |
1210
1160
  | Phase 18 | #420, #421, #422, #423, #424, #425, #426, #427 | Fold metrics onto record, migrate readers, delete activity tier, widget self-drives, drop widget from tool, reconcile event contract, consolidate test clones, UI-direction ADR |
1161
+ | Phase 19 | #446, #447, #444, #445, #462, #463, #442, #441, #443 | ADR-0004 spike, settings command, background widget, native session nav slice, TUI renderer, file-snapshot source, dissolve /agents + viewer, remove definition mgmt, consolidate test clones |
1162
+ | Phase 19 (follow-on) | #470 | README refresh for the removed /agents command surface |
1211
1163
 
1212
1164
  The remaining open issue is #22 (parent-session resolution), a cross-extension track that does not gate the structural work.
1213
1165
 
@@ -1280,5 +1232,15 @@ The upstream test suite is run periodically as a regression canary for the sessi
1280
1232
  [#447]: https://github.com/gotgenes/pi-packages/issues/447
1281
1233
  [#462]: https://github.com/gotgenes/pi-packages/issues/462
1282
1234
  [#463]: https://github.com/gotgenes/pi-packages/issues/463
1235
+ [#470]: https://github.com/gotgenes/pi-packages/issues/470
1236
+ [#535]: https://github.com/gotgenes/pi-packages/issues/535
1237
+ [#536]: https://github.com/gotgenes/pi-packages/issues/536
1238
+ [#537]: https://github.com/gotgenes/pi-packages/issues/537
1239
+ [#538]: https://github.com/gotgenes/pi-packages/issues/538
1240
+ [#539]: https://github.com/gotgenes/pi-packages/issues/539
1241
+ [#540]: https://github.com/gotgenes/pi-packages/issues/540
1242
+ [#541]: https://github.com/gotgenes/pi-packages/issues/541
1243
+ [#542]: https://github.com/gotgenes/pi-packages/issues/542
1244
+ [#543]: https://github.com/gotgenes/pi-packages/issues/543
1283
1245
  [ADR-0002]: ../decisions/0002-extensions-on-a-minimal-core.md
1284
1246
  [ADR-0004]: ../decisions/0004-reconsider-ui-direction.md