lsd-pi 1.2.4 → 1.3.6

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 (357) hide show
  1. package/README.md +22 -16
  2. package/dist/app-paths.d.ts +4 -0
  3. package/dist/app-paths.js +4 -0
  4. package/dist/bedrock-auth.d.ts +4 -0
  5. package/dist/bedrock-auth.js +4 -0
  6. package/dist/bundled-extension-paths.d.ts +4 -0
  7. package/dist/bundled-extension-paths.js +4 -0
  8. package/dist/cli-theme.d.ts +2 -2
  9. package/dist/cli-theme.js +13 -14
  10. package/dist/cli.js +43 -3
  11. package/dist/codex-rotate-settings.d.ts +4 -0
  12. package/dist/codex-rotate-settings.js +4 -0
  13. package/dist/help-text.d.ts +4 -0
  14. package/dist/help-text.js +4 -0
  15. package/dist/lsd-brand.d.ts +4 -0
  16. package/dist/lsd-brand.js +4 -0
  17. package/dist/onboarding-llm.d.ts +5 -0
  18. package/dist/onboarding-llm.js +5 -0
  19. package/dist/project-sessions.d.ts +4 -0
  20. package/dist/project-sessions.js +4 -0
  21. package/dist/resources/agents/generic.md +1 -0
  22. package/dist/resources/agents/scout.md +8 -1
  23. package/dist/resources/agents/worker.md +1 -0
  24. package/dist/resources/extensions/ask-user-questions.js +70 -0
  25. package/dist/resources/extensions/bg-shell/bg-shell-tool.js +6 -16
  26. package/dist/resources/extensions/browser-tools/tools/codegen.js +5 -5
  27. package/dist/resources/extensions/browser-tools/tools/navigation.js +107 -178
  28. package/dist/resources/extensions/browser-tools/tools/network-mock.js +112 -167
  29. package/dist/resources/extensions/browser-tools/tools/pages.js +182 -234
  30. package/dist/resources/extensions/browser-tools/tools/refs.js +202 -461
  31. package/dist/resources/extensions/browser-tools/tools/session.js +176 -323
  32. package/dist/resources/extensions/browser-tools/tools/state-persistence.js +91 -154
  33. package/dist/resources/extensions/browser-tools/utils.js +1 -1
  34. package/dist/resources/extensions/mac-tools/index.js +19 -34
  35. package/dist/resources/extensions/memory/index.js +20 -2
  36. package/dist/resources/extensions/shared/interview-ui.js +103 -20
  37. package/dist/resources/extensions/slash-commands/extension-manifest.json +2 -2
  38. package/dist/resources/extensions/slash-commands/fast.js +73 -0
  39. package/dist/resources/extensions/slash-commands/index.js +2 -0
  40. package/dist/resources/extensions/slash-commands/plan.js +54 -28
  41. package/dist/resources/extensions/slash-commands/tools.js +40 -4
  42. package/dist/resources/extensions/subagent/agent-switcher-component.js +208 -0
  43. package/dist/resources/extensions/subagent/agent-switcher-model.js +107 -0
  44. package/dist/resources/extensions/subagent/background-job-manager.js +24 -6
  45. package/dist/resources/extensions/subagent/background-runner.js +4 -0
  46. package/dist/resources/extensions/subagent/in-process-runner.js +387 -0
  47. package/dist/resources/extensions/subagent/index.js +715 -370
  48. package/dist/resources/extensions/subagent/launch-helpers.js +19 -5
  49. package/dist/resources/extensions/subagent/legacy-runner.js +503 -0
  50. package/dist/resources/extensions/voice/index.js +96 -36
  51. package/dist/resources/extensions/voice/push-to-talk.js +26 -0
  52. package/dist/shared-paths.d.ts +4 -0
  53. package/dist/shared-paths.js +4 -0
  54. package/dist/shared-preferences.d.ts +4 -0
  55. package/dist/shared-preferences.js +4 -0
  56. package/dist/startup-model-validation.d.ts +1 -1
  57. package/dist/startup-timings.d.ts +4 -0
  58. package/dist/startup-timings.js +4 -0
  59. package/dist/update-check.d.ts +4 -0
  60. package/dist/update-check.js +4 -0
  61. package/dist/update-cmd.d.ts +4 -0
  62. package/dist/update-cmd.js +4 -0
  63. package/dist/welcome-screen.js +4 -4
  64. package/dist/wizard.d.ts +4 -0
  65. package/dist/wizard.js +4 -0
  66. package/package.json +1 -1
  67. package/packages/pi-agent-core/dist/agent.d.ts +28 -0
  68. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  69. package/packages/pi-agent-core/dist/agent.js +105 -5
  70. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  71. package/packages/pi-agent-core/dist/types.d.ts +13 -2
  72. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  73. package/packages/pi-agent-core/dist/types.js.map +1 -1
  74. package/packages/pi-agent-core/src/agent.ts +142 -6
  75. package/packages/pi-agent-core/src/types.ts +12 -3
  76. package/packages/pi-ai/dist/adaptive/classifier.d.ts +29 -0
  77. package/packages/pi-ai/dist/adaptive/classifier.d.ts.map +1 -0
  78. package/packages/pi-ai/dist/adaptive/classifier.js +72 -0
  79. package/packages/pi-ai/dist/adaptive/classifier.js.map +1 -0
  80. package/packages/pi-ai/dist/adaptive/classifier.test.d.ts +2 -0
  81. package/packages/pi-ai/dist/adaptive/classifier.test.d.ts.map +1 -0
  82. package/packages/pi-ai/dist/adaptive/classifier.test.js +32 -0
  83. package/packages/pi-ai/dist/adaptive/classifier.test.js.map +1 -0
  84. package/packages/pi-ai/dist/index.d.ts +1 -0
  85. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  86. package/packages/pi-ai/dist/index.js +1 -0
  87. package/packages/pi-ai/dist/index.js.map +1 -1
  88. package/packages/pi-ai/dist/providers/amazon-bedrock.js +0 -2
  89. package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
  90. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  91. package/packages/pi-ai/dist/providers/anthropic-shared.js +0 -2
  92. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  93. package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts +1 -1
  94. package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
  95. package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
  96. package/packages/pi-ai/dist/providers/google-gemini-cli.d.ts.map +1 -1
  97. package/packages/pi-ai/dist/providers/google-gemini-cli.js +0 -4
  98. package/packages/pi-ai/dist/providers/google-gemini-cli.js.map +1 -1
  99. package/packages/pi-ai/dist/providers/google-vertex.js +0 -5
  100. package/packages/pi-ai/dist/providers/google-vertex.js.map +1 -1
  101. package/packages/pi-ai/dist/providers/google.js +0 -5
  102. package/packages/pi-ai/dist/providers/google.js.map +1 -1
  103. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts +35 -2
  104. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
  105. package/packages/pi-ai/dist/providers/openai-codex-responses.js +32 -6
  106. package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  107. package/packages/pi-ai/dist/providers/openai-codex-responses.test.js +127 -16
  108. package/packages/pi-ai/dist/providers/openai-codex-responses.test.js.map +1 -1
  109. package/packages/pi-ai/dist/providers/openai-completions.d.ts +1 -1
  110. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  111. package/packages/pi-ai/dist/providers/openai-completions.js +0 -1
  112. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  113. package/packages/pi-ai/dist/providers/openai-responses.d.ts +9 -2
  114. package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  115. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.d.ts +2 -0
  116. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.d.ts.map +1 -0
  117. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.js +67 -0
  118. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.js.map +1 -0
  119. package/packages/pi-ai/dist/providers/openai-responses.js +21 -3
  120. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  121. package/packages/pi-ai/dist/providers/openai-shared.d.ts +0 -1
  122. package/packages/pi-ai/dist/providers/openai-shared.d.ts.map +1 -1
  123. package/packages/pi-ai/dist/providers/openai-shared.js +0 -4
  124. package/packages/pi-ai/dist/providers/openai-shared.js.map +1 -1
  125. package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  126. package/packages/pi-ai/dist/providers/simple-options.js +2 -1
  127. package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
  128. package/packages/pi-ai/dist/types.d.ts +6 -2
  129. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  130. package/packages/pi-ai/dist/types.js.map +1 -1
  131. package/packages/pi-ai/src/adaptive/classifier.test.ts +38 -0
  132. package/packages/pi-ai/src/adaptive/classifier.ts +107 -0
  133. package/packages/pi-ai/src/index.ts +1 -0
  134. package/packages/pi-ai/src/providers/amazon-bedrock.ts +0 -2
  135. package/packages/pi-ai/src/providers/anthropic-shared.ts +0 -2
  136. package/packages/pi-ai/src/providers/azure-openai-responses.ts +1 -1
  137. package/packages/pi-ai/src/providers/google-gemini-cli.ts +0 -4
  138. package/packages/pi-ai/src/providers/google-vertex.ts +0 -5
  139. package/packages/pi-ai/src/providers/google.ts +0 -5
  140. package/packages/pi-ai/src/providers/openai-codex-responses.test.ts +143 -20
  141. package/packages/pi-ai/src/providers/openai-codex-responses.ts +48 -7
  142. package/packages/pi-ai/src/providers/openai-completions.ts +1 -2
  143. package/packages/pi-ai/src/providers/openai-responses.fast-mode.test.ts +73 -0
  144. package/packages/pi-ai/src/providers/openai-responses.ts +27 -4
  145. package/packages/pi-ai/src/providers/openai-shared.ts +0 -3
  146. package/packages/pi-ai/src/providers/simple-options.ts +2 -1
  147. package/packages/pi-ai/src/types.ts +6 -2
  148. package/packages/pi-coding-agent/dist/cli/args.js +2 -2
  149. package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
  150. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +7 -2
  151. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  152. package/packages/pi-coding-agent/dist/core/agent-session.js +53 -20
  153. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  154. package/packages/pi-coding-agent/dist/core/keybindings.d.ts +1 -1
  155. package/packages/pi-coding-agent/dist/core/keybindings.d.ts.map +1 -1
  156. package/packages/pi-coding-agent/dist/core/keybindings.js +2 -0
  157. package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
  158. package/packages/pi-coding-agent/dist/core/lsp/lsp.md +3 -1
  159. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  160. package/packages/pi-coding-agent/dist/core/sdk.js +36 -8
  161. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  162. package/packages/pi-coding-agent/dist/core/sdk.test.js +37 -0
  163. package/packages/pi-coding-agent/dist/core/sdk.test.js.map +1 -1
  164. package/packages/pi-coding-agent/dist/core/session-manager.d.ts +8 -0
  165. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  166. package/packages/pi-coding-agent/dist/core/session-manager.js +4 -0
  167. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  168. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +18 -7
  169. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  170. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.d.ts +2 -0
  171. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.d.ts.map +1 -0
  172. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.js +35 -0
  173. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.js.map +1 -0
  174. package/packages/pi-coding-agent/dist/core/settings-manager.js +32 -2
  175. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  176. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  177. package/packages/pi-coding-agent/dist/core/skills.js +4 -1
  178. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  179. package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
  180. package/packages/pi-coding-agent/dist/core/slash-commands.js +2 -1
  181. package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
  182. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  183. package/packages/pi-coding-agent/dist/core/system-prompt.js +12 -3
  184. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  185. package/packages/pi-coding-agent/dist/core/tool-priority.d.ts +4 -0
  186. package/packages/pi-coding-agent/dist/core/tool-priority.d.ts.map +1 -0
  187. package/packages/pi-coding-agent/dist/core/tool-priority.js +18 -0
  188. package/packages/pi-coding-agent/dist/core/tool-priority.js.map +1 -0
  189. package/packages/pi-coding-agent/dist/core/tool-priority.test.d.ts +2 -0
  190. package/packages/pi-coding-agent/dist/core/tool-priority.test.d.ts.map +1 -0
  191. package/packages/pi-coding-agent/dist/core/tool-priority.test.js +27 -0
  192. package/packages/pi-coding-agent/dist/core/tool-priority.test.js.map +1 -0
  193. package/packages/pi-coding-agent/dist/core/tools/grep.js +1 -1
  194. package/packages/pi-coding-agent/dist/core/tools/grep.js.map +1 -1
  195. package/packages/pi-coding-agent/dist/core/tools/index.d.ts +2 -0
  196. package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
  197. package/packages/pi-coding-agent/dist/core/tools/index.js +2 -0
  198. package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
  199. package/packages/pi-coding-agent/dist/core/tools/pty.d.ts +10 -1
  200. package/packages/pi-coding-agent/dist/core/tools/pty.d.ts.map +1 -1
  201. package/packages/pi-coding-agent/dist/core/tools/pty.js +29 -3
  202. package/packages/pi-coding-agent/dist/core/tools/pty.js.map +1 -1
  203. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.d.ts +2 -0
  204. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.d.ts.map +1 -0
  205. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js +26 -0
  206. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js.map +1 -0
  207. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.d.ts +45 -0
  208. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.d.ts.map +1 -0
  209. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.js +314 -0
  210. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.js.map +1 -0
  211. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.d.ts +2 -0
  212. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.d.ts.map +1 -0
  213. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.js +122 -0
  214. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.js.map +1 -0
  215. package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.js +1 -1
  216. package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.js.map +1 -1
  217. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts +2 -0
  218. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  219. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +19 -2
  220. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  221. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +11 -2
  222. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  223. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +49 -6
  224. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  225. package/packages/pi-coding-agent/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  226. package/packages/pi-coding-agent/dist/modes/interactive/components/thinking-selector.js +1 -2
  227. package/packages/pi-coding-agent/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  228. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +6 -0
  229. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  230. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +18 -4
  231. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  232. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts +13 -0
  233. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts.map +1 -0
  234. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js +49 -0
  235. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js.map +1 -0
  236. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.d.ts +2 -0
  237. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.d.ts.map +1 -0
  238. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js +197 -0
  239. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js.map +1 -0
  240. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  241. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +106 -0
  242. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  243. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  244. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +7 -0
  245. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  246. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +3 -0
  247. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  248. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  249. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
  250. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  251. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +88 -2
  252. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  253. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
  254. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +41 -0
  255. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  256. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts +2 -2
  257. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  258. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +10 -6
  259. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
  260. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +1 -1
  261. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  262. package/packages/pi-coding-agent/dist/modes/print-mode.d.ts.map +1 -1
  263. package/packages/pi-coding-agent/dist/modes/print-mode.js +6 -0
  264. package/packages/pi-coding-agent/dist/modes/print-mode.js.map +1 -1
  265. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  266. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +20 -0
  267. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  268. package/packages/pi-coding-agent/dist/tests/path-display.test.js +15 -0
  269. package/packages/pi-coding-agent/dist/tests/path-display.test.js.map +1 -1
  270. package/packages/pi-coding-agent/package.json +1 -1
  271. package/packages/pi-coding-agent/src/cli/args.ts +2 -2
  272. package/packages/pi-coding-agent/src/core/agent-session.ts +58 -21
  273. package/packages/pi-coding-agent/src/core/keybindings.ts +4 -1
  274. package/packages/pi-coding-agent/src/core/lsp/lsp.md +3 -1
  275. package/packages/pi-coding-agent/src/core/sdk.test.ts +45 -0
  276. package/packages/pi-coding-agent/src/core/sdk.ts +39 -8
  277. package/packages/pi-coding-agent/src/core/session-manager.ts +12 -0
  278. package/packages/pi-coding-agent/src/core/settings-manager.fast-mode.test.ts +46 -0
  279. package/packages/pi-coding-agent/src/core/settings-manager.ts +50 -9
  280. package/packages/pi-coding-agent/src/core/skills.ts +4 -1
  281. package/packages/pi-coding-agent/src/core/slash-commands.ts +2 -1
  282. package/packages/pi-coding-agent/src/core/system-prompt.ts +14 -3
  283. package/packages/pi-coding-agent/src/core/tool-priority.test.ts +30 -0
  284. package/packages/pi-coding-agent/src/core/tool-priority.ts +17 -0
  285. package/packages/pi-coding-agent/src/core/tools/grep.ts +1 -1
  286. package/packages/pi-coding-agent/src/core/tools/index.ts +3 -0
  287. package/packages/pi-coding-agent/src/core/tools/pty.ts +45 -6
  288. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-summary-line.test.ts +31 -0
  289. package/packages/pi-coding-agent/src/modes/interactive/components/btw-overlay.test.ts +172 -0
  290. package/packages/pi-coding-agent/src/modes/interactive/components/btw-overlay.ts +402 -0
  291. package/packages/pi-coding-agent/src/modes/interactive/components/embedded-terminal.ts +1 -1
  292. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +18 -2
  293. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +63 -9
  294. package/packages/pi-coding-agent/src/modes/interactive/components/thinking-selector.ts +1 -2
  295. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +1154 -1136
  296. package/packages/pi-coding-agent/src/modes/interactive/components/tool-summary-line.ts +64 -0
  297. package/packages/pi-coding-agent/src/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.ts +228 -0
  298. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +494 -389
  299. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +7 -0
  300. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +3 -0
  301. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +103 -3
  302. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +60 -1
  303. package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +11 -7
  304. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +1 -1
  305. package/packages/pi-coding-agent/src/modes/print-mode.ts +6 -0
  306. package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +29 -0
  307. package/packages/pi-coding-agent/src/tests/path-display.test.ts +17 -0
  308. package/packages/pi-tui/dist/components/loader.d.ts +5 -2
  309. package/packages/pi-tui/dist/components/loader.d.ts.map +1 -1
  310. package/packages/pi-tui/dist/components/loader.js +33 -3
  311. package/packages/pi-tui/dist/components/loader.js.map +1 -1
  312. package/packages/pi-tui/src/components/loader.ts +31 -3
  313. package/packages/rpc-client/src/index.ts +1 -1
  314. package/packages/rpc-client/src/rpc-client.ts +29 -0
  315. package/packages/rpc-client/src/rpc-types.ts +1 -1
  316. package/pkg/dist/modes/interactive/theme/theme.d.ts +2 -2
  317. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  318. package/pkg/dist/modes/interactive/theme/theme.js +10 -6
  319. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
  320. package/pkg/dist/modes/interactive/theme/themes.js +1 -1
  321. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  322. package/pkg/package.json +1 -1
  323. package/src/resources/agents/generic.md +1 -0
  324. package/src/resources/agents/scout.md +8 -1
  325. package/src/resources/agents/worker.md +1 -0
  326. package/src/resources/extensions/ask-user-questions.ts +88 -0
  327. package/src/resources/extensions/bg-shell/bg-shell-tool.ts +6 -16
  328. package/src/resources/extensions/browser-tools/tools/codegen.ts +5 -5
  329. package/src/resources/extensions/browser-tools/tools/navigation.ts +118 -196
  330. package/src/resources/extensions/browser-tools/tools/network-mock.ts +114 -205
  331. package/src/resources/extensions/browser-tools/tools/pages.ts +183 -237
  332. package/src/resources/extensions/browser-tools/tools/refs.ts +193 -507
  333. package/src/resources/extensions/browser-tools/tools/session.ts +182 -321
  334. package/src/resources/extensions/browser-tools/tools/state-persistence.ts +94 -172
  335. package/src/resources/extensions/browser-tools/utils.ts +1 -1
  336. package/src/resources/extensions/mac-tools/index.ts +19 -34
  337. package/src/resources/extensions/memory/index.ts +22 -2
  338. package/src/resources/extensions/shared/interview-ui.ts +108 -15
  339. package/src/resources/extensions/shared/tests/ask-user-freetext.test.ts +61 -0
  340. package/src/resources/extensions/shared/tests/custom-ui-fallbacks.test.ts +46 -0
  341. package/src/resources/extensions/slash-commands/extension-manifest.json +2 -2
  342. package/src/resources/extensions/slash-commands/fast.ts +89 -0
  343. package/src/resources/extensions/slash-commands/index.ts +2 -0
  344. package/src/resources/extensions/slash-commands/plan.ts +59 -30
  345. package/src/resources/extensions/slash-commands/tools.ts +43 -4
  346. package/src/resources/extensions/subagent/agent-switcher-component.ts +228 -0
  347. package/src/resources/extensions/subagent/agent-switcher-model.ts +160 -0
  348. package/src/resources/extensions/subagent/background-job-manager.ts +57 -6
  349. package/src/resources/extensions/subagent/background-runner.ts +8 -0
  350. package/src/resources/extensions/subagent/background-types.ts +4 -0
  351. package/src/resources/extensions/subagent/in-process-runner.ts +534 -0
  352. package/src/resources/extensions/subagent/index.ts +998 -493
  353. package/src/resources/extensions/subagent/launch-helpers.ts +15 -4
  354. package/src/resources/extensions/subagent/legacy-runner.ts +607 -0
  355. package/src/resources/extensions/voice/index.ts +308 -238
  356. package/src/resources/extensions/voice/push-to-talk.ts +42 -0
  357. package/src/resources/extensions/voice/tests/push-to-talk.test.ts +109 -0
