@gotgenes/pi-subagents 18.0.1 → 18.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/docs/architecture/architecture.md +165 -203
  3. package/docs/architecture/history/phase-19-implement-ui-decisions.md +282 -0
  4. package/package.json +6 -7
  5. package/src/config/default-agents.ts +3 -0
  6. package/src/lifecycle/subagent.ts +25 -14
  7. package/src/observation/notification.ts +23 -10
  8. package/src/observation/subagent-events-observer.ts +1 -3
  9. package/src/runtime.ts +2 -1
  10. package/src/service/service-adapter.ts +20 -17
  11. package/src/session/model-resolver.ts +31 -30
  12. package/src/tools/agent-tool.ts +17 -13
  13. package/src/tools/get-result-report.ts +60 -0
  14. package/src/tools/get-result-tool.ts +25 -40
  15. package/src/tools/helpers.ts +21 -5
  16. package/src/tools/spawn-config.ts +5 -5
  17. package/src/tools/steer-tool.ts +37 -27
  18. package/src/types.ts +4 -1
  19. package/src/ui/session-navigator.ts +0 -2
  20. package/.prettierignore +0 -5
  21. package/AGENTS.md +0 -7
  22. package/docs/plans/0048-implement-subagents-api.md +0 -304
  23. package/docs/plans/0049-remove-group-join-output-file-rpc.md +0 -180
  24. package/docs/plans/0051-update-adr-0001-hard-fork.md +0 -77
  25. package/docs/plans/0052-remove-scheduled-subagents.md +0 -133
  26. package/docs/plans/0053-extract-model-resolution-from-execute.md +0 -181
  27. package/docs/plans/0054-decompose-index-into-modules.md +0 -302
  28. package/docs/plans/0057-structured-debug-logging.md +0 -154
  29. package/docs/plans/0061-session-format-transcript.md +0 -284
  30. package/docs/plans/0066-replace-as-any-with-sdk-types.md +0 -215
  31. package/docs/plans/0069-create-subagent-runtime.md +0 -345
  32. package/docs/plans/0070-extract-event-handlers.md +0 -306
  33. package/docs/plans/0071-extract-session-config-assembler.md +0 -362
  34. package/docs/plans/0072-inject-agent-manager-collaborators.md +0 -329
  35. package/docs/plans/0076-inject-cwd-into-agent-manager.md +0 -102
  36. package/docs/plans/0077-inject-project-agents-dir.md +0 -115
  37. package/docs/plans/0080-consolidate-agent-config-lookup.md +0 -247
  38. package/docs/plans/0084-extract-git-worktree-manager.md +0 -142
  39. package/docs/plans/0087-evolve-subagent-runtime-methods.md +0 -240
  40. package/docs/plans/0098-extract-agent-record-state-machine.md +0 -435
  41. package/docs/plans/0099-replace-ctx-with-parent-snapshot.md +0 -488
  42. package/docs/plans/0100-replace-callback-threading-with-session-subscription.md +0 -454
  43. package/docs/plans/0102-consolidate-test-record-factory.md +0 -176
  44. package/docs/plans/0108-extract-agent-type-registry.md +0 -322
  45. package/docs/plans/0109-extract-settings-manager.md +0 -276
  46. package/docs/plans/0110-agent-activity-tracker.md +0 -297
  47. package/docs/plans/0111-split-agent-record-lifecycle.md +0 -582
  48. package/docs/plans/0112-replace-agent-manager-callbacks.md +0 -241
  49. package/docs/plans/0113-disambiguate-spawn-options.md +0 -155
  50. package/docs/plans/0114-narrow-agent-tool-menu-deps.md +0 -279
  51. package/docs/plans/0115-decompose-agent-tool.md +0 -337
  52. package/docs/plans/0116-type-housekeeping.md +0 -351
  53. package/docs/plans/0118-settings-manager-apply-methods.md +0 -271
  54. package/docs/plans/0123-remove-vi-fn-cast-smell.md +0 -179
  55. package/docs/plans/0131-consolidate-shared-test-fixtures.md +0 -207
  56. package/docs/plans/0132-inject-io-into-session-config.md +0 -219
  57. package/docs/plans/0133-inject-sdk-boundary-into-agent-runner.md +0 -373
  58. package/docs/plans/0134-reduce-as-any-casts.md +0 -366
  59. package/docs/plans/0135-extract-display-helpers.md +0 -182
  60. package/docs/plans/0136-decompose-agent-menu.md +0 -300
  61. package/docs/plans/0144-consolidate-observation-model.md +0 -263
  62. package/docs/plans/0145-decompose-execute-push-ctx-to-boundary.md +0 -290
  63. package/docs/plans/0146-narrow-ui-context.md +0 -319
  64. package/docs/plans/0147-inject-wrap-text-into-conversation-viewer.md +0 -166
  65. package/docs/plans/0148-split-agent-widget-rendering.md +0 -255
  66. package/docs/plans/0152-add-prompt-snippet.md +0 -91
  67. package/docs/plans/0164-reorganize-into-domain-directories.md +0 -409
  68. package/docs/plans/0165-decompose-resolved-spawn-config.md +0 -157
  69. package/docs/plans/0166-extract-parent-session-info.md +0 -231
  70. package/docs/plans/0167-narrow-runner-io.md +0 -150
  71. package/docs/plans/0168-extract-tool-filter-config.md +0 -173
  72. package/docs/plans/0169-extract-run-context-from-run-options.md +0 -194
  73. package/docs/plans/0170-reduce-build-content-lines-complexity.md +0 -225
  74. package/docs/plans/0171-reduce-render-result-complexity.md +0 -200
  75. package/docs/plans/0172-extract-turn-formatting.md +0 -206
  76. package/docs/plans/0180-reorder-append-prompt-for-kv-cache.md +0 -100
  77. package/docs/plans/0185-remove-persistent-agent-memory.md +0 -161
  78. package/docs/plans/0188-replace-any-casts-with-sdk-types.md +0 -162
  79. package/docs/plans/0192-define-session-context-interface.md +0 -107
  80. package/docs/plans/0193-runtime-owns-context-queries.md +0 -245
  81. package/docs/plans/0194-align-tool-interfaces-for-structural-typing.md +0 -139
  82. package/docs/plans/0195-convert-tool-factories-to-classes.md +0 -264
  83. package/docs/plans/0196-convert-runner-menu-to-classes.md +0 -268
  84. package/docs/plans/0205-decompose-render-widget-lines.md +0 -140
  85. package/docs/plans/0206-decompose-show-agent-detail.md +0 -211
  86. package/docs/plans/0207-decompose-agent-widget-update.md +0 -242
  87. package/docs/plans/0208-extract-shared-test-fixtures.md +0 -298
  88. package/docs/plans/0214-convert-remaining-closure-factories-to-classes.md +0 -261
  89. package/docs/plans/0215-decompose-build-parent-context.md +0 -166
  90. package/docs/plans/0216-decompose-start-agent.md +0 -255
  91. package/docs/plans/0217-extract-overwrite-guard.md +0 -176
  92. package/docs/plans/0218-push-sdk-boundary-in-settings.md +0 -172
  93. package/docs/plans/0219-reduce-test-duplication-top-3-clone-families.md +0 -162
  94. package/docs/plans/0227-evolve-agent-record-into-agent.md +0 -322
  95. package/docs/plans/0228-async-start-agent-dissolve-run-handle.md +0 -288
  96. package/docs/plans/0229-agent-born-complete.md +0 -564
  97. package/docs/plans/0230-extract-concurrency-queue.md +0 -265
  98. package/docs/plans/0231-push-exec-registry-to-runner.md +0 -245
  99. package/docs/plans/0232-agent-resume-internal-observer-lifecycle.md +0 -180
  100. package/docs/plans/0237-remove-disallowed-tools.md +0 -180
  101. package/docs/plans/0238-remove-extensions-filtering.md +0 -191
  102. package/docs/plans/0239-collapse-filter-active-tools.md +0 -217
  103. package/docs/plans/0242-rename-agent-tool-to-subagent.md +0 -165
  104. package/docs/plans/0256-extract-worktree-isolation.md +0 -256
  105. package/docs/plans/0257-extract-child-session-factory.md +0 -285
  106. package/docs/plans/0262-add-workspace-provider-seam.md +0 -264
  107. package/docs/plans/0264-remove-extension-lifecycle-control.md +0 -277
  108. package/docs/plans/0265-born-complete-subagent-session.md +0 -333
  109. package/docs/plans/0270-type-consumable-public-surface.md +0 -204
  110. package/docs/plans/0272-export-workspace-collaborator-types.md +0 -147
  111. package/docs/plans/0277-encapsulate-agent-session.md +0 -304
  112. package/docs/plans/0280-rename-agent-to-subagent.md +0 -197
  113. package/docs/plans/0360-custom-agents-default-append.md +0 -132
  114. package/docs/plans/0373-extract-subagent-state.md +0 -250
  115. package/docs/plans/0374-encapsulate-subagent-start-notification.md +0 -268
  116. package/docs/plans/0375-extract-run-listener-workspace-bracket.md +0 -300
  117. package/docs/plans/0376-extract-manager-observer.md +0 -232
  118. package/docs/plans/0377-split-widget-delegation-from-runtime.md +0 -254
  119. package/docs/plans/0378-consolidate-lifecycle-test-fixtures.md +0 -243
  120. package/docs/plans/0379-consolidate-ui-tools-test-fixtures.md +0 -335
  121. package/docs/plans/0380-extract-layered-settings-loader.md +0 -223
  122. package/docs/plans/0381-replace-concurrency-queue-with-limiter.md +0 -267
  123. package/docs/plans/0400-include-parent-prompt-in-replace-mode.md +0 -199
  124. package/docs/plans/0403-abort-subagents-on-interrupt.md +0 -180
  125. package/docs/plans/0412-unify-session-mock-builders.md +0 -146
  126. package/docs/plans/0420-fold-run-metrics-live-activity-onto-record.md +0 -228
  127. package/docs/plans/0421-migrate-activity-readers-to-record-getters.md +0 -309
  128. package/docs/plans/0422-delete-activity-tracker-ui-observer.md +0 -204
  129. package/docs/plans/0423-widget-self-drive-from-lifecycle.md +0 -264
  130. package/docs/plans/0424-drop-widget-dep-from-subagent-tool.md +0 -166
  131. package/docs/plans/0425-reconcile-subagent-events-contract.md +0 -144
  132. package/docs/plans/0426-consolidate-residual-test-clone-families.md +0 -235
  133. package/docs/plans/0427-reconsider-ui-direction.md +0 -212
  134. package/docs/plans/0441-remove-agent-definition-management-subtree.md +0 -180
  135. package/docs/plans/0442-dissolve-agents-conversation-viewer.md +0 -195
  136. package/docs/plans/0443-consolidate-remaining-test-clone-families.md +0 -268
  137. package/docs/plans/0444-shrink-widget-to-background-agents.md +0 -156
  138. package/docs/plans/0445-native-session-navigation.md +0 -365
  139. package/docs/plans/0446-spike-session-navigation-entry-criteria.md +0 -171
  140. package/docs/plans/0447-extract-subagents-settings-command.md +0 -220
  141. package/docs/plans/0448-block-disabled-agent-spawn.md +0 -207
  142. package/docs/plans/0462-navigation-renderer-tui-components.md +0 -338
  143. package/docs/plans/0463-file-snapshot-source-evicted-agents.md +0 -306
  144. package/docs/plans/0470-refresh-readme-command-surface.md +0 -159
  145. package/docs/retro/0048-implement-subagents-api.md +0 -44
  146. package/docs/retro/0049-remove-group-join-output-file-rpc.md +0 -49
  147. package/docs/retro/0051-update-adr-0001-hard-fork.md +0 -39
  148. package/docs/retro/0053-extract-model-resolution-from-execute.md +0 -40
  149. package/docs/retro/0054-decompose-index-into-modules.md +0 -53
  150. package/docs/retro/0057-structured-debug-logging.md +0 -77
  151. package/docs/retro/0061-session-format-transcript.md +0 -41
  152. package/docs/retro/0066-replace-as-any-with-sdk-types.md +0 -44
  153. package/docs/retro/0069-create-subagent-runtime.md +0 -43
  154. package/docs/retro/0070-extract-event-handlers.md +0 -49
  155. package/docs/retro/0071-extract-session-config-assembler.md +0 -60
  156. package/docs/retro/0072-inject-agent-manager-collaborators.md +0 -46
  157. package/docs/retro/0076-inject-cwd-into-agent-manager.md +0 -35
  158. package/docs/retro/0077-inject-project-agents-dir.md +0 -33
  159. package/docs/retro/0080-consolidate-agent-config-lookup.md +0 -35
  160. package/docs/retro/0084-extract-git-worktree-manager.md +0 -37
  161. package/docs/retro/0087-evolve-subagent-runtime-methods.md +0 -42
  162. package/docs/retro/0098-extract-agent-record-state-machine.md +0 -46
  163. package/docs/retro/0099-replace-ctx-with-parent-snapshot.md +0 -37
  164. package/docs/retro/0100-replace-callback-threading-with-session-subscription.md +0 -36
  165. package/docs/retro/0102-consolidate-test-record-factory.md +0 -30
  166. package/docs/retro/0108-extract-agent-type-registry.md +0 -41
  167. package/docs/retro/0109-extract-settings-manager.md +0 -55
  168. package/docs/retro/0110-agent-activity-tracker.md +0 -44
  169. package/docs/retro/0111-split-agent-record-lifecycle.md +0 -61
  170. package/docs/retro/0112-replace-agent-manager-callbacks.md +0 -35
  171. package/docs/retro/0113-disambiguate-spawn-options.md +0 -29
  172. package/docs/retro/0114-narrow-agent-tool-menu-deps.md +0 -38
  173. package/docs/retro/0115-decompose-agent-tool.md +0 -51
  174. package/docs/retro/0116-type-housekeeping.md +0 -42
  175. package/docs/retro/0118-settings-manager-apply-methods.md +0 -40
  176. package/docs/retro/0123-remove-vi-fn-cast-smell.md +0 -49
  177. package/docs/retro/0131-consolidate-shared-test-fixtures.md +0 -46
  178. package/docs/retro/0132-inject-io-into-session-config.md +0 -33
  179. package/docs/retro/0133-inject-sdk-boundary-into-agent-runner.md +0 -45
  180. package/docs/retro/0134-reduce-as-any-casts.md +0 -56
  181. package/docs/retro/0135-extract-display-helpers.md +0 -38
  182. package/docs/retro/0136-decompose-agent-menu.md +0 -43
  183. package/docs/retro/0144-consolidate-observation-model.md +0 -39
  184. package/docs/retro/0145-decompose-execute-push-ctx-to-boundary.md +0 -56
  185. package/docs/retro/0146-narrow-ui-context.md +0 -70
  186. package/docs/retro/0147-inject-wrap-text-into-conversation-viewer.md +0 -90
  187. package/docs/retro/0148-split-agent-widget-rendering.md +0 -39
  188. package/docs/retro/0152-add-prompt-snippet.md +0 -34
  189. package/docs/retro/0164-reorganize-into-domain-directories.md +0 -82
  190. package/docs/retro/0165-decompose-resolved-spawn-config.md +0 -78
  191. package/docs/retro/0166-extract-parent-session-info.md +0 -66
  192. package/docs/retro/0167-narrow-runner-io.md +0 -63
  193. package/docs/retro/0168-extract-tool-filter-config.md +0 -76
  194. package/docs/retro/0169-extract-run-context-from-run-options.md +0 -68
  195. package/docs/retro/0170-reduce-build-content-lines-complexity.md +0 -72
  196. package/docs/retro/0171-reduce-render-result-complexity.md +0 -77
  197. package/docs/retro/0172-extract-turn-formatting.md +0 -80
  198. package/docs/retro/0180-reorder-append-prompt-for-kv-cache.md +0 -62
  199. package/docs/retro/0185-remove-persistent-agent-memory.md +0 -73
  200. package/docs/retro/0188-replace-any-casts-with-sdk-types.md +0 -69
  201. package/docs/retro/0192-define-session-context-interface.md +0 -59
  202. package/docs/retro/0193-runtime-owns-context-queries.md +0 -75
  203. package/docs/retro/0194-align-tool-interfaces-for-structural-typing.md +0 -62
  204. package/docs/retro/0195-convert-tool-factories-to-classes.md +0 -84
  205. package/docs/retro/0196-convert-runner-menu-to-classes.md +0 -73
  206. package/docs/retro/0205-decompose-render-widget-lines.md +0 -64
  207. package/docs/retro/0206-decompose-show-agent-detail.md +0 -67
  208. package/docs/retro/0207-decompose-agent-widget-update.md +0 -92
  209. package/docs/retro/0208-extract-shared-test-fixtures.md +0 -102
  210. package/docs/retro/0214-convert-remaining-closure-factories-to-classes.md +0 -66
  211. package/docs/retro/0215-decompose-build-parent-context.md +0 -61
  212. package/docs/retro/0216-decompose-start-agent.md +0 -80
  213. package/docs/retro/0217-extract-overwrite-guard.md +0 -63
  214. package/docs/retro/0218-push-sdk-boundary-in-settings.md +0 -72
  215. package/docs/retro/0219-reduce-test-duplication-top-3-clone-families.md +0 -64
  216. package/docs/retro/0227-evolve-agent-record-into-agent.md +0 -80
  217. package/docs/retro/0228-async-start-agent-dissolve-run-handle.md +0 -80
  218. package/docs/retro/0229-agent-born-complete.md +0 -89
  219. package/docs/retro/0230-extract-concurrency-queue.md +0 -38
  220. package/docs/retro/0231-push-exec-registry-to-runner.md +0 -71
  221. package/docs/retro/0232-agent-resume-internal-observer-lifecycle.md +0 -109
  222. package/docs/retro/0237-remove-disallowed-tools.md +0 -78
  223. package/docs/retro/0238-remove-extensions-filtering.md +0 -72
  224. package/docs/retro/0239-collapse-filter-active-tools.md +0 -70
  225. package/docs/retro/0242-rename-agent-tool-to-subagent.md +0 -82
  226. package/docs/retro/0256-extract-worktree-isolation.md +0 -89
  227. package/docs/retro/0257-extract-child-session-factory.md +0 -33
  228. package/docs/retro/0262-add-workspace-provider-seam.md +0 -89
  229. package/docs/retro/0264-remove-extension-lifecycle-control.md +0 -91
  230. package/docs/retro/0265-born-complete-subagent-session.md +0 -97
  231. package/docs/retro/0270-type-consumable-public-surface.md +0 -108
  232. package/docs/retro/0272-export-workspace-collaborator-types.md +0 -38
  233. package/docs/retro/0277-encapsulate-agent-session.md +0 -80
  234. package/docs/retro/0280-rename-agent-to-subagent.md +0 -96
  235. package/docs/retro/0360-custom-agents-default-append.md +0 -81
  236. package/docs/retro/0373-extract-subagent-state.md +0 -94
  237. package/docs/retro/0374-encapsulate-subagent-start-notification.md +0 -171
  238. package/docs/retro/0375-extract-run-listener-workspace-bracket.md +0 -104
  239. package/docs/retro/0376-extract-manager-observer.md +0 -81
  240. package/docs/retro/0377-split-widget-delegation-from-runtime.md +0 -105
  241. package/docs/retro/0378-consolidate-lifecycle-test-fixtures.md +0 -100
  242. package/docs/retro/0379-consolidate-ui-tools-test-fixtures.md +0 -52
  243. package/docs/retro/0380-extract-layered-settings-loader.md +0 -97
  244. package/docs/retro/0381-replace-concurrency-queue-with-limiter.md +0 -95
  245. package/docs/retro/0400-include-parent-prompt-in-replace-mode.md +0 -84
  246. package/docs/retro/0403-abort-subagents-on-interrupt.md +0 -90
  247. package/docs/retro/0412-unify-session-mock-builders.md +0 -80
  248. package/docs/retro/0420-fold-run-metrics-live-activity-onto-record.md +0 -90
  249. package/docs/retro/0421-migrate-activity-readers-to-record-getters.md +0 -105
  250. package/docs/retro/0422-delete-activity-tracker-ui-observer.md +0 -97
  251. package/docs/retro/0423-widget-self-drive-from-lifecycle.md +0 -112
  252. package/docs/retro/0424-drop-widget-dep-from-subagent-tool.md +0 -89
  253. package/docs/retro/0425-reconcile-subagent-events-contract.md +0 -101
  254. package/docs/retro/0426-consolidate-residual-test-clone-families.md +0 -100
  255. package/docs/retro/0427-reconsider-ui-direction.md +0 -102
  256. package/docs/retro/0441-remove-agent-definition-management-subtree.md +0 -97
  257. package/docs/retro/0442-dissolve-agents-conversation-viewer.md +0 -108
  258. package/docs/retro/0443-consolidate-remaining-test-clone-families.md +0 -28
  259. package/docs/retro/0444-shrink-widget-to-background-agents.md +0 -81
  260. package/docs/retro/0445-native-session-navigation.md +0 -122
  261. package/docs/retro/0446-spike-session-navigation-entry-criteria.md +0 -99
  262. package/docs/retro/0447-extract-subagents-settings-command.md +0 -91
  263. package/docs/retro/0448-block-disabled-agent-spawn.md +0 -75
  264. package/docs/retro/0462-navigation-renderer-tui-components.md +0 -105
  265. package/docs/retro/0463-file-snapshot-source-evicted-agents.md +0 -103
  266. package/docs/retro/0470-refresh-readme-command-surface.md +0 -40
  267. package/src/observation/notification-state.ts +0 -27
  268. package/vitest.config.ts +0 -14
