@gotgenes/pi-subagents 18.0.1 → 18.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/docs/architecture/architecture.md +157 -204
  3. package/docs/architecture/history/phase-19-implement-ui-decisions.md +282 -0
  4. package/package.json +5 -6
  5. package/src/lifecycle/subagent.ts +5 -9
  6. package/src/observation/notification.ts +23 -10
  7. package/src/observation/subagent-events-observer.ts +1 -3
  8. package/src/tools/get-result-report.ts +60 -0
  9. package/src/tools/get-result-tool.ts +25 -40
  10. package/src/types.ts +1 -1
  11. package/src/ui/session-navigator.ts +0 -2
  12. package/.prettierignore +0 -5
  13. package/AGENTS.md +0 -7
  14. package/docs/plans/0048-implement-subagents-api.md +0 -304
  15. package/docs/plans/0049-remove-group-join-output-file-rpc.md +0 -180
  16. package/docs/plans/0051-update-adr-0001-hard-fork.md +0 -77
  17. package/docs/plans/0052-remove-scheduled-subagents.md +0 -133
  18. package/docs/plans/0053-extract-model-resolution-from-execute.md +0 -181
  19. package/docs/plans/0054-decompose-index-into-modules.md +0 -302
  20. package/docs/plans/0057-structured-debug-logging.md +0 -154
  21. package/docs/plans/0061-session-format-transcript.md +0 -284
  22. package/docs/plans/0066-replace-as-any-with-sdk-types.md +0 -215
  23. package/docs/plans/0069-create-subagent-runtime.md +0 -345
  24. package/docs/plans/0070-extract-event-handlers.md +0 -306
  25. package/docs/plans/0071-extract-session-config-assembler.md +0 -362
  26. package/docs/plans/0072-inject-agent-manager-collaborators.md +0 -329
  27. package/docs/plans/0076-inject-cwd-into-agent-manager.md +0 -102
  28. package/docs/plans/0077-inject-project-agents-dir.md +0 -115
  29. package/docs/plans/0080-consolidate-agent-config-lookup.md +0 -247
  30. package/docs/plans/0084-extract-git-worktree-manager.md +0 -142
  31. package/docs/plans/0087-evolve-subagent-runtime-methods.md +0 -240
  32. package/docs/plans/0098-extract-agent-record-state-machine.md +0 -435
  33. package/docs/plans/0099-replace-ctx-with-parent-snapshot.md +0 -488
  34. package/docs/plans/0100-replace-callback-threading-with-session-subscription.md +0 -454
  35. package/docs/plans/0102-consolidate-test-record-factory.md +0 -176
  36. package/docs/plans/0108-extract-agent-type-registry.md +0 -322
  37. package/docs/plans/0109-extract-settings-manager.md +0 -276
  38. package/docs/plans/0110-agent-activity-tracker.md +0 -297
  39. package/docs/plans/0111-split-agent-record-lifecycle.md +0 -582
  40. package/docs/plans/0112-replace-agent-manager-callbacks.md +0 -241
  41. package/docs/plans/0113-disambiguate-spawn-options.md +0 -155
  42. package/docs/plans/0114-narrow-agent-tool-menu-deps.md +0 -279
  43. package/docs/plans/0115-decompose-agent-tool.md +0 -337
  44. package/docs/plans/0116-type-housekeeping.md +0 -351
  45. package/docs/plans/0118-settings-manager-apply-methods.md +0 -271
  46. package/docs/plans/0123-remove-vi-fn-cast-smell.md +0 -179
  47. package/docs/plans/0131-consolidate-shared-test-fixtures.md +0 -207
  48. package/docs/plans/0132-inject-io-into-session-config.md +0 -219
  49. package/docs/plans/0133-inject-sdk-boundary-into-agent-runner.md +0 -373
  50. package/docs/plans/0134-reduce-as-any-casts.md +0 -366
  51. package/docs/plans/0135-extract-display-helpers.md +0 -182
  52. package/docs/plans/0136-decompose-agent-menu.md +0 -300
  53. package/docs/plans/0144-consolidate-observation-model.md +0 -263
  54. package/docs/plans/0145-decompose-execute-push-ctx-to-boundary.md +0 -290
  55. package/docs/plans/0146-narrow-ui-context.md +0 -319
  56. package/docs/plans/0147-inject-wrap-text-into-conversation-viewer.md +0 -166
  57. package/docs/plans/0148-split-agent-widget-rendering.md +0 -255
  58. package/docs/plans/0152-add-prompt-snippet.md +0 -91
  59. package/docs/plans/0164-reorganize-into-domain-directories.md +0 -409
  60. package/docs/plans/0165-decompose-resolved-spawn-config.md +0 -157
  61. package/docs/plans/0166-extract-parent-session-info.md +0 -231
  62. package/docs/plans/0167-narrow-runner-io.md +0 -150
  63. package/docs/plans/0168-extract-tool-filter-config.md +0 -173
  64. package/docs/plans/0169-extract-run-context-from-run-options.md +0 -194
  65. package/docs/plans/0170-reduce-build-content-lines-complexity.md +0 -225
  66. package/docs/plans/0171-reduce-render-result-complexity.md +0 -200
  67. package/docs/plans/0172-extract-turn-formatting.md +0 -206
  68. package/docs/plans/0180-reorder-append-prompt-for-kv-cache.md +0 -100
  69. package/docs/plans/0185-remove-persistent-agent-memory.md +0 -161
  70. package/docs/plans/0188-replace-any-casts-with-sdk-types.md +0 -162
  71. package/docs/plans/0192-define-session-context-interface.md +0 -107
  72. package/docs/plans/0193-runtime-owns-context-queries.md +0 -245
  73. package/docs/plans/0194-align-tool-interfaces-for-structural-typing.md +0 -139
  74. package/docs/plans/0195-convert-tool-factories-to-classes.md +0 -264
  75. package/docs/plans/0196-convert-runner-menu-to-classes.md +0 -268
  76. package/docs/plans/0205-decompose-render-widget-lines.md +0 -140
  77. package/docs/plans/0206-decompose-show-agent-detail.md +0 -211
  78. package/docs/plans/0207-decompose-agent-widget-update.md +0 -242
  79. package/docs/plans/0208-extract-shared-test-fixtures.md +0 -298
  80. package/docs/plans/0214-convert-remaining-closure-factories-to-classes.md +0 -261
  81. package/docs/plans/0215-decompose-build-parent-context.md +0 -166
  82. package/docs/plans/0216-decompose-start-agent.md +0 -255
  83. package/docs/plans/0217-extract-overwrite-guard.md +0 -176
  84. package/docs/plans/0218-push-sdk-boundary-in-settings.md +0 -172
  85. package/docs/plans/0219-reduce-test-duplication-top-3-clone-families.md +0 -162
  86. package/docs/plans/0227-evolve-agent-record-into-agent.md +0 -322
  87. package/docs/plans/0228-async-start-agent-dissolve-run-handle.md +0 -288
  88. package/docs/plans/0229-agent-born-complete.md +0 -564
  89. package/docs/plans/0230-extract-concurrency-queue.md +0 -265
  90. package/docs/plans/0231-push-exec-registry-to-runner.md +0 -245
  91. package/docs/plans/0232-agent-resume-internal-observer-lifecycle.md +0 -180
  92. package/docs/plans/0237-remove-disallowed-tools.md +0 -180
  93. package/docs/plans/0238-remove-extensions-filtering.md +0 -191
  94. package/docs/plans/0239-collapse-filter-active-tools.md +0 -217
  95. package/docs/plans/0242-rename-agent-tool-to-subagent.md +0 -165
  96. package/docs/plans/0256-extract-worktree-isolation.md +0 -256
  97. package/docs/plans/0257-extract-child-session-factory.md +0 -285
  98. package/docs/plans/0262-add-workspace-provider-seam.md +0 -264
  99. package/docs/plans/0264-remove-extension-lifecycle-control.md +0 -277
  100. package/docs/plans/0265-born-complete-subagent-session.md +0 -333
  101. package/docs/plans/0270-type-consumable-public-surface.md +0 -204
  102. package/docs/plans/0272-export-workspace-collaborator-types.md +0 -147
  103. package/docs/plans/0277-encapsulate-agent-session.md +0 -304
  104. package/docs/plans/0280-rename-agent-to-subagent.md +0 -197
  105. package/docs/plans/0360-custom-agents-default-append.md +0 -132
  106. package/docs/plans/0373-extract-subagent-state.md +0 -250
  107. package/docs/plans/0374-encapsulate-subagent-start-notification.md +0 -268
  108. package/docs/plans/0375-extract-run-listener-workspace-bracket.md +0 -300
  109. package/docs/plans/0376-extract-manager-observer.md +0 -232
  110. package/docs/plans/0377-split-widget-delegation-from-runtime.md +0 -254
  111. package/docs/plans/0378-consolidate-lifecycle-test-fixtures.md +0 -243
  112. package/docs/plans/0379-consolidate-ui-tools-test-fixtures.md +0 -335
  113. package/docs/plans/0380-extract-layered-settings-loader.md +0 -223
  114. package/docs/plans/0381-replace-concurrency-queue-with-limiter.md +0 -267
  115. package/docs/plans/0400-include-parent-prompt-in-replace-mode.md +0 -199
  116. package/docs/plans/0403-abort-subagents-on-interrupt.md +0 -180
  117. package/docs/plans/0412-unify-session-mock-builders.md +0 -146
  118. package/docs/plans/0420-fold-run-metrics-live-activity-onto-record.md +0 -228
  119. package/docs/plans/0421-migrate-activity-readers-to-record-getters.md +0 -309
  120. package/docs/plans/0422-delete-activity-tracker-ui-observer.md +0 -204
  121. package/docs/plans/0423-widget-self-drive-from-lifecycle.md +0 -264
  122. package/docs/plans/0424-drop-widget-dep-from-subagent-tool.md +0 -166
  123. package/docs/plans/0425-reconcile-subagent-events-contract.md +0 -144
  124. package/docs/plans/0426-consolidate-residual-test-clone-families.md +0 -235
  125. package/docs/plans/0427-reconsider-ui-direction.md +0 -212
  126. package/docs/plans/0441-remove-agent-definition-management-subtree.md +0 -180
  127. package/docs/plans/0442-dissolve-agents-conversation-viewer.md +0 -195
  128. package/docs/plans/0443-consolidate-remaining-test-clone-families.md +0 -268
  129. package/docs/plans/0444-shrink-widget-to-background-agents.md +0 -156
  130. package/docs/plans/0445-native-session-navigation.md +0 -365
  131. package/docs/plans/0446-spike-session-navigation-entry-criteria.md +0 -171
  132. package/docs/plans/0447-extract-subagents-settings-command.md +0 -220
  133. package/docs/plans/0448-block-disabled-agent-spawn.md +0 -207
  134. package/docs/plans/0462-navigation-renderer-tui-components.md +0 -338
  135. package/docs/plans/0463-file-snapshot-source-evicted-agents.md +0 -306
  136. package/docs/plans/0470-refresh-readme-command-surface.md +0 -159
  137. package/docs/retro/0048-implement-subagents-api.md +0 -44
  138. package/docs/retro/0049-remove-group-join-output-file-rpc.md +0 -49
  139. package/docs/retro/0051-update-adr-0001-hard-fork.md +0 -39
  140. package/docs/retro/0053-extract-model-resolution-from-execute.md +0 -40
  141. package/docs/retro/0054-decompose-index-into-modules.md +0 -53
  142. package/docs/retro/0057-structured-debug-logging.md +0 -77
  143. package/docs/retro/0061-session-format-transcript.md +0 -41
  144. package/docs/retro/0066-replace-as-any-with-sdk-types.md +0 -44
  145. package/docs/retro/0069-create-subagent-runtime.md +0 -43
  146. package/docs/retro/0070-extract-event-handlers.md +0 -49
  147. package/docs/retro/0071-extract-session-config-assembler.md +0 -60
  148. package/docs/retro/0072-inject-agent-manager-collaborators.md +0 -46
  149. package/docs/retro/0076-inject-cwd-into-agent-manager.md +0 -35
  150. package/docs/retro/0077-inject-project-agents-dir.md +0 -33
  151. package/docs/retro/0080-consolidate-agent-config-lookup.md +0 -35
  152. package/docs/retro/0084-extract-git-worktree-manager.md +0 -37
  153. package/docs/retro/0087-evolve-subagent-runtime-methods.md +0 -42
  154. package/docs/retro/0098-extract-agent-record-state-machine.md +0 -46
  155. package/docs/retro/0099-replace-ctx-with-parent-snapshot.md +0 -37
  156. package/docs/retro/0100-replace-callback-threading-with-session-subscription.md +0 -36
  157. package/docs/retro/0102-consolidate-test-record-factory.md +0 -30
  158. package/docs/retro/0108-extract-agent-type-registry.md +0 -41
  159. package/docs/retro/0109-extract-settings-manager.md +0 -55
  160. package/docs/retro/0110-agent-activity-tracker.md +0 -44
  161. package/docs/retro/0111-split-agent-record-lifecycle.md +0 -61
  162. package/docs/retro/0112-replace-agent-manager-callbacks.md +0 -35
  163. package/docs/retro/0113-disambiguate-spawn-options.md +0 -29
  164. package/docs/retro/0114-narrow-agent-tool-menu-deps.md +0 -38
  165. package/docs/retro/0115-decompose-agent-tool.md +0 -51
  166. package/docs/retro/0116-type-housekeeping.md +0 -42
  167. package/docs/retro/0118-settings-manager-apply-methods.md +0 -40
  168. package/docs/retro/0123-remove-vi-fn-cast-smell.md +0 -49
  169. package/docs/retro/0131-consolidate-shared-test-fixtures.md +0 -46
  170. package/docs/retro/0132-inject-io-into-session-config.md +0 -33
  171. package/docs/retro/0133-inject-sdk-boundary-into-agent-runner.md +0 -45
  172. package/docs/retro/0134-reduce-as-any-casts.md +0 -56
  173. package/docs/retro/0135-extract-display-helpers.md +0 -38
  174. package/docs/retro/0136-decompose-agent-menu.md +0 -43
  175. package/docs/retro/0144-consolidate-observation-model.md +0 -39
  176. package/docs/retro/0145-decompose-execute-push-ctx-to-boundary.md +0 -56
  177. package/docs/retro/0146-narrow-ui-context.md +0 -70
  178. package/docs/retro/0147-inject-wrap-text-into-conversation-viewer.md +0 -90
  179. package/docs/retro/0148-split-agent-widget-rendering.md +0 -39
  180. package/docs/retro/0152-add-prompt-snippet.md +0 -34
  181. package/docs/retro/0164-reorganize-into-domain-directories.md +0 -82
  182. package/docs/retro/0165-decompose-resolved-spawn-config.md +0 -78
  183. package/docs/retro/0166-extract-parent-session-info.md +0 -66
  184. package/docs/retro/0167-narrow-runner-io.md +0 -63
  185. package/docs/retro/0168-extract-tool-filter-config.md +0 -76
  186. package/docs/retro/0169-extract-run-context-from-run-options.md +0 -68
  187. package/docs/retro/0170-reduce-build-content-lines-complexity.md +0 -72
  188. package/docs/retro/0171-reduce-render-result-complexity.md +0 -77
  189. package/docs/retro/0172-extract-turn-formatting.md +0 -80
  190. package/docs/retro/0180-reorder-append-prompt-for-kv-cache.md +0 -62
  191. package/docs/retro/0185-remove-persistent-agent-memory.md +0 -73
  192. package/docs/retro/0188-replace-any-casts-with-sdk-types.md +0 -69
  193. package/docs/retro/0192-define-session-context-interface.md +0 -59
  194. package/docs/retro/0193-runtime-owns-context-queries.md +0 -75
  195. package/docs/retro/0194-align-tool-interfaces-for-structural-typing.md +0 -62
  196. package/docs/retro/0195-convert-tool-factories-to-classes.md +0 -84
  197. package/docs/retro/0196-convert-runner-menu-to-classes.md +0 -73
  198. package/docs/retro/0205-decompose-render-widget-lines.md +0 -64
  199. package/docs/retro/0206-decompose-show-agent-detail.md +0 -67
  200. package/docs/retro/0207-decompose-agent-widget-update.md +0 -92
  201. package/docs/retro/0208-extract-shared-test-fixtures.md +0 -102
  202. package/docs/retro/0214-convert-remaining-closure-factories-to-classes.md +0 -66
  203. package/docs/retro/0215-decompose-build-parent-context.md +0 -61
  204. package/docs/retro/0216-decompose-start-agent.md +0 -80
  205. package/docs/retro/0217-extract-overwrite-guard.md +0 -63
  206. package/docs/retro/0218-push-sdk-boundary-in-settings.md +0 -72
  207. package/docs/retro/0219-reduce-test-duplication-top-3-clone-families.md +0 -64
  208. package/docs/retro/0227-evolve-agent-record-into-agent.md +0 -80
  209. package/docs/retro/0228-async-start-agent-dissolve-run-handle.md +0 -80
  210. package/docs/retro/0229-agent-born-complete.md +0 -89
  211. package/docs/retro/0230-extract-concurrency-queue.md +0 -38
  212. package/docs/retro/0231-push-exec-registry-to-runner.md +0 -71
  213. package/docs/retro/0232-agent-resume-internal-observer-lifecycle.md +0 -109
  214. package/docs/retro/0237-remove-disallowed-tools.md +0 -78
  215. package/docs/retro/0238-remove-extensions-filtering.md +0 -72
  216. package/docs/retro/0239-collapse-filter-active-tools.md +0 -70
  217. package/docs/retro/0242-rename-agent-tool-to-subagent.md +0 -82
  218. package/docs/retro/0256-extract-worktree-isolation.md +0 -89
  219. package/docs/retro/0257-extract-child-session-factory.md +0 -33
  220. package/docs/retro/0262-add-workspace-provider-seam.md +0 -89
  221. package/docs/retro/0264-remove-extension-lifecycle-control.md +0 -91
  222. package/docs/retro/0265-born-complete-subagent-session.md +0 -97
  223. package/docs/retro/0270-type-consumable-public-surface.md +0 -108
  224. package/docs/retro/0272-export-workspace-collaborator-types.md +0 -38
  225. package/docs/retro/0277-encapsulate-agent-session.md +0 -80
  226. package/docs/retro/0280-rename-agent-to-subagent.md +0 -96
  227. package/docs/retro/0360-custom-agents-default-append.md +0 -81
  228. package/docs/retro/0373-extract-subagent-state.md +0 -94
  229. package/docs/retro/0374-encapsulate-subagent-start-notification.md +0 -171
  230. package/docs/retro/0375-extract-run-listener-workspace-bracket.md +0 -104
  231. package/docs/retro/0376-extract-manager-observer.md +0 -81
  232. package/docs/retro/0377-split-widget-delegation-from-runtime.md +0 -105
  233. package/docs/retro/0378-consolidate-lifecycle-test-fixtures.md +0 -100
  234. package/docs/retro/0379-consolidate-ui-tools-test-fixtures.md +0 -52
  235. package/docs/retro/0380-extract-layered-settings-loader.md +0 -97
  236. package/docs/retro/0381-replace-concurrency-queue-with-limiter.md +0 -95
  237. package/docs/retro/0400-include-parent-prompt-in-replace-mode.md +0 -84
  238. package/docs/retro/0403-abort-subagents-on-interrupt.md +0 -90
  239. package/docs/retro/0412-unify-session-mock-builders.md +0 -80
  240. package/docs/retro/0420-fold-run-metrics-live-activity-onto-record.md +0 -90
  241. package/docs/retro/0421-migrate-activity-readers-to-record-getters.md +0 -105
  242. package/docs/retro/0422-delete-activity-tracker-ui-observer.md +0 -97
  243. package/docs/retro/0423-widget-self-drive-from-lifecycle.md +0 -112
  244. package/docs/retro/0424-drop-widget-dep-from-subagent-tool.md +0 -89
  245. package/docs/retro/0425-reconcile-subagent-events-contract.md +0 -101
  246. package/docs/retro/0426-consolidate-residual-test-clone-families.md +0 -100
  247. package/docs/retro/0427-reconsider-ui-direction.md +0 -102
  248. package/docs/retro/0441-remove-agent-definition-management-subtree.md +0 -97
  249. package/docs/retro/0442-dissolve-agents-conversation-viewer.md +0 -108
  250. package/docs/retro/0443-consolidate-remaining-test-clone-families.md +0 -28
  251. package/docs/retro/0444-shrink-widget-to-background-agents.md +0 -81
  252. package/docs/retro/0445-native-session-navigation.md +0 -122
  253. package/docs/retro/0446-spike-session-navigation-entry-criteria.md +0 -99
  254. package/docs/retro/0447-extract-subagents-settings-command.md +0 -91
  255. package/docs/retro/0448-block-disabled-agent-spawn.md +0 -75
  256. package/docs/retro/0462-navigation-renderer-tui-components.md +0 -105
  257. package/docs/retro/0463-file-snapshot-source-evicted-agents.md +0 -103
  258. package/docs/retro/0470-refresh-readme-command-surface.md +0 -40
  259. package/src/observation/notification-state.ts +0 -27
  260. package/vitest.config.ts +0 -14