@@ -12,49 +12,240 @@
12
12
  * Uses JSON mode to capture structured output from subagents.
13
13
  */
14
14
 
15
- import { spawn, execFileSync, type ChildProcess } from "node:child_process";
16
15
  import * as crypto from "node:crypto";
17
16
  import * as fs from "node:fs";
18
17
  import * as os from "node:os";
19
18
  import * as path from "node:path";
20
19
  import type { AgentToolResult } from "@gsd/pi-agent-core";
21
- import type { Message } from "@gsd/pi-ai";
20
+ import type { ImageContent, Message } from "@gsd/pi-ai";
22
21
  import { StringEnum } from "@gsd/pi-ai";
23
22
  import {
24
23
  type ExtensionAPI,
25
- getAgentDir,
26
24
  getMarkdownTheme,
27
25
  } from "@gsd/pi-coding-agent";
28
26
  import { Container, Key, Markdown, Spacer, Text } from "@gsd/pi-tui";
29
27
  import { Type } from "@sinclair/typebox";
30
28
  import { formatTokenCount, shortcutDesc } from "../shared/mod.js";
31
29
  import { type AgentConfig, type AgentScope, discoverAgents } from "./agents.js";
32
- import { buildSubagentProcessArgs, getBundledExtensionPathsFromEnv } from "./launch-helpers.js";
33
30
  import {
34
31
  type IsolationEnvironment,
35
- type IsolationMode,
36
32
  type MergeResult,
37
33
  createIsolation,
38
34
  mergeDeltaPatches,
39
35
  readIsolationMode,
40
36
  } from "./isolation.js";
41
37
  import { registerWorker, updateWorker } from "./worker-registry.js";
42
- import { handleSubagentPermissionRequest, isSubagentPermissionRequest } from "./approval-proxy.js";
43
38
  import { resolveConfiguredSubagentModel } from "./configured-model.js";
44
- import {
45
- normalizeSubagentModel,
46
- resolveSubagentModel,
47
- } from "./model-resolution.js";
39
+ import { resolveSubagentModel } from "./model-resolution.js";
48
40
  import { loadEffectivePreferences } from "../shared/preferences.js";