@@ -1,24 +1,17 @@
1
- /* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-redundant-type-constituents -- Pi SDK types are not fully exported; see upstream Pi SDK for type improvements */
2
1
  /**
3
2
  * Model resolution: exact match ("provider/modelId") with fuzzy fallback.
4
3
  */
5
-
6
- export interface ModelEntry {
7
- id: string;
8
- name: string;
9
- provider: string;
10
- }
4
+ import type { Model } from "@earendil-works/pi-ai";
11
5
 
12
6
  export interface ModelRegistry {
13
- find(provider: string, modelId: string): any;
14
- getAll(): any[];
15
- getAvailable?(): any[];
7
+ find(provider: string, modelId: string): Model<any> | undefined;
8
+ getAll(): Model<any>[];
9
+ getAvailable?(): Model<any>[];
16
10
  }
17
11
 
18
12
  /** Successful model resolution — `model` is the resolved or inherited model instance. */
19
13
  export interface ModelResolutionResult {
20
-
21
- model: any;
14
+ model: Model<any> | undefined;
22
15
  error?: undefined;
23
16
  }
24
17
 
@@ -42,12 +35,13 @@ export type ModelResolution = ModelResolutionResult | ModelResolutionError;
42
35
  * - `modelFromParams` false → silent fallback to `parentModel`.