@@ -0,0 +1,282 @@
1
+ # Phase 19: Implement the ADR-0004 UI decisions
2
+
3
+ ## Summary
4
+
5
+ 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.
6
+
7
+ The sequencing follows Kent Beck's "make the change easy, then make the easy change."
8
+ The end state deletes `agent-menu.ts` — the god-command that bundles four unrelated jobs — and everything reachable only from it.
9
+ Rather than surgically mutate that doomed module (and the #1 churn hotspot `index.ts`) once per option, Phase 19 first stood up the replacement surfaces additively, then removed the now-orphaned subtree in a single terminal cut.
10
+ This kept every responsibility's old surface live until its replacement existed (ADR-0004's no-interim-regression invariant), turned the three replacement steps into genuinely parallel work (none touched `agent-menu.ts`), and reduced `index.ts` edits from four surgical removals to one deregistration.
11
+
12
+ Seven numbered steps in three phases, plus two follow-ups (Steps 4a–4b) carved from the #445 slice:
13
+
14
+ - **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).
15
+ - **Phase B — dissolve `/agents` (terminal cut):** delete the orphaned subtree in two deletion commits, one per subtree (Steps 5–6).
16
+ - **Phase C — test health:** consolidate the test clones that survive the cut (Step 7).
17
+
18
+ All nine steps are closed: [#446], [#447], [#444], [#445], [#462], [#463], [#442], [#441], [#443].
19
+ A follow-on issue, [#470] (README staleness — the terminal cut removed `/agents` but left the README describing it), was filed after Steps 5–6 shipped and closed independently; see "Follow-on issues" below.
20
+
21
+ ## Health metrics
22
+
23
+ | Metric | Phase 18 (start) | Phase 19 target | Phase 19 (delivered) |
24
+ | ---------------------- | ------------------------ | -------------------- | ------------------------------------ |
25
+ | Health score | 78/100 (B) | 83/100 (B+) | 78/100 (B) — unchanged |
26
+ | Source LOC | 7,650 (61 files) | ~6,780 (~55 files) | 7,068 (57 files) |
27
+ | Production duplication | 11 lines (1 group) | 0 lines | 0 lines ✅ |
28
+ | Test clone groups | 16 | ≤ 10 | 9 ✅ |
29
+ | Top churn hotspot | `index.ts` (103 commits) | `index.ts` (cooling) | `index.ts` (109 commits, cooling) ✅ |
30
+
31
+ The health score held flat at 78/100 rather than reaching the 83/100 target — the score's `hotspots` and `unit size` deductions are dominated by long-lived test-suite characteristics (large test functions, `index.ts`'s cumulative churn history) that this phase's scope did not target.
32
+ Every metric the phase's steps directly controlled — production duplication, test clone groups, and the churn trend on `index.ts` — hit or beat its target.
33
+
34
+ ## Steps
35
+
36
+ ### Step 1 — Spike: resolve ADR-0004 entry criteria ([#446])
37
+
38
+ Smell: Category C (coupling boundary) — four open decisions block the session-navigation implementation.
39
+ Target: `docs/decisions/0004-reconsider-ui-direction.md` addendum.
40
+
41
+ The four entry criteria from ADR-0004:
42
+
43
+ 1. **Root-continuity:** Does the root's in-flight turn survive `ctx.switchSession()` and a return gesture?
44
+ 2. **View-only vs interactive:** `switchSession` (full interactive takeover) or `loadEntriesFromFile` (read-only transcript built from JSONL)?
45
+ 3. **Parallel-agent navigation:** Operator gesture to select which of N background agents to view (from the widget, a command, or both).
46
+ 4. **Settings command name:** `/subagents-settings`, `/agents-settings`, or another form consistent with sibling packages?
47
+
48
+ 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.
49
+ No production source files change; the spike closes when the ADR addendum is merged.
50
+
51
+ Outcome: ADR-0004 updated with all four entry-criteria answers; Step 4 unblocked.
52
+
53
+ `Release: independent`
54
+
55
+ ### Step 2 — Extract settings to a focused `/subagents-settings` command ([#447])
56
+
57
+ 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`.
58
+ Target files:
59
+
60
+ - 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).
61
+ - `src/index.ts` — register the new command (name confirmed by Step 1); pass `settings` directly.
62
+ - New `test/ui/subagents-settings.test.ts` — unit tests for the extracted handler.
63
+
64
+ `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.
65
+ 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.
66
+
67
+ Outcome: new `subagents-settings.ts` (~80 LOC) and focused command registered; `agent-menu.ts` untouched.
68
+
69
+ `Release: independent`
70
+
71
+ ### Step 3 — Shrink widget to background agents only ([#444])
72
+
73
+ Smell: Category C (coupling) — the widget shows all agents including foreground ones, duplicating the `subagent` tool's inline `onUpdate` stream for foreground runs.
74
+ Target files:
75
+
76
+ - `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`.
77
+ - `src/ui/widget-renderer.ts` — verify no foreground-specific rendering path survives.
78
+ - `test/ui/agent-widget.test.ts` — add background-only filtering tests; update assertions.
79
+
80
+ The widget calls `listAgents()` at two sites today — `update()` (feeding `seedFinishedAgents`, `assembleWidgetState`, and `clearWidget`) and `renderWidget()` (the tree map).
81
+ 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.
82
+ `Subagent.invocation.runInBackground` is the reliable signal: set by `spawn-config.ts` → `AgentInvocation.runInBackground` → stored on `Subagent.invocation`.
83
+ ADR-0004 Decision A: foreground runs suppress the widget; the inline `onUpdate` stream is authoritative there.
84
+
85
+ Outcome: widget shows only background agents; foreground/widget duplication eliminated; the background predicate lives at a single funnel.
86
+
87
+ `Release: independent`
88
+
89
+ ### Step 4 — Implement native session navigation ([#445])
90
+
91
+ Smell: Category C (coupling) — the bespoke `ConversationViewer` re-implements session-transcript rendering when Pi's own machinery targets the already-persisted child session JSONL.
92
+ This step adds the new surface alongside the existing viewer; it does not touch `agent-menu.ts`.
93
+ Target files:
94
+
95
+ - 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.
96
+ - New typed accessor on `Subagent`/`SubagentSession` returning `record.messages` as `AgentMessage[]` (the boundary currently widens it to `readonly unknown[]`).
97
+ - `src/index.ts` — register the new command; the background widget ([#444]) is an optional secondary selection gesture, not a dependency.
98
+
99
+ ADR-0004 Decision B: "Tell-Don't-Ask — hand Pi the session path; Pi owns the viewer."
100
+ 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).
101
+
102
+ - **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.
103
+ - **Evicted / untracked agent** — render from the file snapshot: `parseSessionEntries(readFileSync(record.outputFile, "utf8"))` → drop the `SessionHeader` → `buildSessionContext(...).messages`.
104
+
105
+ 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).
106
+ 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.
107
+ `Subagent.outputFile` already exposes the persisted child session JSONL path via `subagentSession?.outputFile` — no new SDK dependency.
108
+ 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).
109
+
110
+ 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.
111
+
112
+ 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`).
113
+ It is **live-source only** behind a renderer-agnostic `TranscriptSource` seam, rendered via Pi's `serializeConversation` text.
114
+ 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.
115
+ 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).
116
+
117
+ `Release: independent` (spike-gated)
118
+
119
+ ### Step 4a — Upgrade native-navigation renderer to Pi TUI components ([#462])
120
+
121
+ 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.
122
+ Until the native renderer reaches parity, the terminal cut (Step 5) cannot delete the bespoke viewer without a fidelity regression.
123
+ 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.
124
+
125
+ 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.
126
+
127
+ Outcome: native session navigation renders at parity with the removed `ConversationViewer`; Step 5 can delete the bespoke viewer with no fidelity regression.
128
+
129
+ 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.
130
+ The `TranscriptOverlay` caches that `Container` and rebuilds it on source change only (Pi's `rebuildChatFromMessages` path), keeping the lightweight `◍` streaming indicator.
131
+ 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.
132
+ 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.
133
+ `custom`-role messages are skipped (the bespoke viewer never rendered them either).
134
+ Selection and sourcing are untouched; native navigation now renders at parity, unblocking Step 5 for rendering fidelity.
135
+
136
+ `Release: independent`
137
+
138
+ ### Step 4b — File-snapshot source for evicted agents ([#463])
139
+
140
+ 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.
141
+ 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.
142
+
143
+ Independent: this is a new capability the bespoke viewer never had, so it gates nothing and is not a Step 5 prerequisite.
144
+ Best sequenced after Step 4a (shared renderer), but carries no hard dependency.
145
+
146
+ 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.
147
+
148
+ 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).
149
+ 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.
150
+ 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()`.
151
+ `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`.
152
+ Evicted entries carry an `· evicted (snapshot)` label marker.
153
+ 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.
154
+
155
+ `Release: independent`
156
+
157
+ ### Step 5 — Dissolve `/agents` and remove the conversation-viewer subtree ([#442])
158
+
159
+ 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.
160
+ This is the first of two deletion commits (split by subtree).
161
+ 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.
162
+ Target files:
163
+
164
+ - `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).
165
+ - Delete `src/ui/agent-menu.ts` (331 LOC) and `test/ui/agent-menu.test.ts` (185 LOC).
166
+ - 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.
167
+ - 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`.
168
+
169
+ 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).
170
+ Deleting the hub in one move avoids any surgical edit to the doomed file and leaves the definition-management leaves orphaned for Step 6.
171
+
172
+ 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.
173
+ Without the prep commit, deleting either subtree first would have left the other half referencing a deleted module.
174
+ `index.ts` also shed the now-dead `join` (node:path) and `buildParentSnapshot` imports in addition to the two module imports.
175
+ The `menu-ui.ts` module is transient and is removed with the wizard/editor in Step 6.
176
+
177
+ Outcome: ✅ `/agents` dissolved; −767 LOC source (menu hub + viewer + formatters); −812 LOC test; largest test function eliminated; `index.ts` dewired.
178
+
179
+ `Release: batch "dissolve-agents"`
180
+
181
+ ### Step 6 — Remove the orphaned agent-definition management subtree ([#441])
182
+
183
+ 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.
184
+ This is the second deletion commit (split by subtree).
185
+ Target files:
186
+
187
+ - Delete `src/ui/agent-creation-wizard.ts` (233 LOC) and `test/ui/agent-creation-wizard.test.ts` (296 LOC).
188
+ - 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.
189
+ - Delete `src/ui/agent-file-ops.ts` (59 LOC) and `test/ui/agent-file-ops.test.ts` (112 LOC) — only consumers were wizard + editor.
190
+ - Delete `src/ui/agent-file-writer.ts` (55 LOC) and `test/ui/agent-file-writer.test.ts` (148 LOC) — only consumers were wizard + editor.
191
+ - `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.
192
+
193
+ 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.
194
+ 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.
195
+
196
+ 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.
197
+
198
+ 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.
199
+ `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.
200
+ Production duplication: 0 lines (confirmed by `fallow dupes`); `fallow dead-code`: clean. 16 test clone groups remain — Step 7 consolidation target.
201
+
202
+ `Release: batch "dissolve-agents"`
203
+
204
+ ### Step 7 — Consolidate remaining test clone families ([#443])
205
+
206
+ 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.
207
+ Run after the cut so no helper is extracted into a file the cut then deletes.
208
+ Target files:
209
+
210
+ - `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`).
211
+ - `test/ui/agent-widget.test.ts` — merge the duplicate `makeWidget` helper defined twice across two `describe` blocks (14-line clone at :225/:284).
212
+ - `test/session/session-config.test.ts` — extract a shared fixture for the 16-line internal clone (lines 131–146 / 151–166).
213
+ - `test/lifecycle/concurrency-limiter.test.ts` — extract shared setup for the 10-line clone (lines 21–30 / 148–155).
214
+ - `test/tools/spawn-config.test.ts` — extract a shared fixture for the 9-line clone (lines 22–30 / 35–43).
215
+
216
+ Outcome (landed): test clone groups reduced from 16 to 9 (≤ 10 target met).
217
+ Eight genuine arrange/fixture/helper families were extracted: `makeNavigable` (shared `test/helpers/make-navigable.ts`), `emitResumeUsageAndCompaction` (shared `test/helpers/mock-session.ts`), and local helpers for `makeWidget`, the captured-overlay render (`renderCapturedOverlay`), the `resultConsumed` observer (`seedResultConsumedObserver`), the ready subagent (`makeReadySubagent`), and the prepared bracket (`preparedBracket`).
218
+ The nine residual families are the repeated system-under-test call (`resolveSpawnConfig`, `assembleSessionConfig`, `schedule`, `SessionNavigatorHandler.handle`, `spawnBg`+`await`, `agent.run()`, `execute`), left intact per the testing guardrail — the repeated act is the test subject, not duplication to remove.
219
+ One family the plan pre-classified as captured-overlay boilerplate (`dup:ea0a1bce`) proved to be an act-clone once the boilerplate was extracted, so it joins the residual set rather than being wrapped.
220
+
221
+ `Release: independent`
222
+
223
+ ## Step dependency diagram
224
+
225
+ ```mermaid
226
+ flowchart LR
227
+ S1["✅ Step 1 - Spike (#446)"]
228
+ S2["✅ Step 2 - Settings command (#447)"]
229
+ S3["✅ Step 3 - Background widget (#444)"]
230
+ S4["✅ Step 4 - Native session nav slice (#445)"]
231
+ S4a["✅ Step 4a - Renderer to TUI components (#462)"]
232
+ S4b["✅ Step 4b - File-snapshot source (#463)"]
233
+ S5["✅ Step 5 - Dissolve /agents + viewer (#442)"]
234
+ S6["✅ Step 6 - Remove definition mgmt (#441)"]
235
+ S7["✅ Step 7 - Test clones (#443)"]
236
+
237
+ S1 --> S4
238
+ S4 --> S4a
239
+ S4 --> S4b
240
+ S2 --> S5
241
+ S3 --> S5
242
+ S4a --> S5
243
+ S5 --> S6
244
+ S6 --> S7
245
+ ```
246
+
247
+ 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.
248
+ Step 4b (file-snapshot source) is a new capability and gates nothing.
249
+ The old `S1 → S6 → S7` chain hid the widget dependency; this diagram makes it explicit.
250
+
251
+ ## Parallel tracks
252
+
253
+ - **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.
254
+ 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`.
255
+ Steps 2 and 4 each append a command registration to `index.ts` (additive, low-conflict).
256
+ 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.
257
+ - **Track B — Dissolution (Steps 5 → 6):** the terminal cut, gated on all of Track A landing.
258
+ 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.
259
+ - **Track C — Test health (Step 7):** clone consolidation, run after the cut so no surviving helper is extracted into a doomed file.
260
+
261
+ ## Release batches
262
+
263
+ - **Batch "dissolve-agents":** Steps 5, 6 (ship together; tail = Step 6).
264
+ Depends on Steps 2, 3, 4 already merged.
265
+ - Independently releasable: Steps 1, 2, 3, 4, 4a, 4b, 7.
266
+
267
+ ## Follow-on issues
268
+
269
+ - [#470] — the terminal cut (Steps 5–6) removed `/agents`, the conversation viewer, and the creation wizard/config editor, but `README.md` was never updated and continued documenting the removed surface through the `pi-subagents-v18.0.0` release.
270
+ Filed and closed independently after the phase's steps landed; the README now documents `/subagents:settings`, `/subagents:sessions`, and the background widget.
271
+
272
+ [ADR-0004]: ../../decisions/0004-reconsider-ui-direction.md
273
+ [#441]: https://github.com/gotgenes/pi-packages/issues/441
274
+ [#442]: https://github.com/gotgenes/pi-packages/issues/442
275
+ [#443]: https://github.com/gotgenes/pi-packages/issues/443
276
+ [#444]: https://github.com/gotgenes/pi-packages/issues/444
277
+ [#445]: https://github.com/gotgenes/pi-packages/issues/445
278
+ [#446]: https://github.com/gotgenes/pi-packages/issues/446
279
+ [#447]: https://github.com/gotgenes/pi-packages/issues/447
280
+ [#462]: https://github.com/gotgenes/pi-packages/issues/462
281
+ [#463]: https://github.com/gotgenes/pi-packages/issues/463
282
+ [#470]: https://github.com/gotgenes/pi-packages/issues/470
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-subagents",
3
- "version": "18.0.1",
3
+ "version": "18.0.2",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -15,11 +15,10 @@
15
15
  "files": [
16
16
  "src",
17
17
  "dist",
18
- "docs",
19
- "vitest.config.ts",
20
- "AGENTS.md",
21
- "CHANGELOG.md",
22
- ".prettierignore"
18
+ "docs/*.md",
19
+ "docs/architecture",
20
+ "docs/decisions",
21
+ "CHANGELOG.md"
23
22
  ],
24
23
  "imports": {
25
24
  "#src/*": "./src/*",
@@ -17,7 +17,6 @@ import { SubagentState, type SubagentStatus } from "#src/lifecycle/subagent-stat
17
17
  import type { LifetimeUsage } from "#src/lifecycle/usage";
18
18
  import type { WorkspaceProvider } from "#src/lifecycle/workspace";
19
19
  import { WorkspaceBracket } from "#src/lifecycle/workspace-bracket";
20
- import { NotificationState } from "#src/observation/notification-state";
21
20
  import { subscribeSubagentObserver } from "#src/observation/record-observer";
22
21
  import type { RunConfig } from "#src/runtime";
23
22
  import type { AgentInvocation, CompactionInfo, ParentSessionInfo, SessionMessage, SubagentType, ThinkingLevel } from "#src/types";
@@ -108,8 +107,6 @@ export class Subagent {
108
107
  private readonly workspaceBracket: WorkspaceBracket;
109
108
 
110
109
  subagentSession?: SubagentSession;
111
- private _notification?: NotificationState;
112
- get notification(): NotificationState | undefined { return this._notification; }
113
110
 
114
111
  // Steer buffer — messages queued before the session is ready
115
112
  private _pendingSteers: string[] = [];
@@ -121,6 +118,11 @@ export class Subagent {
121
118
  return this.subagentSession?.outputFile;
122
119
  }
123
120
 
121
+ /** The tool call ID that spawned this background agent, if any. */
122
+ get toolCallId(): string | undefined {
123
+ return this.execution.parentSession?.toolCallId;
124
+ }
125
+
124
126
  /** Returns true when a SubagentSession is available (session is ready). */
125
127
  isSessionReady(): boolean {
126
128
  return this.subagentSession != null;
@@ -192,12 +194,6 @@ export class Subagent {
192
194
  this.workspaceBracket = new WorkspaceBracket(
193
195
  this.execution.getWorkspaceProvider ?? (() => undefined),
194
196
  );
195
-
196
- // Notification state — created from parentSession.toolCallId if present
197
- const toolCallId = init.execution.parentSession?.toolCallId;
198
- if (toolCallId) {
199
- this._notification = new NotificationState(toolCallId);
200
- }
201
197
  }
202
198
 
203
199
  /**
@@ -55,7 +55,7 @@ export function formatTaskNotification(record: Subagent, resultMaxLen: number):
55
55
  : record.result
56
56
  : "No output.";
57
57
 
58
- const toolCallId = record.notification?.toolCallId;
58
+ const toolCallId = record.toolCallId;
59
59
  const outputFile = record.outputFile;
60
60
  return [
61
61
  "<task-notification>",
@@ -123,15 +123,21 @@ export function buildEventData(record: Subagent) {
123
123
  // ---- Notification system factory ----
124
124
 
125
125
  export interface NotificationSystem {
126
- cancelNudge: (key: string) => void;
127
126
  sendCompletion: (record: Subagent) => void;
128
127
  dispose: () => void;
129
128
  }
130
129
 
130
+ /** Delivery-consumption operation: get-result-tool's dependency on NotificationManager. */
131
+ export interface ResultDelivery {
132
+ /** Record the parent consumed this agent's result: suppress its completion nudge. */
133
+ consume: (id: string) => void;
134
+ }
135
+
131
136
  const NUDGE_HOLD_MS = 200;
132
137
 
133
- export class NotificationManager implements NotificationSystem {
138
+ export class NotificationManager implements NotificationSystem, ResultDelivery {
134
139
  private pendingNudges = new Map<string, ReturnType<typeof setTimeout>>();
140
+ private consumed = new Set<string>();
135
141
 
136
142
  constructor(
137
143
  private sendMessage: (
@@ -140,21 +146,28 @@ export class NotificationManager implements NotificationSystem {
140
146
  ) => void,
141
147
  ) {}
142
148
 
143
- cancelNudge(key: string): void {
144
- const timer = this.pendingNudges.get(key);
145
- if (timer != null) {
146
- clearTimeout(timer);
147
- this.pendingNudges.delete(key);
148
- }
149
+ consume(id: string): void {
150
+ this.consumed.add(id);
151
+ this.cancelNudge(id);
149
152
  }
150
153
 
151
154
  sendCompletion(record: Subagent): void {
155
+ if (this.consumed.has(record.id)) return;
152
156
  this.scheduleNudge(record.id, () => this.emitIndividualNudge(record));
153
157
  }
154
158
 
155
159
  dispose(): void {
156
160
  for (const timer of this.pendingNudges.values()) clearTimeout(timer);
157
161
  this.pendingNudges.clear();
162
+ this.consumed.clear();
163
+ }
164
+
165
+ private cancelNudge(key: string): void {
166
+ const timer = this.pendingNudges.get(key);
167
+ if (timer != null) {
168
+ clearTimeout(timer);
169
+ this.pendingNudges.delete(key);
170
+ }
158
171
  }
159
172
 
160
173
  private scheduleNudge(key: string, send: () => void, delay = NUDGE_HOLD_MS): void {
@@ -173,7 +186,7 @@ export class NotificationManager implements NotificationSystem {
173
186
  }
174
187
 
175
188
  private emitIndividualNudge(record: Subagent): void {
176
- if (record.notification?.resultConsumed) return;
189
+ if (this.consumed.has(record.id)) return;
177
190
 
178
191
  const notification = formatTaskNotification(record, 500);
179
192
  const outputFile = record.outputFile;
@@ -64,9 +64,7 @@ export class SubagentEventsObserver implements SubagentManagerObserver {
64
64
  completedAt: record.completedAt,
65
65
  });
66
66
 
67
- // Skip notification if result was already consumed via get_subagent_result.
68
- if (record.notification?.resultConsumed) return;
69
-
67
+ // The manager decides whether to nudge (it owns the consumed-result state).
70
68
  this.notifications.sendCompletion(record);
71
69
  }
72
70
 
@@ -0,0 +1,60 @@
1
+ /**
2
+ * get-result-report.ts — Pure report assembly for get_subagent_result.
3
+ *
4
+ * All functions are stateless: they receive an AgentReport, returning
5
+ * formatted strings. No SDK types, no timers, no side effects.
6
+ * Consumed by GetResultTool.execute in get-result-tool.ts. Mirrors the
7
+ * result-renderer.ts pattern used by the subagent tool's TUI renderer.
8
+ */
9
+
10
+ import type { SubagentStatus } from "#src/lifecycle/subagent";
11
+
12
+ /** The data a get_subagent_result report renders from — only what the formatter reads. */
13
+ export interface AgentReport {
14
+ id: string;
15
+ displayName: string;
16
+ status: SubagentStatus;
17
+ toolUses: number;
18
+ /** Pre-formatted lifetime token total; "" when zero. */
19
+ tokens: string;
20
+ contextPercent: number | null;
21
+ compactionCount: number;
22
+ /** Pre-formatted duration string. */
23
+ duration: string;
24
+ description: string;
25
+ result: string | undefined;
26
+ error: string | undefined;
27
+ /** Present only when verbose was requested and a conversation is available. */
28
+ conversation?: string;
29
+ }
30
+
31
+ /** Assemble the stats parts: Tool uses / tokens? / Context? / Compactions? / Duration. */
32
+ export function renderStatsParts(report: AgentReport): string[] {
33
+ const parts = [`Tool uses: ${report.toolUses}`];
34
+ if (report.tokens) parts.push(report.tokens);
35
+ if (report.contextPercent !== null) parts.push(`Context: ${Math.round(report.contextPercent)}%`);
36
+ if (report.compactionCount) parts.push(`Compactions: ${report.compactionCount}`);
37
+ parts.push(`Duration: ${report.duration}`);
38
+ return parts;
39
+ }
40
+
41
+ /** Select the per-status body: running note, error line, or trimmed result. */
42
+ export function renderReportBody(report: AgentReport): string {
43
+ if (report.status === "running")
44
+ return "Agent is still running. Use wait: true or check back later.";
45
+ if (report.status === "error") return `Error: ${report.error}`;
46
+ return report.result?.trim() ?? "No output.";
47
+ }
48
+
49
+ /** Assemble the full get_subagent_result report text. */
50
+ export function formatAgentReport(report: AgentReport): string {
51
+ let output =
52
+ `Agent: ${report.id}\n` +
53
+ `Type: ${report.displayName} | Status: ${report.status} | ${renderStatsParts(report).join(" | ")}\n` +
54
+ `Description: ${report.description}\n\n`;
55
+ output += renderReportBody(report);
56
+ if (report.conversation) {
57
+ output += `\n\n--- Agent Conversation ---\n${report.conversation}`;
58
+ }
59
+ return output;
60
+ }
@@ -1,6 +1,7 @@
1
1
  import { defineTool } from "@earendil-works/pi-coding-agent";
2
2
  import { Type } from "@sinclair/typebox";
3
3
  import type { AgentConfigLookup } from "#src/config/agent-types";
4
+ import { type AgentReport, formatAgentReport } from "#src/tools/get-result-report";
4
5
  import { formatLifetimeTokens, textResult } from "#src/tools/helpers";
5
6
  import type { Subagent } from "#src/types";
6
7
  import { formatDuration, getDisplayName } from "#src/ui/display";
@@ -12,7 +13,7 @@ export interface GetResultToolManager {
12
13
  }
13
14
 
14
15
  export interface GetResultToolNotifications {
15
- cancelNudge(key: string): void;
16
+ consume(id: string): void;
16
17
  }
17
18
 
18
19
  // ---- Class ----
@@ -37,53 +38,37 @@ export class GetResultTool {
37
38
  }
38
39
 
39
40
  // Wait for completion if requested.
40
- // Pre-mark resultConsumed BEFORE awaiting: onComplete fires inside .then()
41
- // (attached earlier at spawn time) and always runs before this await resumes.
42
- // Setting the flag here prevents a redundant follow-up notification.
41
+ // Consume BEFORE awaiting: onComplete fires inside .then() (attached
42
+ // earlier at spawn time) and always runs before this await resumes.
43
+ // Consuming here prevents a redundant follow-up notification.
43
44
  if (params.wait && record.status === "running" && record.promise) {
44
- // Pre-mark consumed BEFORE awaiting — onComplete fires inside .then() and
45
- // always runs before this await resumes. Prevents a redundant notification.
46
- record.notification?.markConsumed();
47
- this.notifications.cancelNudge(params.agent_id);
45
+ this.notifications.consume(params.agent_id);
48
46
  await record.promise;
49
47
  }
50
48
 
51
- const displayName = getDisplayName(record.type, this.registry);
52
- const duration = formatDuration(record.startedAt, record.completedAt);
53
- const tokens = formatLifetimeTokens(record);
54
- const contextPercent = record.getContextPercent();
55
- const statsParts = [`Tool uses: ${record.toolUses}`];
56
- if (tokens) statsParts.push(tokens);
57
- if (contextPercent !== null) statsParts.push(`Context: ${Math.round(contextPercent)}%`);
58
- if (record.compactionCount) statsParts.push(`Compactions: ${record.compactionCount}`);
59
- statsParts.push(`Duration: ${duration}`);
60
-
61
- let output =
62
- `Agent: ${record.id}\n` +
63
- `Type: ${displayName} | Status: ${record.status} | ${statsParts.join(" | ")}\n` +
64
- `Description: ${record.description}\n\n`;
65
-
66
- if (record.status === "running") {
67
- output += "Agent is still running. Use wait: true or check back later.";
68
- } else if (record.status === "error") {
69
- output += `Error: ${record.error}`;
70
- } else {
71
- output += record.result?.trim() ?? "No output.";
72
- }
73
-
74
- // Mark result as consumed — suppresses the completion notification
49
+ // Consume the settled result — suppresses the completion notification.
75
50
  if (record.status !== "running" && record.status !== "queued") {
76
- record.notification?.markConsumed();
77
- this.notifications.cancelNudge(params.agent_id);
51
+ this.notifications.consume(params.agent_id);
78
52
  }
79
53
 
80
- // Verbose: include full conversation
81
- const conversation = params.verbose ? record.getConversation() : undefined;
82
- if (conversation) {
83
- output += `\n\n--- Agent Conversation ---\n${conversation}`;
84
- }
54
+ return textResult(formatAgentReport(this.buildReport(record, params.verbose)));
55
+ }
85
56
 
86
- return textResult(output);
57
+ private buildReport(record: Subagent, verbose?: boolean): AgentReport {
58
+ return {
59
+ id: record.id,
60
+ displayName: getDisplayName(record.type, this.registry),
61
+ status: record.status,
62
+ toolUses: record.toolUses,
63
+ tokens: formatLifetimeTokens(record),
64
+ contextPercent: record.getContextPercent(),
65
+ compactionCount: record.compactionCount,
66
+ duration: formatDuration(record.startedAt, record.completedAt),
67
+ description: record.description,
68
+ result: record.result,
69
+ error: record.error,
70
+ conversation: verbose ? record.getConversation() : undefined,
71
+ };
87
72
  }
88
73
 
89
74
  toToolDefinition() {
package/src/types.ts CHANGED
@@ -108,7 +108,7 @@ export interface ParentSessionInfo {
108
108
  parentSessionFile?: string;
109
109
  /** Session ID of the parent agent (stored in the child session's parentSession header). */
110
110
  parentSessionId?: string;
111
- /** Tool call ID for background notification wiring. When set, spawn attaches NotificationState. */
111
+ /** Tool call ID for background notification wiring. Exposed on the record via Subagent.toolCallId. */
112
112
  toolCallId?: string;
113
113
  }
114
114
 
@@ -167,7 +167,6 @@ export class TranscriptOverlay implements Component {
167
167
  });
168
168
  }
169
169
 
170
- // fallow-ignore-next-line unused-class-member
171
170
  handleInput(data: string): void {
172
171
  if (matchesKey(data, "escape") || matchesKey(data, "q")) {
173
172
  this.closed = true;
@@ -244,7 +243,6 @@ export class TranscriptOverlay implements Component {
244
243
  this.content.invalidate();
245
244
  }
246
245
 
247
- // fallow-ignore-next-line unused-class-member
248
246
  dispose(): void {
249
247
  this.closed = true;
250
248
  if (this.unsubscribe) {
package/.prettierignore DELETED
@@ -1,5 +0,0 @@
1
- # This fork does not use Prettier — formatting is handled by Biome
2
- # (see biome.json and the sibling Pi-package conventions). Ignore all
3
- # files to prevent Prettier from being run accidentally (e.g. by an
4
- # agent harness with project-level write-time Prettier formatting).
5
- *
package/AGENTS.md DELETED
@@ -1,7 +0,0 @@
1
- # AGENTS.md
2
-
3
- ⚠️ It looks like the agent was started from a package subdirectory.
4
-
5
- Advise the user to launch Pi from the **repository root** (`pi-packages/`) instead.
6
- The root provides `.pi/settings.json`, `.pi/prompts/`, and the full skill set.
7
- Package-specific context for this package is available via the `package-pi-subagents` skill.