41
+ import {
42
+ readBudgetSubagentModelFromSettings,
43
+ runLegacySingleAgent,
44
+ stopLegacySubagents,
45
+ } from "./legacy-runner.js";
46
+ import {
47
+ MAX_IN_PROCESS_SUBAGENT_DEPTH,
48
+ startInProcessSingleAgent,
49
+ type SubagentHandle,
50
+ } from "./in-process-runner.js";
49
51
  import { CmuxClient, shellEscape } from "../cmux/index.js";
50
52
  import { BackgroundJobManager, type BackgroundSubagentJob } from "./background-job-manager.js";
51
53
  import { runSubagentInBackground } from "./background-runner.js";
54
+ import { showAgentSwitcher } from "./agent-switcher-component.js";
55
+ import {
56
+ buildAgentSwitchTargets,
57
+ type AgentSwitchTarget,
58
+ } from "./agent-switcher-model.js";
52
59
 
53
60
  const MAX_PARALLEL_TASKS = 8;
54
61
  const MAX_CONCURRENCY = 4;
55
62
  const COLLAPSED_ITEM_COUNT = 10;
56
63
  const DEFAULT_AWAIT_SUBAGENT_TIMEOUT_SECONDS = 120;
57
- const liveSubagentProcesses = new Set<ChildProcess>();
64
+ const USE_IN_PROCESS_SUBAGENT = process.env.LSD_SUBAGENT_IN_PROCESS !== "0";
65
+
66
+ type AgentSessionState = "running" | "completed" | "failed";
67
+
68
+ interface AgentSessionLink {
69
+ id: string;
70
+ agentName: string;
71
+ task: string;
72
+ parentSessionFile: string;
73
+ subagentSessionFile: string;
74
+ createdAt: number;
75
+ updatedAt: number;
76
+ state: AgentSessionState;
77
+ }
78
+
79
+ const agentSessionLinksById = new Map<string, AgentSessionLink>();
80
+ const agentSessionIdsByParent = new Map<string, string[]>();
81
+ const parentSessionByChild = new Map<string, string>();
82
+
83
+ interface LiveSubagentRuntime {
84
+ sessionFile?: string;
85
+ parentSessionFile?: string;
86
+ agentName: string;
87
+ isBusy: () => boolean;
88
+ sendPrompt: (text: string, images?: ImageContent[]) => Promise<void>;
89
+ sendSteer: (text: string, images?: ImageContent[]) => Promise<void>;
90
+ sendFollowUp: (text: string, images?: ImageContent[]) => Promise<void>;
91
+ }
92
+
93
+ const liveRuntimeBySessionFile = new Map<string, LiveSubagentRuntime>();
94
+ const inProcessSubagentDepthBySessionId = new Map<string, number>();
95
+ const inProcessSubagentAncestryBySessionId = new Map<string, string[]>();
96
+ let agentSessionLinkCounter = 0;
97
+
98
+ function listSessionFiles(sessionDir: string): string[] {
99
+ if (!fs.existsSync(sessionDir)) return [];
100
+ try {
101
+ return fs
102
+ .readdirSync(sessionDir)
103
+ .filter((name) => name.endsWith(".jsonl"))
104
+ .map((name) => path.join(sessionDir, name));
105
+ } catch {
106
+ return [];
107
+ }
108
+ }
109
+
110
+ function registerAgentSessionLink(link: Omit<AgentSessionLink, "id" | "createdAt" | "updatedAt">): AgentSessionLink {
111
+ const now = Date.now();
112
+ const id = `agent-${++agentSessionLinkCounter}`;
113
+ const full: AgentSessionLink = { ...link, id, createdAt: now, updatedAt: now };
114
+ agentSessionLinksById.set(id, full);
115
+ const list = agentSessionIdsByParent.get(link.parentSessionFile) ?? [];
116
+ list.push(id);
117
+ agentSessionIdsByParent.set(link.parentSessionFile, list);
118
+ parentSessionByChild.set(link.subagentSessionFile, link.parentSessionFile);
119
+ return full;
120
+ }
121
+
122
+ function updateAgentSessionLinkState(subagentSessionFile: string, state: AgentSessionState): void {
123
+ for (const link of agentSessionLinksById.values()) {
124
+ if (link.subagentSessionFile === subagentSessionFile) {
125
+ link.state = state;
126
+ link.updatedAt = Date.now();
127
+ return;
128
+ }
129
+ }
130
+ }
131
+
132
+ function upsertAgentSessionLink(
133
+ agentName: string,
134
+ task: string,
135
+ parentSessionFile: string,
136
+ subagentSessionFile: string,
137
+ state: AgentSessionState,
138
+ ): void {
139
+ const existingParent = parentSessionByChild.get(subagentSessionFile);
140
+ if (!existingParent) {
141
+ registerAgentSessionLink({
142
+ agentName,
143
+ task,
144
+ parentSessionFile,
145
+ subagentSessionFile,
146
+ state,
147
+ });
148
+ return;
149
+ }
150
+
151
+ updateAgentSessionLinkState(subagentSessionFile, state);
152
+ }
153
+
154
+ function getAgentSessionLinksForParent(parentSessionFile: string): AgentSessionLink[] {
155
+ const ids = agentSessionIdsByParent.get(parentSessionFile) ?? [];
156
+ return ids
157
+ .map((id) => agentSessionLinksById.get(id))
158
+ .filter((entry): entry is AgentSessionLink => Boolean(entry))
159
+ .sort((a, b) => b.updatedAt - a.updatedAt);
160
+ }
161
+
162
+ function readSessionHeader(sessionFile: string): {
163
+ parentSession?: string;
164
+ subagentName?: string;
165
+ subagentTask?: string;
166
+ subagentSystemPrompt?: string;
167
+ subagentTools?: string[];
168
+ } | null {
169
+ try {
170
+ const content = fs.readFileSync(sessionFile, "utf-8");
171
+ const firstLine = content.split("\n").find((line) => line.trim().length > 0);
172
+ if (!firstLine) return null;
173
+ const parsed = JSON.parse(firstLine);
174
+ if (!parsed || parsed.type !== "session") return null;
175
+ return {
176
+ parentSession: typeof parsed.parentSession === "string" ? parsed.parentSession : undefined,
177
+ subagentName: typeof parsed.subagentName === "string" ? parsed.subagentName : undefined,
178
+ subagentTask: typeof parsed.subagentTask === "string" ? parsed.subagentTask : undefined,
179
+ subagentSystemPrompt: typeof parsed.subagentSystemPrompt === "string" ? parsed.subagentSystemPrompt : undefined,
180
+ subagentTools: Array.isArray(parsed.subagentTools)
181
+ ? parsed.subagentTools.filter((tool: unknown): tool is string => typeof tool === "string")
182
+ : undefined,
183
+ };
184
+ } catch {
185
+ return null;
186
+ }
187
+ }
188
+
189
+ function backfillAgentSessionLinksForParent(parentSessionFile: string, sessionDir: string): AgentSessionLink[] {
190
+ for (const sessionFile of listSessionFiles(sessionDir)) {
191
+ if (sessionFile === parentSessionFile) continue;
192
+ const header = readSessionHeader(sessionFile);
193
+ if (header?.parentSession !== parentSessionFile) continue;
194
+ const existingParent = parentSessionByChild.get(sessionFile);
195
+ if (!existingParent) {
196
+ registerAgentSessionLink({
197
+ agentName: header.subagentName ?? "subagent",
198
+ task: header.subagentTask ?? "Recovered from persisted session lineage",
199
+ parentSessionFile,
200
+ subagentSessionFile: sessionFile,
201
+ state: "completed",
202
+ });
203
+ }
204
+ }
205
+ return getAgentSessionLinksForParent(parentSessionFile);
206
+ }
207
+
208
+ function formatSwitchTargetSummary(target: AgentSwitchTarget): string {
209
+ const current = target.isCurrent ? " (current)" : "";
210
+ if (target.kind === "parent") {
211
+ return `● parent — main session${current}`;
212
+ }
213
+
214
+ const icon = target.state === "running" ? "▶" : target.state === "failed" ? "✗" : "✓";
215
+ return `${icon} ${target.agentName} — ${target.taskPreview}${current}`;
216
+ }
217
+
218
+ function buildSwitchTargetsForParent(
219
+ parentSessionFile: string,
220
+ currentSessionFile: string,
221
+ currentCwd: string,
222
+ trackedLinks: AgentSessionLink[],
223
+ runningJobs: BackgroundSubagentJob[],
224
+ ): AgentSwitchTarget[] {
225
+ return buildAgentSwitchTargets({
226
+ currentSessionFile,
227
+ rootParentSessionFile: parentSessionFile,
228
+ currentCwd,
229
+ trackedLinks: trackedLinks.map((link) => ({
230
+ id: link.id,
231
+ agentName: link.agentName,
232
+ task: link.task,
233
+ parentSessionFile: link.parentSessionFile,
234
+ subagentSessionFile: link.subagentSessionFile,
235
+ updatedAt: link.updatedAt,
236
+ state: link.state,
237
+ })),
238
+ runningJobs: runningJobs.map((job) => ({
239
+ id: job.id,
240
+ agentName: job.agentName,
241
+ task: job.task,
242
+ startedAt: job.startedAt,
243
+ parentSessionFile: job.parentSessionFile,
244
+ sessionFile: job.sessionFile,
245
+ cwd: job.cwd,
246
+ })),
247
+ });
248
+ }
58
249
 