43
36
  */
44
37
  export function resolveInvocationModel(
45
- parentModel: unknown,
38
+ parentModel: Model<any> | undefined,
46
39
  modelInput: string | undefined,
47
40
  modelFromParams: boolean,
48
- registry: ModelRegistry,
41
+ registry: ModelRegistry | undefined,
49
42
  ): ModelResolution {
50
43
  if (!modelInput) return { model: parentModel };
44
+ if (!registry) return { error: "No model registry available." };
51
45
  const resolved = resolveModel(modelInput, registry);
52
46
  if (typeof resolved !== "string") return { model: resolved };
53
47
  if (modelFromParams) return { error: resolved };
@@ -62,9 +56,9 @@ export function resolveInvocationModel(
62
56
  export function resolveModel(
63
57
  input: string,
64
58
  registry: ModelRegistry,
65
- ): any | string {
59
+ ): Model<any> | string {
66
60
  // Available models (those with auth configured)
67
- const all = (registry.getAvailable?.() ?? registry.getAll()) as ModelEntry[];
61
+ const all = registry.getAvailable?.() ?? registry.getAll();
68
62
  const availableSet = new Set(all.map(m => `${m.provider}/${m.id}`.toLowerCase()));
69
63
 
70
64
  // 1. Exact match: "provider/modelId" — only if available (has auth)
@@ -79,10 +73,27 @@ export function resolveModel(
79
73
  }
80
74
 
81
75
  // 2. Fuzzy match against available models
82
- const query = input.toLowerCase();
76
+ const bestMatch = findBestFuzzyMatch(all, input.toLowerCase());
77
+ if (bestMatch) {
78
+ const found = registry.find(bestMatch.provider, bestMatch.id);
79
+ if (found) return found;
80
+ }
83
81
 
84
- // Score each model: prefer exact id match > id contains > name contains > provider+id contains
85
- let bestMatch: ModelEntry | undefined;
82
+ // 3. No match list available models
83
+ const modelList = all
84
+ .map(m => ` ${m.provider}/${m.id}`)
85
+ .sort()
86
+ .join("\n");
87
+ return `Model not found: "${input}".\n\nAvailable models:\n${modelList}`;
88
+ }
89
+
90
+ /**
91
+ * Score each candidate model — prefer exact id match > id contains > name
92
+ * contains > provider+id contains — and return the best match at or above
93
+ * the acceptance threshold (20), or undefined if nothing scores high enough.
94
+ */
95
+ function findBestFuzzyMatch(all: Model<any>[], query: string): Model<any> | undefined {
96
+ let bestMatch: Model<any> | undefined;
86
97
  let bestScore = 0;
87
98
 
88
99
  for (const m of all) {
@@ -107,15 +118,5 @@ export function resolveModel(
107
118
  }
108
119
  }
109
120
 
110
- if (bestMatch && bestScore >= 20) {
111
- const found = registry.find(bestMatch.provider, bestMatch.id);
112
- if (found) return found;
113
- }
114
-
115
- // 3. No match — list available models
116
- const modelList = all
117
- .map(m => ` ${m.provider}/${m.id}`)
118
- .sort()
119
- .join("\n");
120
- return `Model not found: "${input}".\n\nAvailable models:\n${modelList}`;
121
+ return bestScore >= 20 ? bestMatch : undefined;
121
122
  }
@@ -8,7 +8,7 @@ import type { ParentSnapshot } from "#src/lifecycle/parent-snapshot";
8
8
  import type { AgentSpawnConfig } from "#src/lifecycle/subagent-manager";
9
9
  import { spawnBackground } from "#src/tools/background-spawner";
10
10
  import { runForeground } from "#src/tools/foreground-runner";
11
- import { buildDetails, buildTypeListText, textResult } from "#src/tools/helpers";
11
+ import { buildAgentGuidelines, buildDetails, buildTypeListText, textResult } from "#src/tools/helpers";
12
12
  import { renderAgentResult } from "#src/tools/result-renderer";
13
13
  import { type ModelInfo, resolveSpawnConfig } from "#src/tools/spawn-config";
14
14
  import type { ParentSessionInfo, Subagent } from "#src/types";
@@ -42,6 +42,7 @@ export type AgentToolSettings = {
42
42
  export class AgentTool {
43
43
  private readonly typeListText: string;
44
44
  private readonly availableTypesText: string;
45
+ private readonly agentGuidelines: string[];
45
46
 
46
47
  constructor(
47
48
  private readonly manager: AgentToolManager,
@@ -52,6 +53,7 @@ export class AgentTool {
52
53
  ) {
53
54
  this.typeListText = buildTypeListText(registry, agentDir);
54
55
  this.availableTypesText = registry.getAvailableTypes().join(", ");
56
+ this.agentGuidelines = buildAgentGuidelines(registry);
55
57
  }
56
58
 
57
59
  async execute(
@@ -128,6 +130,19 @@ export class AgentTool {
128
130
  const agentDir = this.agentDir;
129
131
  const registry = this.registry;
130
132
 
133
+ const guidelines = [
134
+ "- For parallel work, use run_in_background: true on each agent. Foreground calls run sequentially — only one executes at a time.",
135
+ ...this.agentGuidelines,
136
+ "- Provide clear, detailed prompts so the agent can work autonomously.",
137
+ "- Subagent results are returned as text — summarize them for the user.",
138
+ "- Use run_in_background for work you don't need immediately. You will be notified when it completes.",
139
+ "- Use resume with an agent ID to continue a previous agent's work.",
140
+ "- Use steer_subagent to send mid-run messages to a running background agent.",
141
+ '- Use model to specify a different model (as "provider/modelId", or fuzzy e.g. "haiku", "sonnet").',
142
+ "- Use thinking to control extended thinking level.",
143
+ "- Use inherit_context if the agent needs the parent conversation history.",
144
+ ].join("\n");
145
+
131
146
  return defineTool({
132
147
  name: "subagent" as const,
133
148
  label: "Subagent",
@@ -140,18 +155,7 @@ Available agent types:
140
155
  ${typeListText}
141
156
 
142
157
  Guidelines:
143
- - For parallel work, use run_in_background: true on each agent. Foreground calls run sequentially — only one executes at a time.
144
- - Use Explore for codebase searches and code understanding.
145
- - Use Plan for architecture and implementation planning.
146
- - Use general-purpose for complex tasks that need file editing.
147
- - Provide clear, detailed prompts so the agent can work autonomously.
148
- - Subagent results are returned as text — summarize them for the user.
149
- - Use run_in_background for work you don't need immediately. You will be notified when it completes.
150
- - Use resume with an agent ID to continue a previous agent's work.
151
- - Use steer_subagent to send mid-run messages to a running background agent.
152
- - Use model to specify a different model (as "provider/modelId", or fuzzy e.g. "haiku", "sonnet").
153
- - Use thinking to control extended thinking level.
154
- - Use inherit_context if the agent needs the parent conversation history.
158
+ ${guidelines}
155
159
  `,
156
160
  parameters: Type.Object({
157
161
  prompt: Type.String({
@@ -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() {
@@ -72,9 +72,8 @@ export interface TypeListRegistry extends AgentConfigLookup {
72
72
  * Extracted from index.ts so it can be called inside createAgentTool.
73
73
  */
74
74
  export function buildTypeListText(registry: TypeListRegistry, agentDir: string): string {
75
- const isEnabled = (name: string) => registry.resolveAgentConfig(name).enabled !== false;
76
- const defaultNames = registry.getDefaultAgentNames().filter(isEnabled);
77
- const userNames = registry.getUserAgentNames().filter(isEnabled);
75
+ const defaultNames = registry.getDefaultAgentNames().filter((name) => isEnabledAgent(registry, name));
76
+ const userNames = registry.getUserAgentNames().filter((name) => isEnabledAgent(registry, name));
78
77
 
79
78
  const defaultDescs = defaultNames.map((name) => {
80
79
  const cfg = registry.resolveAgentConfig(name);
@@ -88,14 +87,31 @@ export function buildTypeListText(registry: TypeListRegistry, agentDir: string):
88
87
  });
89
88
 
90
89
  return [
91
- "Default agents:",
92
- ...defaultDescs,
90
+ ...(defaultDescs.length > 0 ? ["Default agents:", ...defaultDescs] : []),
93
91
  ...(customDescs.length > 0 ? ["", "Custom agents:", ...customDescs] : []),
94
92
  "",
95
93
  `Custom agents can be defined in .pi/agents/<name>.md (project) or ${agentDir}/agents/<name>.md (global) — they are picked up automatically. Project-level agents override global ones. Creating a .md file with the same name as a default agent overrides it.`,
96
94
  ].join("\n");
97
95
  }
98
96
 
97
+ /** True when an agent config is present and not explicitly disabled. */
98
+ function isEnabledAgent(registry: AgentConfigLookup, name: string): boolean {
99
+ return registry.resolveAgentConfig(name).enabled !== false;
100
+ }
101
+
102
+ /**
103
+ * Collect the per-agent usage guidelines for the subagent tool's Guidelines: block.
104
+ * Sourced from each enabled default agent's `toolGuideline`, in registry order,
105
+ * so a disabled built-in drops its guideline automatically.
106
+ */
107
+ export function buildAgentGuidelines(registry: TypeListRegistry): string[] {
108
+ return registry
109
+ .getDefaultAgentNames()
110
+ .filter((name) => isEnabledAgent(registry, name))
111
+ .map((name) => registry.resolveAgentConfig(name).toolGuideline)
112
+ .filter((line): line is string => line !== undefined);
113
+ }
114
+
99
115
  /** Derive a short model label from a model string. */
100
116
  export function getModelLabelFromConfig(model: string): string {
101
117
  // Strip provider prefix (e.g. "anthropic/claude-sonnet-4-6" → "claude-sonnet-4-6")
@@ -1,4 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-argument -- Pi SDK types are not fully exported; see upstream Pi SDK for type improvements */
2
1
  /**
3
2
  * spawn-config.ts — Pure config resolution for the Agent tool.
4
3
  *
@@ -11,6 +10,7 @@ import type { Model } from "@earendil-works/pi-ai";
11
10
  import type { AgentTypeRegistry } from "#src/config/agent-types";
12
11
  import { resolveAgentInvocationConfig } from "#src/config/invocation-config";
13
12
  import { normalizeMaxTurns } from "#src/lifecycle/turn-limits";
13
+ import type { ModelRegistry } from "#src/session/model-resolver";
14
14
  import { resolveInvocationModel } from "#src/session/model-resolver";
15
15
  import type { AgentInvocation, SubagentType, ThinkingLevel } from "#src/types";
16
16
  import {
@@ -22,8 +22,8 @@ import {
22
22
 
23
23
  /** Model info extracted from the parent session context. */
24
24
  export interface ModelInfo {
25
- parentModel: { id: string; name?: string } | undefined;
26
- modelRegistry: unknown;
25
+ parentModel: Model<any> | undefined;
26
+ modelRegistry: ModelRegistry | undefined;
27
27
  }
28
28
 
29
29
  /** Identity: who is being spawned. */
@@ -99,7 +99,7 @@ export function resolveSpawnConfig(
99
99
  modelInfo.parentModel,
100
100
  resolvedConfig.modelInput,
101
101
  resolvedConfig.modelFromParams,
102
- modelInfo.modelRegistry as any,
102
+ modelInfo.modelRegistry,
103
103
  );
104
104
  if (resolution.error) return { error: resolution.error };
105
105
  const model = resolution.model;
@@ -113,7 +113,7 @@ export function resolveSpawnConfig(
113
113
  const effectiveModelId = model?.id;
114
114
  const modelName =
115
115
  effectiveModelId && effectiveModelId !== parentModelId
116
- ? (model?.name ?? effectiveModelId).replace(/^Claude\s+/i, "").toLowerCase()
116
+ ? model.name.replace(/^Claude\s+/i, "").toLowerCase()
117
117
  : undefined;
118
118
 
119
119
  const effectiveMaxTurns = normalizeMaxTurns(
@@ -1,7 +1,7 @@
1
1
  import { defineTool } from "@earendil-works/pi-coding-agent";
2
2
  import { Type } from "@sinclair/typebox";
3
3
  import { formatLifetimeTokens, textResult } from "#src/tools/helpers";
4
- import type { Subagent } from "#src/types";
4
+ import type { SteerOutcome, Subagent } from "#src/types";
5
5
 
6
6
  // ---- Deps interfaces ----
7
7
 
@@ -34,41 +34,51 @@ export class SteerTool {
34
34
  `Agent not found: "${params.agent_id}". It may have been cleaned up.`,
35
35
  );
36
36
  }
37
- if (record.status !== "running") {
37
+
38
+ let outcome: SteerOutcome;
39
+ try {
40
+ outcome = await record.steer(params.message);
41
+ } catch (err) {
38
42
  return textResult(
39
- `Agent "${params.agent_id}" is not running (status: ${record.status}). Cannot steer a non-running agent.`,
43
+ `Failed to steer agent: ${err instanceof Error ? err.message : String(err)}`,
40
44
  );
41
45
  }
42
- try {
43
- const delivered = await record.steer(params.message);
44
- this.events.emit("subagents:steered", { id: record.id, message: params.message });
45
- if (!delivered) {
46
+
47
+ switch (outcome.kind) {
48
+ case "rejected":
46
49
  return textResult(
47
- `Steering message queued for agent ${record.id}. It will be delivered once the session initializes.`,
50
+ `Agent "${params.agent_id}" is not running (status: ${outcome.status}). Cannot steer a non-running agent.`,
48
51
  );
49
- }
50
- const tokens = formatLifetimeTokens(record);
51
- const contextPercent = record.getContextPercent();
52
- const stateParts: string[] = [];
53
- if (tokens) stateParts.push(tokens);
54
- stateParts.push(`${record.toolUses} tool ${record.toolUses === 1 ? "use" : "uses"}`);
55
- if (contextPercent !== null)
56
- stateParts.push(`context ${Math.round(contextPercent)}% full`);
57
- if (record.compactionCount)
58
- stateParts.push(
59
- `${record.compactionCount} compaction${record.compactionCount === 1 ? "" : "s"}`,
52
+ case "buffered":
53
+ this.events.emit("subagents:steered", { id: record.id, message: params.message });
54
+ return textResult(
55
+ `Steering message queued for agent ${record.id}. It will be delivered once the session initializes.`,
60
56
  );
61
- return textResult(
62
- `Steering message sent to agent ${record.id}. The agent will process it after its current tool execution.\n` +
63
- `Current state: ${stateParts.join(" · ")}`,
64
- );
65
- } catch (err) {
66
- return textResult(
67
- `Failed to steer agent: ${err instanceof Error ? err.message : String(err)}`,
68
- );
57
+ case "delivered":
58
+ this.events.emit("subagents:steered", { id: record.id, message: params.message });
59
+ return this.renderDelivered(record);
69
60
  }
70
61
  }
71
62
 
63
+ /** Render the success message with live state for a delivered steer. */
64
+ private renderDelivered(record: Subagent) {
65
+ const tokens = formatLifetimeTokens(record);
66
+ const contextPercent = record.getContextPercent();
67
+ const stateParts: string[] = [];
68
+ if (tokens) stateParts.push(tokens);
69
+ stateParts.push(`${record.toolUses} tool ${record.toolUses === 1 ? "use" : "uses"}`);
70
+ if (contextPercent !== null)
71
+ stateParts.push(`context ${Math.round(contextPercent)}% full`);
72
+ if (record.compactionCount)
73
+ stateParts.push(
74
+ `${record.compactionCount} compaction${record.compactionCount === 1 ? "" : "s"}`,
75
+ );
76
+ return textResult(
77
+ `Steering message sent to agent ${record.id}. The agent will process it after its current tool execution.\n` +
78
+ `Current state: ${stateParts.join(" · ")}`,
79
+ );
80
+ }
81
+
72
82
  toToolDefinition() {
73
83
  return defineTool({
74
84
  name: "steer_subagent" as const,
package/src/types.ts CHANGED
@@ -7,6 +7,7 @@ import type { AgentSessionEvent, SessionContext as SdkSessionContext } from "@ea
7
7
  import type { ModelRegistry } from "#src/session/model-resolver";
8
8
 
9
9
 
10
+ export type { SteerOutcome } from "#src/lifecycle/subagent";
10
11
  export { Subagent } from "#src/lifecycle/subagent";
11
12
  export type { AgentSessionEvent, ThinkingLevel };
12
13
 
@@ -55,6 +56,8 @@ export interface AgentConfig extends AgentIdentity, AgentPromptConfig {
55
56
  inheritContext?: boolean;
56
57
  /** Default for spawn: run in background. undefined = caller decides. */
57
58
  runInBackground?: boolean;
59
+ /** One-line usage guideline for the subagent tool's Guidelines: block. Omitted — no guideline line. */
60
+ toolGuideline?: string;
58
61
  /** true = this is an embedded default agent (informational) */
59
62
  isDefault?: boolean;
60
63
  /** false = agent is hidden from the registry */
@@ -108,7 +111,7 @@ export interface ParentSessionInfo {
108
111
  parentSessionFile?: string;
109
112
  /** Session ID of the parent agent (stored in the child session's parentSession header). */
110
113
  parentSessionId?: string;
111
- /** Tool call ID for background notification wiring. When set, spawn attaches NotificationState. */
114
+ /** Tool call ID for background notification wiring. Exposed on the record via Subagent.toolCallId. */
112
115
  toolCallId?: string;
113
116
  }
114
117
 
@@ -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.