59
250
  const AwaitSubagentParams = Type.Object({
60
251
  jobs: Type.Optional(
@@ -72,40 +263,7 @@ const AwaitSubagentParams = Type.Object({
72
263
  });
73
264
 
74
265
  export async function stopLiveSubagents(): Promise<void> {
75
- const active = Array.from(liveSubagentProcesses);
76
- if (active.length === 0) return;
77
-
78
- for (const proc of active) {
79
- try {
80
- proc.kill("SIGTERM");
81
- } catch {
82
- /* ignore */
83
- }
84
- }
85
-
86
- await Promise.all(
87
- active.map(
88
- (proc) =>
89
- new Promise<void>((resolve) => {
90
- const done = () => resolve();
91
- const timer = setTimeout(done, 500);
92
- proc.once("exit", () => {
93
- clearTimeout(timer);
94
- resolve();
95
- });
96
- }),
97
- ),
98
- );
99
-
100
- for (const proc of active) {
101
- if (proc.exitCode === null) {
102
- try {
103
- proc.kill("SIGKILL");
104
- } catch {
105
- /* ignore */
106
- }
107
- }
108
- }
266
+ await stopLegacySubagents();
109
267
  }
110
268
 
111
269
  function formatBackgroundSubagentResults(jobs: BackgroundSubagentJob[]): string {
@@ -325,20 +483,8 @@ interface SingleResult {
325
483
  errorMessage?: string;
326
484
  step?: number;
327
485
  backgroundJobId?: string;
328
- }
329
-
330
- interface ForegroundSingleRunControl {
331
- agentName: string;
332
- task: string;
333
- cwd: string;
334
- abortController: AbortController;
335
- resultPromise: Promise<{ summary: string; stderr: string; exitCode: number; model?: string }>;
336
- adoptToBackground: (jobId: string) => boolean;
337
- }
338
-
339
- interface ForegroundSingleRunHooks {
340
- onStart?: (control: ForegroundSingleRunControl) => void;
341
- onFinish?: () => void;
486
+ sessionFile?: string;
487
+ parentSessionFile?: string;
342
488
  }
343
489
 
344
490
  interface SubagentDetails {
@@ -395,343 +541,8 @@ async function mapWithConcurrencyLimit<TIn, TOut>(
395
541
  return results;
396
542
  }
397
543
 
398
- function writePromptToTempFile(agentName: string, prompt: string): { dir: string; filePath: string } {
399
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
400
- const safeName = agentName.replace(/[^\w.-]+/g, "_");
401
- const filePath = path.join(tmpDir, `prompt-${safeName}.md`);
402
- fs.writeFileSync(filePath, prompt, { encoding: "utf-8", mode: 0o600 });
403
- return { dir: tmpDir, filePath };
404
- }
405
-
406
- function readBudgetSubagentModelFromSettings(): string | undefined {
407
- try {
408
- const settingsPath = path.join(getAgentDir(), "settings.json");
409
- if (!fs.existsSync(settingsPath)) return undefined;
410
- const raw = fs.readFileSync(settingsPath, "utf-8");
411
- const parsed = JSON.parse(raw) as { budgetSubagentModel?: unknown };
412
- return typeof parsed.budgetSubagentModel === "string"
413
- ? normalizeSubagentModel(parsed.budgetSubagentModel)
414
- : undefined;
415
- } catch {
416
- return undefined;
417
- }
418
- }
419
-
420
- function resolveSubagentCliPath(defaultCwd: string): string | null {
421
- const candidates = [process.env.GSD_BIN_PATH, process.env.LSD_BIN_PATH, process.argv[1]]
422
- .map((value) => value?.trim())
423
- .filter((value): value is string => Boolean(value && value !== "undefined"));
424
-
425
- for (const candidate of candidates) {
426
- if (path.isAbsolute(candidate) && fs.existsSync(candidate)) return candidate;
427
- }
428
-
429
- const cwdCandidates = [path.join(defaultCwd, "dist", "loader.js"), path.join(defaultCwd, "scripts", "dev-cli.js")];
430
- for (const candidate of cwdCandidates) {
431
- if (fs.existsSync(candidate)) return candidate;
432
- }
433
-
434
- for (const binName of ["lsd", "gsd"]) {
435
- try {
436
- const resolved = execFileSync("which", [binName], { encoding: "utf-8" }).trim();
437
- if (resolved) return resolved;
438
- } catch {
439
- /* ignore */
440
- }
441
- }
442
-
443
- return null;
444
- }
445
-
446
- function processSubagentEventLine(
447
- line: string,
448
- currentResult: SingleResult,
449
- emitUpdate: () => void,
450
- proc?: ChildProcess,
451
- ): boolean {
452
- if (!line.trim()) return false;
453
- let event: any;
454
- try {
455
- event = JSON.parse(line);
456
- } catch {
457
- return false;
458
- }
459
-
460
- if (proc && isSubagentPermissionRequest(event)) {
461
- void handleSubagentPermissionRequest(event, proc);
462
- return false;
463
- }
464
-
465
- if ((event.type === "message_end" || event.type === "turn_end") && event.message) {
466
- const msg = event.message as Message;
467
- currentResult.messages.push(msg);
468
-
469
- if (msg.role === "assistant") {
470
- currentResult.usage.turns++;
471
- const usage = msg.usage;
472
- if (usage) {
473
- currentResult.usage.input += usage.input || 0;
474
- currentResult.usage.output += usage.output || 0;
475
- currentResult.usage.cacheRead += usage.cacheRead || 0;
476
- currentResult.usage.cacheWrite += usage.cacheWrite || 0;
477
- currentResult.usage.cost += usage.cost?.total || 0;
478
- currentResult.usage.contextTokens = usage.totalTokens || 0;
479
- }
480
- if (msg.model && (!currentResult.model || msg.model.includes("/"))) currentResult.model = msg.model;
481
- if (msg.stopReason) currentResult.stopReason = msg.stopReason;
482
- if (msg.errorMessage) currentResult.errorMessage = msg.errorMessage;
483
- }
484
- emitUpdate();
485
- }
486
-
487
- if (event.type === "tool_result_end" && event.message) {
488
- currentResult.messages.push(event.message as Message);
489
- emitUpdate();
490
- }
491
-
492
- return event.type === "agent_end";
493
- }
494
-
495
- async function waitForFile(filePath: string, signal: AbortSignal | undefined, timeoutMs = 30 * 60 * 1000): Promise<boolean> {
496
- const started = Date.now();
497
- while (Date.now() - started < timeoutMs) {
498
- if (signal?.aborted) return false;
499
- if (fs.existsSync(filePath)) return true;
500
- await new Promise((resolve) => setTimeout(resolve, 150));
501
- }
502
- return false;
503
- }
504
-
505
544
  type OnUpdateCallback = (partial: AgentToolResult<SubagentDetails>) => void;
506
545
 
507
- async function runSingleAgent(
508
- defaultCwd: string,
509
- agents: AgentConfig[],
510
- agentName: string,
511
- task: string,
512
- cwd: string | undefined,
513
- step: number | undefined,
514
- modelOverride: string | undefined,
515
- parentModel: { provider: string; id: string } | undefined,
516
- signal: AbortSignal | undefined,
517
- onUpdate: OnUpdateCallback | undefined,
518
- makeDetails: (results: SingleResult[]) => SubagentDetails,
519
- foregroundHooks?: ForegroundSingleRunHooks,
520
- ): Promise<SingleResult> {
521
- const agent = agents.find((a) => a.name === agentName);
522
-
523
- if (!agent) {
524
- const available = agents.map((a) => `"${a.name}"`).join(", ") || "none";
525
- return {
526
- agent: agentName,
527
- agentSource: "unknown",
528
- task,
529
- exitCode: 1,
530
- messages: [],
531
- stderr: `Unknown agent: "${agentName}". Available agents: ${available}.`,
532
- usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
533
- step,
534
- };
535
- }
536
-
537
- let tmpPromptDir: string | null = null;
538
- let tmpPromptPath: string | null = null;
539
-
540
- const preferences = loadEffectivePreferences()?.preferences;
541
- const settingsBudgetModel = readBudgetSubagentModelFromSettings();
542
- const resolvedModel = resolveConfiguredSubagentModel(agent, preferences, settingsBudgetModel);
543
- const inferredModel = resolveSubagentModel(
544
- { name: agent.name, model: resolvedModel },
545
- { overrideModel: modelOverride, parentModel },
546
- );
547
-
548
- const currentResult: SingleResult = {
549
- agent: agentName,
550
- agentSource: agent.source,
551
- task,
552
- exitCode: 0,
553
- messages: [],
554
- stderr: "",
555
- usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
556
- model: inferredModel,
557
- step,
558
- };
559
-
560
- const emitUpdate = () => {
561
- if (onUpdate) {
562
- onUpdate({
563
- content: [{ type: "text", text: getFinalOutput(currentResult.messages) || "(running...)" }],
564
- details: makeDetails([currentResult]),
565
- });
566
- }
567
- };
568
-
569
- let wasAborted = false;
570
- let deferTempPromptCleanup = false;
571
- let tempPromptCleanupDone = false;
572
-
573
- const cleanupTempPromptFiles = () => {
574
- if (tempPromptCleanupDone) return;
575
- tempPromptCleanupDone = true;
576
- if (tmpPromptPath)
577
- try {
578
- fs.unlinkSync(tmpPromptPath);
579
- } catch {
580
- /* ignore */
581
- }
582
- if (tmpPromptDir)
583
- try {
584
- fs.rmdirSync(tmpPromptDir);
585
- } catch {
586
- /* ignore */
587
- }
588
- };
589
-
590
- try {
591
- if (agent.systemPrompt.trim()) {
592
- const tmp = writePromptToTempFile(agent.name, agent.systemPrompt);
593
- tmpPromptDir = tmp.dir;
594
- tmpPromptPath = tmp.filePath;
595
- }
596
- const args = buildSubagentProcessArgs(agent, task, tmpPromptPath, inferredModel);
597
-
598
- const exitCode = await new Promise<number>((resolve) => {
599
- const bundledPaths = getBundledExtensionPathsFromEnv();
600
- const extensionArgs = bundledPaths.flatMap((p) => ["--extension", p]);
601
- const cliPath = resolveSubagentCliPath(cwd ?? defaultCwd);
602
- if (!cliPath) {
603
- currentResult.stderr += "Unable to resolve LSD/GSD CLI path for subagent launch.";
604
- resolve(1);
605
- return;
606
- }
607
- const proc = spawn(
608
- process.execPath,
609
- [cliPath, ...extensionArgs, ...args],
610
- { cwd: cwd ?? defaultCwd, shell: false, stdio: ["pipe", "pipe", "pipe"] },
611
- );
612
- // Keep stdin open so approval/classifier responses can be proxied back
613
- // into the child process. Closing it here can leave the subagent stuck
614
- // after its first turn when it requests permission for a tool call.
615
- liveSubagentProcesses.add(proc);
616
- let buffer = "";
617
- let completionSeen = false;
618
- let resolved = false;
619
- let foregroundReleased = false;
620
- const procAbortController = new AbortController();
621
- let resolveBackgroundResult: ((value: { summary: string; stderr: string; exitCode: number; model?: string }) => void) | undefined;
622
- let rejectBackgroundResult: ((reason?: unknown) => void) | undefined;
623
- const backgroundResultPromise = new Promise<{ summary: string; stderr: string; exitCode: number; model?: string }>((resolveBg, rejectBg) => {
624
- resolveBackgroundResult = resolveBg;
625
- rejectBackgroundResult = rejectBg;
626
- });
627
-
628
- const finishForeground = (code: number) => {
629
- if (resolved) return;
630
- resolved = true;
631
- resolve(code);
632
- };
633
-
634
- const adoptToBackground = (jobId: string): boolean => {
635
- if (resolved || foregroundReleased) return false;
636
- foregroundReleased = true;
637
- deferTempPromptCleanup = true;
638
- currentResult.backgroundJobId = jobId;
639
- finishForeground(0);
640
- return true;
641
- };
642
-
643
- backgroundResultPromise.finally(() => {
644
- if (deferTempPromptCleanup) cleanupTempPromptFiles();
645
- });
646
-
647
- foregroundHooks?.onStart?.({
648
- agentName,
649
- task,
650
- cwd: cwd ?? defaultCwd,
651
- abortController: procAbortController,
652
- resultPromise: backgroundResultPromise,
653
- adoptToBackground,
654
- });
655
-
656
- proc.stdout.on("data", (data) => {
657
- buffer += data.toString();
658
- const lines = buffer.split("\n");
659
- buffer = lines.pop() || "";
660
- for (const line of lines) {
661
- if (processSubagentEventLine(line, currentResult, emitUpdate, proc)) {
662
- completionSeen = true;
663
- try {
664
- proc.kill("SIGTERM");
665
- } catch {
666
- /* ignore */
667
- }
668
- }
669
- }
670
- });
671
-
672
- proc.stderr.on("data", (data) => {
673
- currentResult.stderr += data.toString();
674
- });
675
-
676
- proc.on("close", (code) => {
677
- liveSubagentProcesses.delete(proc);
678
- if (buffer.trim()) {
679
- const completedOnFlush = processSubagentEventLine(buffer, currentResult, emitUpdate, proc);
680
- completionSeen = completionSeen || completedOnFlush;
681
- }
682
- const finalExitCode = completionSeen && (code === null || code === 143 || code === 15) ? 0 : (code ?? 0);
683
- currentResult.exitCode = finalExitCode;
684
- resolveBackgroundResult?.({
685
- summary: getFinalOutput(currentResult.messages),
686
- stderr: currentResult.stderr,
687
- exitCode: finalExitCode,
688
- model: currentResult.model,
689
- });
690
- foregroundHooks?.onFinish?.();
691
- finishForeground(finalExitCode);
692
- });
693
-
694
- proc.on("error", (error) => {
695
- liveSubagentProcesses.delete(proc);
696
- rejectBackgroundResult?.(error);
697
- foregroundHooks?.onFinish?.();
698
- finishForeground(1);
699
- });
700
-
701
- const killProc = () => {
702
- wasAborted = true;
703
- procAbortController.abort();
704
- proc.kill("SIGTERM");
705
- setTimeout(() => {
706
- if (!proc.killed) proc.kill("SIGKILL");
707
- }, 5000);
708
- };
709
-
710
- if (signal) {
711
- if (signal.aborted) killProc();
712
- else signal.addEventListener("abort", killProc, { once: true });
713
- }
714
-
715
- if (procAbortController.signal.aborted) {
716
- killProc();
717
- } else {
718
- procAbortController.signal.addEventListener("abort", () => {
719
- proc.kill("SIGTERM");
720
- setTimeout(() => {
721
- if (!proc.killed) proc.kill("SIGKILL");
722
- }, 5000);
723
- }, { once: true });
724
- }
725
- });
726
-
727
- currentResult.exitCode = exitCode;
728
- if (wasAborted) throw new Error("Subagent was aborted");
729
- return currentResult;
730
- } finally {
731
- if (!deferTempPromptCleanup) cleanupTempPromptFiles();
732
- }
733
- }
734
-
735
546
  const TaskItem = Type.Object({
736
547
  agent: Type.String({ description: "Name of the agent to invoke" }),
737
548
  task: Type.String({ description: "Task to delegate to the agent" }),
@@ -803,17 +614,91 @@ export default function(pi: ExtensionAPI) {
803
614
  let bgManager: BackgroundJobManager | null = null;
804
615
  const foregroundSubagentStatusKey = "foreground-subagent";
805
616
  const foregroundSubagentHint = "Ctrl+B: move foreground subagent to background";
806
- type ActiveForegroundSubagent = ForegroundSingleRunControl & { claimed: boolean };
617
+ type ActiveForegroundSubagent = {
618
+ claimed: boolean;
619
+ agentName: string;
620
+ task: string;
621
+ cwd: string;
622
+ parentSessionFile?: string;
623
+ resultPromise: Promise<{
624
+ summary: string;
625
+ stderr: string;
626
+ exitCode: number;
627
+ model?: string;
628
+ sessionFile?: string;
629
+ parentSessionFile?: string;
630
+ }>;
631
+ adoptToBackground: (jobId: string) => boolean;
632
+ abortController?: AbortController;
633
+ handle?: SubagentHandle;
634
+ sendPrompt?: (text: string, images?: ImageContent[]) => Promise<void>;
635
+ sendSteer?: (text: string, images?: ImageContent[]) => Promise<void>;
636
+ sendFollowUp?: (text: string, images?: ImageContent[]) => Promise<void>;
637
+ isBusy?: () => boolean;
638
+ };
807
639
  let activeForegroundSubagent: ActiveForegroundSubagent | null = null;
640
+ let activeSessionFileForUi: string | undefined;
641
+ const liveStreamBufferBySession = new Map<string, string>();
642
+
643
+ function flushLiveStream(sessionFile: string): void {
644
+ const buffered = liveStreamBufferBySession.get(sessionFile);
645
+ if (!buffered || !buffered.trim()) return;
646
+ liveStreamBufferBySession.set(sessionFile, "");
647
+ pi.sendMessage(
648
+ {
649
+ customType: "live_subagent_stream",
650
+ content: buffered,
651
+ display: true,
652
+ },
653
+ { deliverAs: "followUp" },
654
+ );
655
+ }
656
+
657
+ function pushLiveStreamDelta(sessionFile: string, delta: string): void {
658
+ const prev = liveStreamBufferBySession.get(sessionFile) ?? "";
659
+ const next = prev + delta;
660
+ liveStreamBufferBySession.set(sessionFile, next);
661
+ if (next.length >= 120 || next.includes("\n")) {
662
+ flushLiveStream(sessionFile);
663
+ }
664
+ }
665
+
666
+ function getCurrentSessionSubagentMetadata(sessionFile: string | undefined) {
667
+ if (!sessionFile) return null;
668
+ return readSessionHeader(sessionFile);
669
+ }
670
+
671
+ function applyCurrentSessionSubagentTools(ctx: any): void {
672
+ const metadata = getCurrentSessionSubagentMetadata(ctx.sessionManager.getSessionFile());
673
+ if (metadata?.subagentTools && metadata.subagentTools.length > 0) {
674
+ ctx.setActiveTools(metadata.subagentTools);
675
+ }
676
+ }
808
677
 
809
678
  function getBgManager(): BackgroundJobManager {
810
679
  if (!bgManager) throw new Error("BackgroundJobManager not initialized.");
811
680
  return bgManager;
812
681
  }
813
682
 
814
- pi.on("session_start", async () => {
683
+ pi.on("session_start", async (_event, ctx) => {
684
+ activeSessionFileForUi = ctx.sessionManager.getSessionFile();
815
685
  bgManager = new BackgroundJobManager({
816
686
  onJobComplete: (job) => {
687
+ if (job.sessionFile && job.parentSessionFile) {
688
+ const existingParent = parentSessionByChild.get(job.sessionFile);
689
+ if (!existingParent) {
690
+ registerAgentSessionLink({
691
+ agentName: job.agentName,
692
+ task: job.task,
693
+ parentSessionFile: job.parentSessionFile,
694
+ subagentSessionFile: job.sessionFile,
695
+ state: job.status === "failed" ? "failed" : "completed",
696
+ });
697
+ } else {
698
+ updateAgentSessionLinkState(job.sessionFile, job.status === "failed" ? "failed" : "completed");
699
+ }
700
+ }
701
+
817
702
  if (job.awaited) return;
818
703
  const statusEmoji = job.status === "completed" ? "✓" : job.status === "cancelled" ? "✗ cancelled" : "✗ failed";
819
704
  const elapsed = ((Date.now() - job.startedAt) / 1000).toFixed(1);
@@ -839,25 +724,94 @@ export default function(pi: ExtensionAPI) {
839
724
  );
840
725
  },
841
726
  });
727
+ applyCurrentSessionSubagentTools(ctx);
842
728
  });
843
729
 
730
+ pi.on("session_switch", async (_event, ctx) => {
731
+ activeSessionFileForUi = ctx.sessionManager.getSessionFile();
732
+ applyCurrentSessionSubagentTools(ctx);
733
+ });
844
734
 
845
- pi.on("session_before_switch", async () => {
846
- activeForegroundSubagent = null;
847
- if (bgManager) {
848
- for (const job of bgManager.getRunningJobs()) {
849
- bgManager.cancel(job.id);
735
+ pi.on("before_agent_start", async (event, ctx) => {
736
+ const metadata = getCurrentSessionSubagentMetadata(ctx.sessionManager.getSessionFile());
737
+ if (!metadata?.subagentSystemPrompt) return;
738
+ const subagentName = metadata.subagentName ?? "subagent";
739
+ const taskNote = metadata.subagentTask
740
+ ? `Original delegated task: ${metadata.subagentTask}`
741
+ : "Continue operating as the delegated subagent for this session.";
742
+ const antiRecursion = [
743
+ `You are already the ${subagentName} subagent for this session.`,
744
+ "Do not spawn or delegate to another subagent with the same name as yourself.",
745
+ `If the user asks you to continue ${subagentName} work, do that work directly in this session.`,
746
+ taskNote,
747
+ ].join("\n");
748
+ return {
749
+ systemPrompt: `${event.systemPrompt}\n\n${antiRecursion}\n\n${metadata.subagentSystemPrompt}`,
750
+ };
751
+ });
752
+ pi.on("input", async (event, ctx) => {
753
+ const sessionFile = ctx.sessionManager.getSessionFile();
754
+ const runtimeFromSession = sessionFile ? liveRuntimeBySessionFile.get(sessionFile) : undefined;
755
+ const runtimeFromForeground = (!runtimeFromSession && activeForegroundSubagent && !activeForegroundSubagent.claimed &&
756
+ activeForegroundSubagent.sendPrompt && activeForegroundSubagent.sendSteer && activeForegroundSubagent.sendFollowUp && activeForegroundSubagent.isBusy &&
757
+ sessionFile && activeForegroundSubagent.parentSessionFile === sessionFile)
758
+ ? {
759
+ agentName: activeForegroundSubagent.agentName,
760
+ isBusy: activeForegroundSubagent.isBusy,
761
+ sendPrompt: activeForegroundSubagent.sendPrompt,
762
+ sendSteer: activeForegroundSubagent.sendSteer,
763
+ sendFollowUp: activeForegroundSubagent.sendFollowUp,
764
+ }
765
+ : undefined;
766
+ const runtime = runtimeFromSession ?? runtimeFromForeground;
767
+ if (!runtime) return;
768
+
769
+ const text = event.text?.trim();
770
+ if (!text) return { action: "handled" as const };
771
+
772
+ const isSlashCommand = text.startsWith("/");
773
+ if (isSlashCommand) return;
774
+
775
+ try {
776
+ if (runtime.isBusy()) {
777
+ await runtime.sendSteer(text, event.images);
778
+ ctx.ui.notify(`Sent steer to running subagent ${runtime.agentName}.`, "info");
779
+ } else {
780
+ await runtime.sendPrompt(text, event.images);
781
+ ctx.ui.notify(`Sent prompt to live subagent ${runtime.agentName}.`, "info");
850
782
  }
783
+ return { action: "handled" as const };
784
+ } catch (error) {
785
+ ctx.ui.notify(
786
+ `Failed to send input to live subagent ${runtime.agentName}: ${error instanceof Error ? error.message : String(error)}`,
787
+ "error",
788
+ );
789
+ return { action: "handled" as const };
851
790
  }
852
791
  });
853
792
 
793
+
794
+ pi.on("session_before_switch", async () => {
795
+ if (activeSessionFileForUi) flushLiveStream(activeSessionFileForUi);
796
+ activeForegroundSubagent = null;
797
+ });
798
+
854
799
  pi.on("session_shutdown", async () => {
800
+ if (activeSessionFileForUi) flushLiveStream(activeSessionFileForUi);
801
+ activeSessionFileForUi = undefined;
855
802
  activeForegroundSubagent = null;
856
803
  await stopLiveSubagents();
857
804
  if (bgManager) {
858
805
  bgManager.shutdown();
859
806
  bgManager = null;
860
807
  }
808
+ agentSessionLinksById.clear();
809
+ agentSessionIdsByParent.clear();
810
+ parentSessionByChild.clear();
811
+ liveRuntimeBySessionFile.clear();
812
+ inProcessSubagentDepthBySessionId.clear();
813
+ inProcessSubagentAncestryBySessionId.clear();
814
+ liveStreamBufferBySession.clear();
861
815
  });
862
816
 
863
817
  // /subagents command
@@ -972,6 +926,189 @@ export default function(pi: ExtensionAPI) {
972
926
  },
973
927
  });
974
928
 
929
+ // /agent command - switch to the parent or a tracked subagent session
930
+ pi.registerCommand("agent", {
931
+ description: "Switch focus to parent/subagent sessions (/agent picker, /agent <id|index|name>, /agent parent)",
932
+ handler: async (args: string, ctx) => {
933
+ const currentSessionFile = ctx.sessionManager.getSessionFile();
934
+ if (!currentSessionFile) {
935
+ ctx.ui.notify("Current session is in-memory only; /agent requires a persisted session file.", "warning");
936
+ return;
937
+ }
938
+
939
+ const arg = args.trim();
940
+ const parentSessionFile = parentSessionByChild.get(currentSessionFile);
941
+ const currentParent = parentSessionFile ?? currentSessionFile;
942
+ const currentSessionDir = path.dirname(currentParent);
943
+
944
+ let tracked = getAgentSessionLinksForParent(currentParent).filter((entry) => fs.existsSync(entry.subagentSessionFile));
945
+ if (tracked.length === 0) {
946
+ tracked = backfillAgentSessionLinksForParent(currentParent, currentSessionDir)
947
+ .filter((entry) => fs.existsSync(entry.subagentSessionFile));
948
+ }
949
+
950
+ const runningJobs = bgManager?.getRunningJobs() ?? [];
951
+ const switchTargets = buildSwitchTargetsForParent(
952
+ currentParent,
953
+ currentSessionFile,
954
+ ctx.cwd,
955
+ tracked,
956
+ runningJobs,
957
+ );
958
+
959
+ const applySwitchTarget = async (target: AgentSwitchTarget): Promise<void> => {
960
+ if (target.selectionAction === "blocked") {
961
+ ctx.ui.notify(target.blockedReason ?? "That target cannot be selected yet.", "warning");
962
+ return;
963
+ }
964
+
965
+ if (target.selectionAction === "attach_live") {
966
+ if (!fs.existsSync(target.sessionFile)) {
967
+ ctx.ui.notify(`Live subagent session file is missing: ${target.sessionFile}`, "error");
968
+ return;
969
+ }
970
+ const liveRuntime = liveRuntimeBySessionFile.get(target.sessionFile);
971
+ if (!liveRuntime) {
972
+ ctx.ui.notify("Live runtime is no longer available for this subagent. It may have completed.", "warning");
973
+ return;
974
+ }
975
+ const switched = await ctx.switchSession(target.sessionFile);
976
+ if (switched.cancelled) {
977
+ ctx.ui.notify("Session switch was cancelled.", "warning");
978
+ return;
979
+ }
980
+ ctx.ui.notify(`Attached to running subagent ${target.agentName}. Prompts in this session are routed live (busy => steer, idle => prompt). Use /agent parent to return.`, "info");
981
+ return;
982
+ }
983
+
984
+ if (target.kind === "parent") {
985
+ if (!fs.existsSync(target.sessionFile)) {
986
+ ctx.ui.notify(`Parent session file not found: ${target.sessionFile}`, "error");
987
+ return;
988
+ }
989
+ const switched = await ctx.switchSession(target.sessionFile);
990
+ if (switched.cancelled) {
991
+ ctx.ui.notify("Session switch was cancelled.", "warning");
992
+ return;
993
+ }
994
+ ctx.ui.notify("Switched to parent session.", "info");
995
+ return;
996
+ }
997
+
998
+ if (!fs.existsSync(target.sessionFile)) {
999
+ ctx.ui.notify(`Subagent session file is missing: ${target.sessionFile}`, "error");
1000
+ return;
1001
+ }
1002
+
1003
+ const switched = await ctx.switchSession(target.sessionFile);
1004
+ if (switched.cancelled) {
1005
+ ctx.ui.notify("Session switch was cancelled.", "warning");
1006
+ return;
1007
+ }
1008
+ updateAgentSessionLinkState(target.sessionFile, target.state === "failed" ? "failed" : "completed");
1009
+ ctx.ui.notify(`Switched to subagent ${target.agentName}. This resumes the saved subagent session; use /agent parent to return.`, "info");
1010
+ };
1011
+
1012
+ if (!arg) {
1013
+ const subagentTargets = switchTargets.filter((target) => target.kind === "subagent");
1014
+ if (ctx.hasUI) {
1015
+ if (subagentTargets.length === 0 && !parentSessionFile) {
1016
+ ctx.ui.notify("No tracked subagent sessions for this parent session yet. Run a single-mode subagent first (foreground or background).", "info");
1017
+ return;
1018
+ }
1019
+
1020
+ const selected = await showAgentSwitcher(ctx, switchTargets);
1021
+ if (!selected) return;
1022
+ await applySwitchTarget(selected);
1023
+ return;
1024
+ }
1025
+
1026
+ if (subagentTargets.length === 0 && !parentSessionFile) {
1027
+ ctx.ui.notify("No tracked subagent sessions for this parent session yet. Run a single-mode subagent first (foreground or background).", "info");
1028
+ return;
1029
+ }
1030
+
1031
+ const lines = ["Agent switch targets:"];
1032
+ switchTargets.forEach((target, index) => {
1033
+ lines.push(`${index + 1}. ${formatSwitchTargetSummary(target)}`);
1034
+ });
1035
+ lines.push("", "Use `/agent <index|id|name>` for explicit targeting, or `/agent parent`.");
1036
+ ctx.ui.notify(lines.join("\n"), "info");
1037
+ return;
1038
+ }
1039
+
1040
+ if (arg === "parent" || arg === "main") {
1041
+ if (!parentSessionFile) {
1042
+ ctx.ui.notify("You are already in the parent/main session.", "info");
1043
+ return;
1044
+ }
1045
+ if (!fs.existsSync(parentSessionFile)) {
1046
+ ctx.ui.notify(`Parent session file not found: ${parentSessionFile}`, "error");
1047
+ return;
1048
+ }
1049
+ const switched = await ctx.switchSession(parentSessionFile);
1050
+ if (switched.cancelled) {
1051
+ ctx.ui.notify("Session switch was cancelled.", "warning");
1052
+ return;
1053
+ }
1054
+ ctx.ui.notify("Switched to parent session.", "info");
1055
+ return;
1056
+ }
1057
+
1058
+ let target: AgentSessionLink | undefined;
1059
+ if (/^\d+$/.test(arg)) {
1060
+ const index = Number.parseInt(arg, 10) - 1;
1061
+ target = tracked[index];
1062
+ }
1063
+ if (!target) {
1064
+ target = tracked.find((entry) => entry.id === arg);
1065
+ }
1066
+ if (!target) {
1067
+ target = tracked.find((entry) => entry.agentName === arg);
1068
+ }
1069
+ if (!target) {
1070
+ target = tracked.find((entry) => path.basename(entry.subagentSessionFile) === arg);
1071
+ }
1072
+
1073
+ if (!target) {
1074
+ const runningTarget = switchTargets.find((entry) => entry.id === arg && entry.kind === "subagent");
1075
+ if (runningTarget?.state === "running") {
1076
+ ctx.ui.notify(runningTarget.blockedReason ?? "Selected subagent is still running. Live attach is not implemented yet.", "warning");
1077
+ return;
1078
+ }
1079
+ ctx.ui.notify(`Unknown subagent target: ${arg}. Run /agent to list available targets.`, "warning");
1080
+ return;
1081
+ }
1082
+
1083
+ if (!fs.existsSync(target.subagentSessionFile)) {
1084
+ ctx.ui.notify(`Subagent session file is missing: ${target.subagentSessionFile}`, "error");
1085
+ return;
1086
+ }
1087
+
1088
+ if (target.state === "running") {
1089
+ const liveRuntime = liveRuntimeBySessionFile.get(target.subagentSessionFile);
1090
+ if (!liveRuntime) {
1091
+ ctx.ui.notify("Live runtime is no longer available for this subagent. It may have completed.", "warning");
1092
+ return;
1093
+ }
1094
+ const switched = await ctx.switchSession(target.subagentSessionFile);
1095
+ if (switched.cancelled) {
1096
+ ctx.ui.notify("Session switch was cancelled.", "warning");
1097
+ return;
1098
+ }
1099
+ ctx.ui.notify(`Attached to running subagent ${target.agentName}. Prompts in this session are routed live (busy => steer, idle => prompt). Use /agent parent to return.`, "info");
1100
+ return;
1101
+ }
1102
+
1103
+ const switched = await ctx.switchSession(target.subagentSessionFile);
1104
+ if (switched.cancelled) {
1105
+ ctx.ui.notify("Session switch was cancelled.", "warning");
1106
+ return;
1107
+ }
1108
+ updateAgentSessionLinkState(target.subagentSessionFile, target.state === "failed" ? "failed" : "completed");
1109
+ ctx.ui.notify(`Switched to subagent ${target.agentName}. This resumes the saved subagent session; use /agent parent to return.`, "info");
1110
+ },
1111
+ });
975
1112
  pi.registerShortcut(Key.ctrl("b"), {
976
1113
  description: shortcutDesc("Move foreground subagent to background", "/subagents list"),
977
1114
  handler: async (ctx) => {
@@ -986,13 +1123,31 @@ export default function(pi: ExtensionAPI) {
986
1123
  running.claimed = true;
987
1124
  let jobId: string;
988
1125
  try {
989
- jobId = manager.adoptRunning(
990
- running.agentName,
991
- running.task,
992
- running.cwd,
993
- running.abortController,
994
- running.resultPromise,
995
- );
1126
+ if (running.handle) {
1127
+ jobId = manager.adoptHandle(
1128
+ running.agentName,
1129
+ running.task,
1130
+ running.cwd,
1131
+ running.handle,
1132
+ running.resultPromise,
1133
+ {
1134
+ parentSessionFile: running.parentSessionFile ?? ctx.sessionManager.getSessionFile(),
1135
+ },
1136
+ );
1137
+ } else if (running.abortController) {
1138
+ jobId = manager.adoptRunning(
1139
+ running.agentName,
1140
+ running.task,
1141
+ running.cwd,
1142
+ running.abortController,
1143
+ running.resultPromise,
1144
+ {
1145
+ parentSessionFile: running.parentSessionFile ?? ctx.sessionManager.getSessionFile(),
1146
+ },
1147
+ );
1148
+ } else {
1149
+ throw new Error("Foreground subagent cannot be moved to background (missing runtime handle).");
1150
+ }
996
1151
  } catch (error) {
997
1152
  running.claimed = false;
998
1153
  ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
@@ -1075,6 +1230,18 @@ export default function(pi: ExtensionAPI) {
1075
1230
  const confirmProjectAgents = params.confirmProjectAgents ?? false;
1076
1231
  const cmuxClient = CmuxClient.fromPreferences(loadEffectivePreferences()?.preferences);
1077
1232
  const cmuxSplitsEnabled = cmuxClient.getConfig().splits;
1233
+ const invokingSessionFile = ctx.sessionManager.getSessionFile();
1234
+ const invokingSessionId = ctx.sessionManager.getSessionId();
1235
+ const invokingMetadata = getCurrentSessionSubagentMetadata(invokingSessionFile);
1236
+ const currentSubagentName = invokingMetadata?.subagentName;
1237
+ const trackedAncestry = inProcessSubagentAncestryBySessionId.get(invokingSessionId);
1238
+ const currentAncestry = trackedAncestry ?? (currentSubagentName ? [currentSubagentName] : []);
1239
+ const inferredCurrentDepth = currentSubagentName ? Math.max(currentAncestry.length, 1) : 0;
1240
+ const currentSubagentDepth = Math.max(
1241
+ inProcessSubagentDepthBySessionId.get(invokingSessionId) ?? 0,
1242
+ inferredCurrentDepth,
1243
+ );
1244
+ const nextSubagentDepth = currentSubagentDepth + 1;
1078
1245
 
1079
1246
  // Resolve isolation mode
1080
1247
  const isolationMode = readIsolationMode();
@@ -1094,6 +1261,37 @@ export default function(pi: ExtensionAPI) {
1094
1261
  results,
1095
1262
  });
1096
1263
 
1264
+ const trackInProcessDepth = (
1265
+ started: { handle: SubagentHandle; resultPromise: Promise<SingleResult> },
1266
+ depth: number,
1267
+ ancestry: string[],
1268
+ ) => {
1269
+ const sessionId = started.handle.sessionId;
1270
+ if (!sessionId) return;
1271
+ inProcessSubagentDepthBySessionId.set(sessionId, depth);
1272
+ inProcessSubagentAncestryBySessionId.set(sessionId, ancestry);
1273
+ void started.resultPromise.finally(() => {
1274
+ inProcessSubagentDepthBySessionId.delete(sessionId);
1275
+ inProcessSubagentAncestryBySessionId.delete(sessionId);
1276
+ });
1277
+ };
1278
+
1279
+ const buildChildAncestry = (childAgentName: string): string[] => [...currentAncestry, childAgentName];
1280
+
1281
+ const requestedAgentNames = hasSingle
1282
+ ? [params.agent!]
1283
+ : hasChain
1284
+ ? (params.chain ?? []).map((step) => step.agent)
1285
+ : (params.tasks ?? []).map((task) => task.agent);
1286
+
1287
+ if (currentSubagentName && requestedAgentNames.some((name) => name === currentSubagentName)) {
1288
+ return {
1289
+ content: [{ type: "text", text: `Subagent "${currentSubagentName}" cannot spawn another subagent with the same name as itself.` }],
1290
+ details: makeDetails(hasChain ? "chain" : hasTasks ? "parallel" : "single")([]),
1291
+ isError: true,
1292
+ };
1293
+ }
1294
+
1097
1295
  if (modeCount !== 1) {
1098
1296
  const available = agents.map((a) => `${a.name} (${a.source})`).join(", ") || "none";
1099
1297
  return {
@@ -1115,6 +1313,22 @@ export default function(pi: ExtensionAPI) {
1115
1313
  };
1116
1314
  }
1117
1315
 
1316
+ if (params.background && currentSubagentDepth > 0) {
1317
+ return {
1318
+ content: [{ type: "text", text: "Nested background subagent launches are not supported yet. Run the nested subagent in foreground mode." }],
1319
+ details: makeDetails("single")([]),
1320
+ isError: true,
1321
+ };
1322
+ }
1323
+
1324
+ if (USE_IN_PROCESS_SUBAGENT && nextSubagentDepth > MAX_IN_PROCESS_SUBAGENT_DEPTH) {
1325
+ return {
1326
+ content: [{ type: "text", text: `Max subagent depth (${MAX_IN_PROCESS_SUBAGENT_DEPTH}) exceeded.` }],
1327
+ details: makeDetails(hasChain ? "chain" : hasTasks ? "parallel" : "single")([]),
1328
+ isError: true,
1329
+ };
1330
+ }
1331
+
1118
1332
  if ((agentScope === "project" || agentScope === "both") && confirmProjectAgents && ctx.hasUI) {
1119
1333
  const requestedAgentNames = new Set<string>();
1120
1334
  if (params.chain) for (const step of params.chain) requestedAgentNames.add(step.agent);
@@ -1163,19 +1377,45 @@ export default function(pi: ExtensionAPI) {
1163
1377
  }
1164
1378
  : undefined;
1165
1379
 
1166
- const result = await runSingleAgent(
1167
- ctx.cwd,
1168
- agents,
1169
- step.agent,
1170
- taskWithContext,
1171
- step.cwd,
1172
- i + 1,
1173
- step.model,
1174
- ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
1175
- signal,
1176
- chainUpdate,
1177
- makeDetails("chain"),
1178
- );
1380
+ const result = USE_IN_PROCESS_SUBAGENT
1381
+ ? await (async () => {
1382
+ const started = await startInProcessSingleAgent(
1383
+ ctx.cwd,
1384
+ agents,
1385
+ step.agent,
1386
+ taskWithContext,
1387
+ step.cwd,
1388
+ i + 1,
1389
+ step.model,
1390
+ ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
1391
+ signal,
1392
+ chainUpdate,
1393
+ makeDetails("chain"),
1394
+ invokingSessionFile,
1395
+ nextSubagentDepth,
1396
+ invokingSessionId,
1397
+ currentAncestry,
1398
+ );
1399
+ trackInProcessDepth(started, nextSubagentDepth, buildChildAncestry(step.agent));
1400
+ return started.resultPromise;
1401
+ })()
1402
+ : await runLegacySingleAgent(
1403
+ ctx.cwd,
1404
+ agents,
1405
+ step.agent,
1406
+ taskWithContext,
1407
+ step.cwd,
1408
+ i + 1,
1409
+ step.model,
1410
+ ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
1411
+ signal,
1412
+ chainUpdate,
1413
+ makeDetails("chain"),
1414
+ invokingSessionFile,
1415
+ false,
1416
+ undefined,
1417
+ undefined,
1418
+ );
1179
1419
  results.push(result);
1180
1420
 
1181
1421
  const isError =
@@ -1246,24 +1486,54 @@ export default function(pi: ExtensionAPI) {
1246
1486
  const results = await mapWithConcurrencyLimit(params.tasks, MAX_CONCURRENCY, async (t, index) => {
1247
1487
  const workerId = registerWorker(t.agent, t.task, index, batchSize, batchId);
1248
1488
  const runTask = () =>
1249
- runSingleAgent(
1250
- ctx.cwd,
1251
- agents,
1252
- t.agent,
1253
- t.task,
1254
- t.cwd,
1255
- undefined,
1256
- t.model,
1257
- ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
1258
- signal,
1259
- (partial) => {
1260
- if (partial.details?.results[0]) {
1261
- allResults[index] = partial.details.results[0];
1262
- emitParallelUpdate();
1263
- }
1264
- },
1265
- makeDetails("parallel"),
1266
- );
1489
+ USE_IN_PROCESS_SUBAGENT
1490
+ ? (async () => {
1491
+ const started = await startInProcessSingleAgent(
1492
+ ctx.cwd,
1493
+ agents,
1494
+ t.agent,
1495
+ t.task,
1496
+ t.cwd,
1497
+ undefined,
1498
+ t.model,
1499
+ ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
1500
+ signal,
1501
+ (partial) => {
1502
+ if (partial.details?.results[0]) {
1503
+ allResults[index] = partial.details.results[0];
1504
+ emitParallelUpdate();
1505
+ }
1506
+ },
1507
+ makeDetails("parallel"),
1508
+ invokingSessionFile,
1509
+ nextSubagentDepth,
1510
+ invokingSessionId,
1511
+ currentAncestry,
1512
+ );
1513
+ trackInProcessDepth(started, nextSubagentDepth, buildChildAncestry(t.agent));
1514
+ return started.resultPromise;
1515
+ })()
1516
+ : runLegacySingleAgent(
1517
+ ctx.cwd,
1518
+ agents,
1519
+ t.agent,
1520
+ t.task,
1521
+ t.cwd,
1522
+ undefined,
1523
+ t.model,
1524
+ ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
1525
+ signal,
1526
+ (partial) => {
1527
+ if (partial.details?.results[0]) {
1528
+ allResults[index] = partial.details.results[0];
1529
+ emitParallelUpdate();
1530
+ }
1531
+ },
1532
+ makeDetails("parallel"),
1533
+ invokingSessionFile,
1534
+ false,
1535
+ undefined,
1536
+ );
1267
1537
  let result = await runTask();
1268
1538
 
1269
1539
  // Auto-retry failed tasks (likely API rate limit or transient error)
@@ -1330,36 +1600,130 @@ export default function(pi: ExtensionAPI) {
1330
1600
 
1331
1601
  let jobId: string;
1332
1602
  try {
1333
- jobId = runSubagentInBackground(
1334
- manager,
1335
- agents,
1336
- params.agent,
1337
- params.task,
1338
- params.cwd,
1339
- params.model,
1340
- { defaultCwd: ctx.cwd, model: ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined },
1341
- async (bgSignal) => {
1342
- const result = await runSingleAgent(
1343
- ctx.cwd,
1344
- agents,
1345
- params.agent!,
1346
- params.task!,
1347
- params.cwd,
1348
- undefined,
1349
- params.model,
1350
- ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
1351
- bgSignal,
1352
- undefined, // no streaming updates for background jobs
1353
- makeDetails("single"),
1354
- );
1355
- return {
1356
- exitCode: result.exitCode,
1357
- finalOutput: getFinalOutput(result.messages),
1603
+ if (USE_IN_PROCESS_SUBAGENT) {
1604
+ const started = await startInProcessSingleAgent(
1605
+ ctx.cwd,
1606
+ agents,
1607
+ params.agent,
1608
+ params.task,
1609
+ params.cwd,
1610
+ undefined,
1611
+ params.model,
1612
+ ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
1613
+ undefined,
1614
+ undefined,
1615
+ makeDetails("single"),
1616
+ invokingSessionFile,
1617
+ nextSubagentDepth,
1618
+ invokingSessionId,
1619
+ currentAncestry,
1620
+ );
1621
+ trackInProcessDepth(started, nextSubagentDepth, buildChildAncestry(params.agent));
1622
+
1623
+ const effectiveCwd = params.cwd ?? ctx.cwd;
1624
+ jobId = manager.adoptHandle(
1625
+ params.agent,
1626
+ params.task,
1627
+ effectiveCwd,
1628
+ started.handle,
1629
+ started.resultPromise.then((result) => ({
1630
+ summary: (getFinalOutput(result.messages) || "(no output)").slice(0, 300),
1358
1631
  stderr: result.stderr,
1632
+ exitCode: result.exitCode,
1359
1633
  model: result.model,
1360
- };
1361
- },
1362
- );
1634
+ sessionFile: result.sessionFile,
1635
+ parentSessionFile: result.parentSessionFile,
1636
+ })),
1637
+ {
1638
+ parentSessionFile: invokingSessionFile,
1639
+ model: bgInferredModel,
1640
+ },
1641
+ );
1642
+ } else {
1643
+ jobId = runSubagentInBackground(
1644
+ manager,
1645
+ agents,
1646
+ params.agent,
1647
+ params.task,
1648
+ params.cwd,
1649
+ params.model,
1650
+ { defaultCwd: ctx.cwd, model: ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, parentSessionFile: invokingSessionFile },
1651
+ async (bgSignal) => {
1652
+ let liveSessionFile: string | undefined;
1653
+ let liveRuntime: LiveSubagentRuntime | undefined;
1654
+ const result = await runLegacySingleAgent(
1655
+ ctx.cwd,
1656
+ agents,
1657
+ params.agent!,
1658
+ params.task!,
1659
+ params.cwd,
1660
+ undefined,
1661
+ params.model,
1662
+ ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
1663
+ bgSignal,
1664
+ undefined, // no streaming updates for background jobs
1665
+ makeDetails("single"),
1666
+ invokingSessionFile,
1667
+ true,
1668
+ (info) => {
1669
+ if (!invokingSessionFile || !info.sessionFile) return;
1670
+ upsertAgentSessionLink(
1671
+ params.agent!,
1672
+ params.task!,
1673
+ invokingSessionFile,
1674
+ info.sessionFile,
1675
+ "running",
1676
+ );
1677
+ liveSessionFile = info.sessionFile;
1678
+ if (liveRuntime) {
1679
+ liveRuntime.sessionFile = info.sessionFile;
1680
+ liveRuntime.parentSessionFile = info.parentSessionFile ?? invokingSessionFile;
1681
+ liveRuntimeBySessionFile.set(info.sessionFile, liveRuntime);
1682
+ }
1683
+ },
1684
+ (event, partial) => {
1685
+ const sessionFile = partial.sessionFile;
1686
+ if (!sessionFile || activeSessionFileForUi !== sessionFile) return;
1687
+ if (event?.type === "message_update" && event.assistantMessageEvent?.type === "text_delta") {
1688
+ const delta = String(event.assistantMessageEvent.delta ?? "");
1689
+ if (delta) pushLiveStreamDelta(sessionFile, delta);
1690
+ }
1691
+ if (event?.type === "message_end") {
1692
+ flushLiveStream(sessionFile);
1693
+ }
1694
+ },
1695
+ {
1696
+ onStart: (control) => {
1697
+ if (!control.sendPrompt || !control.sendSteer || !control.sendFollowUp || !control.isBusy) return;
1698
+ liveRuntime = {
1699
+ sessionFile: liveSessionFile,
1700
+ parentSessionFile: invokingSessionFile,
1701
+ agentName: params.agent!,
1702
+ isBusy: control.isBusy,
1703
+ sendPrompt: control.sendPrompt,
1704
+ sendSteer: control.sendSteer,
1705
+ sendFollowUp: control.sendFollowUp,
1706
+ };
1707
+ if (liveSessionFile) {
1708
+ liveRuntimeBySessionFile.set(liveSessionFile, liveRuntime);
1709
+ }
1710
+ },
1711
+ onFinish: () => {
1712
+ if (liveSessionFile) liveRuntimeBySessionFile.delete(liveSessionFile);
1713
+ },
1714
+ },
1715
+ );
1716
+ return {
1717
+ exitCode: result.exitCode,
1718
+ finalOutput: getFinalOutput(result.messages),
1719
+ stderr: result.stderr,
1720
+ model: result.model,
1721
+ sessionFile: result.sessionFile,
1722
+ parentSessionFile: result.parentSessionFile,
1723
+ };
1724
+ },
1725
+ );
1726
+ }
1363
1727
  } catch (err) {
1364
1728
  return {
1365
1729
  content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }],
@@ -1387,31 +1751,170 @@ export default function(pi: ExtensionAPI) {
1387
1751
  isolation = await createIsolation(effectiveCwd, taskId, isolationMode);
1388
1752
  }
1389
1753
 
1390
- const result = await runSingleAgent(
1391
- ctx.cwd,
1392
- agents,
1393
- params.agent,
1394
- params.task,
1395
- isolation ? isolation.workDir : params.cwd,
1396
- undefined,
1397
- params.model,
1398
- ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
1399
- signal,
1400
- onUpdate,
1401
- makeDetails("single"),
1402
- !isolation
1403
- ? {
1404
- onStart: (control) => {
1405
- activeForegroundSubagent = { ...control, claimed: false };
1406
- ctx.ui.setStatus(foregroundSubagentStatusKey, foregroundSubagentHint);
1407
- },
1408
- onFinish: () => {
1409
- activeForegroundSubagent = null;
1410
- ctx.ui.setStatus(foregroundSubagentStatusKey, undefined);
1411
- },
1412
- }
1413
- : undefined,
1414
- );
1754
+ let result: SingleResult;
1755
+ if (USE_IN_PROCESS_SUBAGENT && !isolation) {
1756
+ const started = await startInProcessSingleAgent(
1757
+ ctx.cwd,
1758
+ agents,
1759
+ params.agent,
1760
+ params.task,
1761
+ params.cwd,
1762
+ undefined,
1763
+ params.model,
1764
+ ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
1765
+ signal,
1766
+ onUpdate,
1767
+ makeDetails("single"),
1768
+ invokingSessionFile,
1769
+ nextSubagentDepth,
1770
+ invokingSessionId,
1771
+ currentAncestry,
1772
+ );
1773
+ trackInProcessDepth(started, nextSubagentDepth, buildChildAncestry(params.agent));
1774
+
1775
+ const effectiveRunCwd = params.cwd ?? ctx.cwd;
1776
+ let releaseToBackground: ((jobId: string) => void) | undefined;
1777
+ const movedToBackground = new Promise<string>((resolve) => {
1778
+ releaseToBackground = resolve;
1779
+ });
1780
+
1781
+ activeForegroundSubagent = {
1782
+ claimed: false,
1783
+ agentName: params.agent,
1784
+ task: params.task,
1785
+ cwd: effectiveRunCwd,
1786
+ parentSessionFile: invokingSessionFile,
1787
+ handle: started.handle,
1788
+ resultPromise: started.resultPromise.then((done) => ({
1789
+ summary: getFinalOutput(done.messages) || "(no output)",
1790
+ stderr: done.stderr,
1791
+ exitCode: done.exitCode,
1792
+ model: done.model,
1793
+ sessionFile: done.sessionFile,
1794
+ parentSessionFile: done.parentSessionFile,
1795
+ })),
1796
+ adoptToBackground: (jobId: string) => {
1797
+ if (!releaseToBackground) return false;
1798
+ releaseToBackground(jobId);
1799
+ releaseToBackground = undefined;
1800
+ return true;
1801
+ },
1802
+ sendPrompt: started.handle.prompt,
1803
+ sendSteer: started.handle.steer,
1804
+ sendFollowUp: started.handle.followUp,
1805
+ isBusy: started.handle.isBusy,
1806
+ };
1807
+ ctx.ui.setStatus(foregroundSubagentStatusKey, foregroundSubagentHint);
1808
+
1809
+ const winner = await Promise.race([
1810
+ started.resultPromise.then((done) => ({ type: "done" as const, done })),
1811
+ movedToBackground.then((jobId) => ({ type: "background" as const, jobId })),
1812
+ ]);
1813
+
1814
+ if (winner.type === "background") {
1815
+ activeForegroundSubagent = null;
1816
+ ctx.ui.setStatus(foregroundSubagentStatusKey, undefined);
1817
+ result = {
1818
+ ...started.currentResult,
1819
+ backgroundJobId: winner.jobId,
1820
+ };
1821
+ } else {
1822
+ activeForegroundSubagent = null;
1823
+ ctx.ui.setStatus(foregroundSubagentStatusKey, undefined);
1824
+ result = winner.done;
1825
+ }
1826
+ } else {
1827
+ let liveSessionFile: string | undefined;
1828
+ let liveRuntime: LiveSubagentRuntime | undefined;
1829
+ result = await runLegacySingleAgent(
1830
+ ctx.cwd,
1831
+ agents,
1832
+ params.agent,
1833
+ params.task,
1834
+ isolation ? isolation.workDir : params.cwd,
1835
+ undefined,
1836
+ params.model,
1837
+ ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
1838
+ signal,
1839
+ onUpdate,
1840
+ makeDetails("single"),
1841
+ invokingSessionFile,
1842
+ !isolation,
1843
+ !isolation
1844
+ ? (info) => {
1845
+ if (!invokingSessionFile || !info.sessionFile) return;
1846
+ upsertAgentSessionLink(
1847
+ params.agent!,
1848
+ params.task!,
1849
+ invokingSessionFile,
1850
+ info.sessionFile,
1851
+ "running",
1852
+ );
1853
+ liveSessionFile = info.sessionFile;
1854
+ if (liveRuntime) {
1855
+ liveRuntime.sessionFile = info.sessionFile;
1856
+ liveRuntime.parentSessionFile = info.parentSessionFile ?? invokingSessionFile;
1857
+ liveRuntimeBySessionFile.set(info.sessionFile, liveRuntime);
1858
+ }
1859
+ }
1860
+ : undefined,
1861
+ !isolation
1862
+ ? (event, partial) => {
1863
+ const sessionFile = partial.sessionFile;
1864
+ if (!sessionFile || activeSessionFileForUi !== sessionFile) return;
1865
+ if (event?.type === "message_update" && event.assistantMessageEvent?.type === "text_delta") {
1866
+ const delta = String(event.assistantMessageEvent.delta ?? "");
1867
+ if (delta) pushLiveStreamDelta(sessionFile, delta);
1868
+ }
1869
+ if (event?.type === "message_end") {
1870
+ flushLiveStream(sessionFile);
1871
+ }
1872
+ }
1873
+ : undefined,
1874
+ !isolation
1875
+ ? {
1876
+ onStart: (control) => {
1877
+ activeForegroundSubagent = { ...control, claimed: false };
1878
+ ctx.ui.setStatus(foregroundSubagentStatusKey, foregroundSubagentHint);
1879
+
1880
+ if (!control.sendPrompt || !control.sendSteer || !control.sendFollowUp || !control.isBusy) return;
1881
+ liveRuntime = {
1882
+ sessionFile: liveSessionFile,
1883
+ parentSessionFile: invokingSessionFile,
1884
+ agentName: params.agent!,
1885
+ isBusy: control.isBusy,
1886
+ sendPrompt: control.sendPrompt,
1887
+ sendSteer: control.sendSteer,
1888
+ sendFollowUp: control.sendFollowUp,
1889
+ };
1890
+ if (liveSessionFile && liveRuntime) {
1891
+ liveRuntimeBySessionFile.set(liveSessionFile, liveRuntime);
1892
+ }
1893
+ },
1894
+ onFinish: () => {
1895
+ activeForegroundSubagent = null;
1896
+ ctx.ui.setStatus(foregroundSubagentStatusKey, undefined);
1897
+ if (liveSessionFile) liveRuntimeBySessionFile.delete(liveSessionFile);
1898
+ },
1899
+ }
1900
+ : undefined,
1901
+ );
1902
+ }
1903
+
1904
+ if (result.sessionFile && invokingSessionFile) {
1905
+ const existingParent = parentSessionByChild.get(result.sessionFile);
1906
+ if (!existingParent) {
1907
+ registerAgentSessionLink({
1908
+ agentName: result.agent,
1909
+ task: result.task,
1910
+ parentSessionFile: invokingSessionFile,
1911
+ subagentSessionFile: result.sessionFile,
1912
+ state: result.exitCode === 0 ? "completed" : "failed",
1913
+ });
1914
+ } else {
1915
+ updateAgentSessionLinkState(result.sessionFile, result.exitCode === 0 ? "completed" : "failed");
1916
+ }
1917
+ }
1415
1918
 
1416
1919
  if (result.backgroundJobId) {
1417
1920
  return {
@@ -1429,11 +1932,12 @@ export default function(pi: ExtensionAPI) {
1429
1932
  }
1430
1933
 
1431
1934
  const isError = result.exitCode !== 0 || result.stopReason === "error" || result.stopReason === "aborted";
1935
+ const agentSwitchHint = result.sessionFile ? "\n\nTip: run `/agent` to switch focus to this subagent session." : "";
1432
1936
  if (isError) {
1433
1937
  const errorMsg =
1434
1938
  result.errorMessage || result.stderr || getFinalOutput(result.messages) || "(no output)";
1435
1939
  return {
1436
- content: [{ type: "text", text: `Agent ${result.stopReason || "failed"}: ${errorMsg}` }],
1940
+ content: [{ type: "text", text: `Agent ${result.stopReason || "failed"}: ${errorMsg}${agentSwitchHint}` }],
1437
1941
  details: makeDetails("single")([result]),
1438
1942
  isError: true,
1439
1943
  };
@@ -1444,6 +1948,7 @@ export default function(pi: ExtensionAPI) {
1444
1948
  if (mergeResult && !mergeResult.success) {
1445
1949
  outputText += `\n\n⚠ Patch merge failed: ${mergeResult.error || "unknown error"}`;
1446
1950
  }
1951
+ if (agentSwitchHint) outputText += agentSwitchHint;
1447
1952
  return {
1448
1953
  content: [{ type: "text", text: outputText }],
1449
1954
  details: makeDetails("single")([result]),