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
@@ -11,32 +11,170 @@
11
11
  *
12
12
  * Uses JSON mode to capture structured output from subagents.
13
13
  */
14
- import { spawn, execFileSync } from "node:child_process";
15
14
  import * as crypto from "node:crypto";
16
15
  import * as fs from "node:fs";
17
16
  import * as os from "node:os";
18
17
  import * as path from "node:path";
19
18
  import { StringEnum } from "@gsd/pi-ai";
20
- import { getAgentDir, getMarkdownTheme, } from "@gsd/pi-coding-agent";
19
+ import { getMarkdownTheme, } from "@gsd/pi-coding-agent";
21
20
  import { Container, Key, Markdown, Spacer, Text } from "@gsd/pi-tui";
22
21
  import { Type } from "@sinclair/typebox";
23
22
  import { formatTokenCount, shortcutDesc } from "../shared/mod.js";
24
23
  import { discoverAgents } from "./agents.js";
25
- import { buildSubagentProcessArgs, getBundledExtensionPathsFromEnv } from "./launch-helpers.js";
26
24
  import { createIsolation, mergeDeltaPatches, readIsolationMode, } from "./isolation.js";
27
25
  import { registerWorker, updateWorker } from "./worker-registry.js";
28
- import { handleSubagentPermissionRequest, isSubagentPermissionRequest } from "./approval-proxy.js";
29
26
  import { resolveConfiguredSubagentModel } from "./configured-model.js";
30
- import { normalizeSubagentModel, resolveSubagentModel, } from "./model-resolution.js";
27
+ import { resolveSubagentModel } from "./model-resolution.js";
31
28
  import { loadEffectivePreferences } from "../shared/preferences.js";
29
+ import { readBudgetSubagentModelFromSettings, runLegacySingleAgent, stopLegacySubagents, } from "./legacy-runner.js";
30
+ import { MAX_IN_PROCESS_SUBAGENT_DEPTH, startInProcessSingleAgent, } from "./in-process-runner.js";
32
31
  import { CmuxClient } from "../cmux/index.js";
33
32
  import { BackgroundJobManager } from "./background-job-manager.js";
34
33
  import { runSubagentInBackground } from "./background-runner.js";
34
+ import { showAgentSwitcher } from "./agent-switcher-component.js";
35
+ import { buildAgentSwitchTargets, } from "./agent-switcher-model.js";
35
36
  const MAX_PARALLEL_TASKS = 8;
36
37
  const MAX_CONCURRENCY = 4;
37
38
  const COLLAPSED_ITEM_COUNT = 10;
38
39
  const DEFAULT_AWAIT_SUBAGENT_TIMEOUT_SECONDS = 120;
39
- const liveSubagentProcesses = new Set();
40
+ const USE_IN_PROCESS_SUBAGENT = process.env.LSD_SUBAGENT_IN_PROCESS !== "0";
41
+ const agentSessionLinksById = new Map();
42
+ const agentSessionIdsByParent = new Map();
43
+ const parentSessionByChild = new Map();
44
+ const liveRuntimeBySessionFile = new Map();
45
+ const inProcessSubagentDepthBySessionId = new Map();
46
+ const inProcessSubagentAncestryBySessionId = new Map();
47
+ let agentSessionLinkCounter = 0;
48
+ function listSessionFiles(sessionDir) {
49
+ if (!fs.existsSync(sessionDir))
50
+ return [];
51
+ try {
52
+ return fs
53
+ .readdirSync(sessionDir)
54
+ .filter((name) => name.endsWith(".jsonl"))
55
+ .map((name) => path.join(sessionDir, name));
56
+ }
57
+ catch {
58
+ return [];
59
+ }
60
+ }
61
+ function registerAgentSessionLink(link) {
62
+ const now = Date.now();
63
+ const id = `agent-${++agentSessionLinkCounter}`;
64
+ const full = { ...link, id, createdAt: now, updatedAt: now };
65
+ agentSessionLinksById.set(id, full);
66
+ const list = agentSessionIdsByParent.get(link.parentSessionFile) ?? [];
67
+ list.push(id);
68
+ agentSessionIdsByParent.set(link.parentSessionFile, list);
69
+ parentSessionByChild.set(link.subagentSessionFile, link.parentSessionFile);
70
+ return full;
71
+ }
72
+ function updateAgentSessionLinkState(subagentSessionFile, state) {
73
+ for (const link of agentSessionLinksById.values()) {
74
+ if (link.subagentSessionFile === subagentSessionFile) {
75
+ link.state = state;
76
+ link.updatedAt = Date.now();
77
+ return;
78
+ }
79
+ }
80
+ }
81
+ function upsertAgentSessionLink(agentName, task, parentSessionFile, subagentSessionFile, state) {
82
+ const existingParent = parentSessionByChild.get(subagentSessionFile);
83
+ if (!existingParent) {
84
+ registerAgentSessionLink({
85
+ agentName,
86
+ task,
87
+ parentSessionFile,
88
+ subagentSessionFile,
89
+ state,
90
+ });
91
+ return;
92
+ }
93
+ updateAgentSessionLinkState(subagentSessionFile, state);
94
+ }
95
+ function getAgentSessionLinksForParent(parentSessionFile) {
96
+ const ids = agentSessionIdsByParent.get(parentSessionFile) ?? [];
97
+ return ids
98
+ .map((id) => agentSessionLinksById.get(id))
99
+ .filter((entry) => Boolean(entry))
100
+ .sort((a, b) => b.updatedAt - a.updatedAt);
101
+ }
102
+ function readSessionHeader(sessionFile) {
103
+ try {
104
+ const content = fs.readFileSync(sessionFile, "utf-8");
105
+ const firstLine = content.split("\n").find((line) => line.trim().length > 0);
106
+ if (!firstLine)
107
+ return null;
108
+ const parsed = JSON.parse(firstLine);
109
+ if (!parsed || parsed.type !== "session")
110
+ return null;
111
+ return {
112
+ parentSession: typeof parsed.parentSession === "string" ? parsed.parentSession : undefined,
113
+ subagentName: typeof parsed.subagentName === "string" ? parsed.subagentName : undefined,
114
+ subagentTask: typeof parsed.subagentTask === "string" ? parsed.subagentTask : undefined,
115
+ subagentSystemPrompt: typeof parsed.subagentSystemPrompt === "string" ? parsed.subagentSystemPrompt : undefined,
116
+ subagentTools: Array.isArray(parsed.subagentTools)
117
+ ? parsed.subagentTools.filter((tool) => typeof tool === "string")
118
+ : undefined,
119
+ };
120
+ }
121
+ catch {
122
+ return null;
123
+ }
124
+ }
125
+ function backfillAgentSessionLinksForParent(parentSessionFile, sessionDir) {
126
+ for (const sessionFile of listSessionFiles(sessionDir)) {
127
+ if (sessionFile === parentSessionFile)
128
+ continue;
129
+ const header = readSessionHeader(sessionFile);
130
+ if (header?.parentSession !== parentSessionFile)
131
+ continue;
132
+ const existingParent = parentSessionByChild.get(sessionFile);
133
+ if (!existingParent) {
134
+ registerAgentSessionLink({
135
+ agentName: header.subagentName ?? "subagent",
136
+ task: header.subagentTask ?? "Recovered from persisted session lineage",
137
+ parentSessionFile,
138
+ subagentSessionFile: sessionFile,
139
+ state: "completed",
140
+ });
141
+ }
142
+ }
143
+ return getAgentSessionLinksForParent(parentSessionFile);
144
+ }
145
+ function formatSwitchTargetSummary(target) {
146
+ const current = target.isCurrent ? " (current)" : "";
147
+ if (target.kind === "parent") {
148
+ return `● parent — main session${current}`;
149
+ }
150
+ const icon = target.state === "running" ? "▶" : target.state === "failed" ? "✗" : "✓";
151
+ return `${icon} ${target.agentName} — ${target.taskPreview}${current}`;
152
+ }
153
+ function buildSwitchTargetsForParent(parentSessionFile, currentSessionFile, currentCwd, trackedLinks, runningJobs) {
154
+ return buildAgentSwitchTargets({
155
+ currentSessionFile,
156
+ rootParentSessionFile: parentSessionFile,
157
+ currentCwd,
158
+ trackedLinks: trackedLinks.map((link) => ({
159
+ id: link.id,
160
+ agentName: link.agentName,
161
+ task: link.task,
162
+ parentSessionFile: link.parentSessionFile,
163
+ subagentSessionFile: link.subagentSessionFile,
164
+ updatedAt: link.updatedAt,
165
+ state: link.state,
166
+ })),
167
+ runningJobs: runningJobs.map((job) => ({
168
+ id: job.id,
169
+ agentName: job.agentName,
170
+ task: job.task,
171
+ startedAt: job.startedAt,
172
+ parentSessionFile: job.parentSessionFile,
173
+ sessionFile: job.sessionFile,
174
+ cwd: job.cwd,
175
+ })),
176
+ });
177
+ }
40
178
  const AwaitSubagentParams = Type.Object({
41
179
  jobs: Type.Optional(Type.Array(Type.String(), {
42
180
  description: "Subagent job IDs to wait for. Omit to wait for the next running background subagent.",
@@ -47,35 +185,7 @@ const AwaitSubagentParams = Type.Object({
47
185
  })),
48
186
  });
49
187
  export async function stopLiveSubagents() {
50
- const active = Array.from(liveSubagentProcesses);
51
- if (active.length === 0)
52
- return;
53
- for (const proc of active) {
54
- try {
55
- proc.kill("SIGTERM");
56
- }
57
- catch {
58
- /* ignore */
59
- }
60
- }
61
- await Promise.all(active.map((proc) => new Promise((resolve) => {
62
- const done = () => resolve();
63
- const timer = setTimeout(done, 500);
64
- proc.once("exit", () => {
65
- clearTimeout(timer);
66
- resolve();
67
- });
68
- })));
69
- for (const proc of active) {
70
- if (proc.exitCode === null) {
71
- try {
72
- proc.kill("SIGKILL");
73
- }
74
- catch {
75
- /* ignore */
76
- }
77
- }
78
- }
188
+ await stopLegacySubagents();
79
189
  }
80
190
  function formatBackgroundSubagentResults(jobs) {
81
191
  if (jobs.length === 0)
@@ -293,308 +403,6 @@ async function mapWithConcurrencyLimit(items, concurrency, fn) {
293
403
  await Promise.all(workers);
294
404
  return results;
295
405
  }
296
- function writePromptToTempFile(agentName, prompt) {
297
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
298
- const safeName = agentName.replace(/[^\w.-]+/g, "_");
299
- const filePath = path.join(tmpDir, `prompt-${safeName}.md`);
300
- fs.writeFileSync(filePath, prompt, { encoding: "utf-8", mode: 0o600 });
301
- return { dir: tmpDir, filePath };
302
- }
303
- function readBudgetSubagentModelFromSettings() {
304
- try {
305
- const settingsPath = path.join(getAgentDir(), "settings.json");
306
- if (!fs.existsSync(settingsPath))
307
- return undefined;
308
- const raw = fs.readFileSync(settingsPath, "utf-8");
309
- const parsed = JSON.parse(raw);
310
- return typeof parsed.budgetSubagentModel === "string"
311
- ? normalizeSubagentModel(parsed.budgetSubagentModel)
312
- : undefined;
313
- }
314
- catch {
315
- return undefined;
316
- }
317
- }
318
- function resolveSubagentCliPath(defaultCwd) {
319
- const candidates = [process.env.GSD_BIN_PATH, process.env.LSD_BIN_PATH, process.argv[1]]
320
- .map((value) => value?.trim())
321
- .filter((value) => Boolean(value && value !== "undefined"));
322
- for (const candidate of candidates) {
323
- if (path.isAbsolute(candidate) && fs.existsSync(candidate))
324
- return candidate;
325
- }
326
- const cwdCandidates = [path.join(defaultCwd, "dist", "loader.js"), path.join(defaultCwd, "scripts", "dev-cli.js")];
327
- for (const candidate of cwdCandidates) {
328
- if (fs.existsSync(candidate))
329
- return candidate;
330
- }
331
- for (const binName of ["lsd", "gsd"]) {
332
- try {
333
- const resolved = execFileSync("which", [binName], { encoding: "utf-8" }).trim();
334
- if (resolved)
335
- return resolved;
336
- }
337
- catch {
338
- /* ignore */
339
- }
340
- }
341
- return null;
342
- }
343
- function processSubagentEventLine(line, currentResult, emitUpdate, proc) {
344
- if (!line.trim())
345
- return false;
346
- let event;
347
- try {
348
- event = JSON.parse(line);
349
- }
350
- catch {
351
- return false;
352
- }
353
- if (proc && isSubagentPermissionRequest(event)) {
354
- void handleSubagentPermissionRequest(event, proc);
355
- return false;
356
- }
357
- if ((event.type === "message_end" || event.type === "turn_end") && event.message) {
358
- const msg = event.message;
359
- currentResult.messages.push(msg);
360
- if (msg.role === "assistant") {
361
- currentResult.usage.turns++;
362
- const usage = msg.usage;
363
- if (usage) {
364
- currentResult.usage.input += usage.input || 0;
365
- currentResult.usage.output += usage.output || 0;
366
- currentResult.usage.cacheRead += usage.cacheRead || 0;
367
- currentResult.usage.cacheWrite += usage.cacheWrite || 0;
368
- currentResult.usage.cost += usage.cost?.total || 0;
369
- currentResult.usage.contextTokens = usage.totalTokens || 0;
370
- }
371
- if (msg.model && (!currentResult.model || msg.model.includes("/")))
372
- currentResult.model = msg.model;
373
- if (msg.stopReason)
374
- currentResult.stopReason = msg.stopReason;
375
- if (msg.errorMessage)
376
- currentResult.errorMessage = msg.errorMessage;
377
- }
378
- emitUpdate();
379
- }
380
- if (event.type === "tool_result_end" && event.message) {
381
- currentResult.messages.push(event.message);
382
- emitUpdate();
383
- }
384
- return event.type === "agent_end";
385
- }
386
- async function waitForFile(filePath, signal, timeoutMs = 30 * 60 * 1000) {
387
- const started = Date.now();
388
- while (Date.now() - started < timeoutMs) {
389
- if (signal?.aborted)
390
- return false;
391
- if (fs.existsSync(filePath))
392
- return true;
393
- await new Promise((resolve) => setTimeout(resolve, 150));
394
- }
395
- return false;
396
- }
397
- async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, modelOverride, parentModel, signal, onUpdate, makeDetails, foregroundHooks) {
398
- const agent = agents.find((a) => a.name === agentName);
399
- if (!agent) {
400
- const available = agents.map((a) => `"${a.name}"`).join(", ") || "none";
401
- return {
402
- agent: agentName,
403
- agentSource: "unknown",
404
- task,
405
- exitCode: 1,
406
- messages: [],
407
- stderr: `Unknown agent: "${agentName}". Available agents: ${available}.`,
408
- usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
409
- step,
410
- };
411
- }
412
- let tmpPromptDir = null;
413
- let tmpPromptPath = null;
414
- const preferences = loadEffectivePreferences()?.preferences;
415
- const settingsBudgetModel = readBudgetSubagentModelFromSettings();
416
- const resolvedModel = resolveConfiguredSubagentModel(agent, preferences, settingsBudgetModel);
417
- const inferredModel = resolveSubagentModel({ name: agent.name, model: resolvedModel }, { overrideModel: modelOverride, parentModel });
418
- const currentResult = {
419
- agent: agentName,
420
- agentSource: agent.source,
421
- task,
422
- exitCode: 0,
423
- messages: [],
424
- stderr: "",
425
- usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
426
- model: inferredModel,
427
- step,
428
- };
429
- const emitUpdate = () => {
430
- if (onUpdate) {
431
- onUpdate({
432
- content: [{ type: "text", text: getFinalOutput(currentResult.messages) || "(running...)" }],
433
- details: makeDetails([currentResult]),
434
- });
435
- }
436
- };
437
- let wasAborted = false;
438
- let deferTempPromptCleanup = false;
439
- let tempPromptCleanupDone = false;
440
- const cleanupTempPromptFiles = () => {
441
- if (tempPromptCleanupDone)
442
- return;
443
- tempPromptCleanupDone = true;
444
- if (tmpPromptPath)
445
- try {
446
- fs.unlinkSync(tmpPromptPath);
447
- }
448
- catch {
449
- /* ignore */
450
- }
451
- if (tmpPromptDir)
452
- try {
453
- fs.rmdirSync(tmpPromptDir);
454
- }
455
- catch {
456
- /* ignore */
457
- }
458
- };
459
- try {
460
- if (agent.systemPrompt.trim()) {
461
- const tmp = writePromptToTempFile(agent.name, agent.systemPrompt);
462
- tmpPromptDir = tmp.dir;
463
- tmpPromptPath = tmp.filePath;
464
- }
465
- const args = buildSubagentProcessArgs(agent, task, tmpPromptPath, inferredModel);
466
- const exitCode = await new Promise((resolve) => {
467
- const bundledPaths = getBundledExtensionPathsFromEnv();
468
- const extensionArgs = bundledPaths.flatMap((p) => ["--extension", p]);
469
- const cliPath = resolveSubagentCliPath(cwd ?? defaultCwd);
470
- if (!cliPath) {
471
- currentResult.stderr += "Unable to resolve LSD/GSD CLI path for subagent launch.";
472
- resolve(1);
473
- return;
474
- }
475
- const proc = spawn(process.execPath, [cliPath, ...extensionArgs, ...args], { cwd: cwd ?? defaultCwd, shell: false, stdio: ["pipe", "pipe", "pipe"] });
476
- // Keep stdin open so approval/classifier responses can be proxied back
477
- // into the child process. Closing it here can leave the subagent stuck
478
- // after its first turn when it requests permission for a tool call.
479
- liveSubagentProcesses.add(proc);
480
- let buffer = "";
481
- let completionSeen = false;
482
- let resolved = false;
483
- let foregroundReleased = false;
484
- const procAbortController = new AbortController();
485
- let resolveBackgroundResult;
486
- let rejectBackgroundResult;
487
- const backgroundResultPromise = new Promise((resolveBg, rejectBg) => {
488
- resolveBackgroundResult = resolveBg;
489
- rejectBackgroundResult = rejectBg;
490
- });
491
- const finishForeground = (code) => {
492
- if (resolved)
493
- return;
494
- resolved = true;
495
- resolve(code);
496
- };
497
- const adoptToBackground = (jobId) => {
498
- if (resolved || foregroundReleased)
499
- return false;
500
- foregroundReleased = true;
501
- deferTempPromptCleanup = true;
502
- currentResult.backgroundJobId = jobId;
503
- finishForeground(0);
504
- return true;
505
- };
506
- backgroundResultPromise.finally(() => {
507
- if (deferTempPromptCleanup)
508
- cleanupTempPromptFiles();
509
- });
510
- foregroundHooks?.onStart?.({
511
- agentName,
512
- task,
513
- cwd: cwd ?? defaultCwd,
514
- abortController: procAbortController,
515
- resultPromise: backgroundResultPromise,
516
- adoptToBackground,
517
- });
518
- proc.stdout.on("data", (data) => {
519
- buffer += data.toString();
520
- const lines = buffer.split("\n");
521
- buffer = lines.pop() || "";
522
- for (const line of lines) {
523
- if (processSubagentEventLine(line, currentResult, emitUpdate, proc)) {
524
- completionSeen = true;
525
- try {
526
- proc.kill("SIGTERM");
527
- }
528
- catch {
529
- /* ignore */
530
- }
531
- }
532
- }
533
- });
534
- proc.stderr.on("data", (data) => {
535
- currentResult.stderr += data.toString();
536
- });
537
- proc.on("close", (code) => {
538
- liveSubagentProcesses.delete(proc);
539
- if (buffer.trim()) {
540
- const completedOnFlush = processSubagentEventLine(buffer, currentResult, emitUpdate, proc);
541
- completionSeen = completionSeen || completedOnFlush;
542
- }
543
- const finalExitCode = completionSeen && (code === null || code === 143 || code === 15) ? 0 : (code ?? 0);
544
- currentResult.exitCode = finalExitCode;
545
- resolveBackgroundResult?.({
546
- summary: getFinalOutput(currentResult.messages),
547
- stderr: currentResult.stderr,
548
- exitCode: finalExitCode,
549
- model: currentResult.model,
550
- });
551
- foregroundHooks?.onFinish?.();
552
- finishForeground(finalExitCode);
553
- });
554
- proc.on("error", (error) => {
555
- liveSubagentProcesses.delete(proc);
556
- rejectBackgroundResult?.(error);
557
- foregroundHooks?.onFinish?.();
558
- finishForeground(1);
559
- });
560
- const killProc = () => {
561
- wasAborted = true;
562
- procAbortController.abort();
563
- proc.kill("SIGTERM");
564
- setTimeout(() => {
565
- if (!proc.killed)
566
- proc.kill("SIGKILL");
567
- }, 5000);
568
- };
569
- if (signal) {
570
- if (signal.aborted)
571
- killProc();
572
- else
573
- signal.addEventListener("abort", killProc, { once: true });
574
- }
575
- if (procAbortController.signal.aborted) {
576
- killProc();
577
- }
578
- else {
579
- procAbortController.signal.addEventListener("abort", () => {
580
- proc.kill("SIGTERM");
581
- setTimeout(() => {
582
- if (!proc.killed)
583
- proc.kill("SIGKILL");
584
- }, 5000);
585
- }, { once: true });
586
- }
587
- });
588
- currentResult.exitCode = exitCode;
589
- if (wasAborted)
590
- throw new Error("Subagent was aborted");
591
- return currentResult;
592
- }
593
- finally {
594
- if (!deferTempPromptCleanup)
595
- cleanupTempPromptFiles();
596
- }
597
- }
598
406
  const TaskItem = Type.Object({
599
407
  agent: Type.String({ description: "Name of the agent to invoke" }),
600
408
  task: Type.String({ description: "Task to delegate to the agent" }),
@@ -646,14 +454,62 @@ export default function (pi) {
646
454
  const foregroundSubagentStatusKey = "foreground-subagent";
647
455
  const foregroundSubagentHint = "Ctrl+B: move foreground subagent to background";
648
456
  let activeForegroundSubagent = null;
457
+ let activeSessionFileForUi;
458
+ const liveStreamBufferBySession = new Map();
459
+ function flushLiveStream(sessionFile) {
460
+ const buffered = liveStreamBufferBySession.get(sessionFile);
461
+ if (!buffered || !buffered.trim())
462
+ return;
463
+ liveStreamBufferBySession.set(sessionFile, "");
464
+ pi.sendMessage({
465
+ customType: "live_subagent_stream",
466
+ content: buffered,
467
+ display: true,
468
+ }, { deliverAs: "followUp" });
469
+ }
470
+ function pushLiveStreamDelta(sessionFile, delta) {
471
+ const prev = liveStreamBufferBySession.get(sessionFile) ?? "";
472
+ const next = prev + delta;
473
+ liveStreamBufferBySession.set(sessionFile, next);
474
+ if (next.length >= 120 || next.includes("\n")) {
475
+ flushLiveStream(sessionFile);
476
+ }
477
+ }
478
+ function getCurrentSessionSubagentMetadata(sessionFile) {
479
+ if (!sessionFile)
480
+ return null;
481
+ return readSessionHeader(sessionFile);
482
+ }
483
+ function applyCurrentSessionSubagentTools(ctx) {
484
+ const metadata = getCurrentSessionSubagentMetadata(ctx.sessionManager.getSessionFile());
485
+ if (metadata?.subagentTools && metadata.subagentTools.length > 0) {
486
+ ctx.setActiveTools(metadata.subagentTools);
487
+ }
488
+ }
649
489
  function getBgManager() {
650
490
  if (!bgManager)
651
491
  throw new Error("BackgroundJobManager not initialized.");
652
492
  return bgManager;
653
493
  }
654
- pi.on("session_start", async () => {
494
+ pi.on("session_start", async (_event, ctx) => {
495
+ activeSessionFileForUi = ctx.sessionManager.getSessionFile();
655
496
  bgManager = new BackgroundJobManager({
656
497
  onJobComplete: (job) => {
498
+ if (job.sessionFile && job.parentSessionFile) {
499
+ const existingParent = parentSessionByChild.get(job.sessionFile);
500
+ if (!existingParent) {
501
+ registerAgentSessionLink({
502
+ agentName: job.agentName,
503
+ task: job.task,
504
+ parentSessionFile: job.parentSessionFile,
505
+ subagentSessionFile: job.sessionFile,
506
+ state: job.status === "failed" ? "failed" : "completed",
507
+ });
508
+ }
509
+ else {
510
+ updateAgentSessionLinkState(job.sessionFile, job.status === "failed" ? "failed" : "completed");
511
+ }
512
+ }
657
513
  if (job.awaited)
658
514
  return;
659
515
  const statusEmoji = job.status === "completed" ? "✓" : job.status === "cancelled" ? "✗ cancelled" : "✗ failed";
@@ -676,22 +532,91 @@ export default function (pi) {
676
532
  }, { deliverAs: "followUp" });
677
533
  },
678
534
  });
535
+ applyCurrentSessionSubagentTools(ctx);
679
536
  });
680
- pi.on("session_before_switch", async () => {
681
- activeForegroundSubagent = null;
682
- if (bgManager) {
683
- for (const job of bgManager.getRunningJobs()) {
684
- bgManager.cancel(job.id);
537
+ pi.on("session_switch", async (_event, ctx) => {
538
+ activeSessionFileForUi = ctx.sessionManager.getSessionFile();
539
+ applyCurrentSessionSubagentTools(ctx);
540
+ });
541
+ pi.on("before_agent_start", async (event, ctx) => {
542
+ const metadata = getCurrentSessionSubagentMetadata(ctx.sessionManager.getSessionFile());
543
+ if (!metadata?.subagentSystemPrompt)
544
+ return;
545
+ const subagentName = metadata.subagentName ?? "subagent";
546
+ const taskNote = metadata.subagentTask
547
+ ? `Original delegated task: ${metadata.subagentTask}`
548
+ : "Continue operating as the delegated subagent for this session.";
549
+ const antiRecursion = [
550
+ `You are already the ${subagentName} subagent for this session.`,
551
+ "Do not spawn or delegate to another subagent with the same name as yourself.",
552
+ `If the user asks you to continue ${subagentName} work, do that work directly in this session.`,
553
+ taskNote,
554
+ ].join("\n");
555
+ return {
556
+ systemPrompt: `${event.systemPrompt}\n\n${antiRecursion}\n\n${metadata.subagentSystemPrompt}`,
557
+ };
558
+ });
559
+ pi.on("input", async (event, ctx) => {
560
+ const sessionFile = ctx.sessionManager.getSessionFile();
561
+ const runtimeFromSession = sessionFile ? liveRuntimeBySessionFile.get(sessionFile) : undefined;
562
+ const runtimeFromForeground = (!runtimeFromSession && activeForegroundSubagent && !activeForegroundSubagent.claimed &&
563
+ activeForegroundSubagent.sendPrompt && activeForegroundSubagent.sendSteer && activeForegroundSubagent.sendFollowUp && activeForegroundSubagent.isBusy &&
564
+ sessionFile && activeForegroundSubagent.parentSessionFile === sessionFile)
565
+ ? {
566
+ agentName: activeForegroundSubagent.agentName,
567
+ isBusy: activeForegroundSubagent.isBusy,
568
+ sendPrompt: activeForegroundSubagent.sendPrompt,
569
+ sendSteer: activeForegroundSubagent.sendSteer,
570
+ sendFollowUp: activeForegroundSubagent.sendFollowUp,
685
571
  }
572
+ : undefined;
573
+ const runtime = runtimeFromSession ?? runtimeFromForeground;
574
+ if (!runtime)
575
+ return;
576
+ const text = event.text?.trim();
577
+ if (!text)
578
+ return { action: "handled" };
579
+ const isSlashCommand = text.startsWith("/");
580
+ if (isSlashCommand)
581
+ return;
582
+ try {
583
+ if (runtime.isBusy()) {
584
+ await runtime.sendSteer(text, event.images);
585
+ ctx.ui.notify(`Sent steer to running subagent ${runtime.agentName}.`, "info");
586
+ }
587
+ else {
588
+ await runtime.sendPrompt(text, event.images);
589
+ ctx.ui.notify(`Sent prompt to live subagent ${runtime.agentName}.`, "info");
590
+ }
591
+ return { action: "handled" };
686
592
  }
593
+ catch (error) {
594
+ ctx.ui.notify(`Failed to send input to live subagent ${runtime.agentName}: ${error instanceof Error ? error.message : String(error)}`, "error");
595
+ return { action: "handled" };
596
+ }
597
+ });
598
+ pi.on("session_before_switch", async () => {
599
+ if (activeSessionFileForUi)
600
+ flushLiveStream(activeSessionFileForUi);
601
+ activeForegroundSubagent = null;
687
602
  });
688
603
  pi.on("session_shutdown", async () => {
604
+ if (activeSessionFileForUi)
605
+ flushLiveStream(activeSessionFileForUi);
606
+ activeSessionFileForUi = undefined;
689
607
  activeForegroundSubagent = null;
690
608
  await stopLiveSubagents();
691
609
  if (bgManager) {
692
610
  bgManager.shutdown();
693
611
  bgManager = null;
694
612
  }
613
+ agentSessionLinksById.clear();
614
+ agentSessionIdsByParent.clear();
615
+ parentSessionByChild.clear();
616
+ liveRuntimeBySessionFile.clear();
617
+ inProcessSubagentDepthBySessionId.clear();
618
+ inProcessSubagentAncestryBySessionId.clear();
619
+ liveStreamBufferBySession.clear();
695
620
  });
696
621
  // /subagents command
697
622
  pi.registerCommand("subagents", {
@@ -795,6 +720,166 @@ export default function (pi) {
795
720
  ctx.ui.notify(`Available agents (${discovery.agents.length}):\n${lines.join("\n")}`, "info");
796
721
  },
797
722
  });
723
+ // /agent command - switch to the parent or a tracked subagent session
724
+ pi.registerCommand("agent", {
725
+ description: "Switch focus to parent/subagent sessions (/agent picker, /agent <id|index|name>, /agent parent)",
726
+ handler: async (args, ctx) => {
727
+ const currentSessionFile = ctx.sessionManager.getSessionFile();
728
+ if (!currentSessionFile) {
729
+ ctx.ui.notify("Current session is in-memory only; /agent requires a persisted session file.", "warning");
730
+ return;
731
+ }
732
+ const arg = args.trim();
733
+ const parentSessionFile = parentSessionByChild.get(currentSessionFile);
734
+ const currentParent = parentSessionFile ?? currentSessionFile;
735
+ const currentSessionDir = path.dirname(currentParent);
736
+ let tracked = getAgentSessionLinksForParent(currentParent).filter((entry) => fs.existsSync(entry.subagentSessionFile));
737
+ if (tracked.length === 0) {
738
+ tracked = backfillAgentSessionLinksForParent(currentParent, currentSessionDir)
739
+ .filter((entry) => fs.existsSync(entry.subagentSessionFile));
740
+ }
741
+ const runningJobs = bgManager?.getRunningJobs() ?? [];
742
+ const switchTargets = buildSwitchTargetsForParent(currentParent, currentSessionFile, ctx.cwd, tracked, runningJobs);
743
+ const applySwitchTarget = async (target) => {
744
+ if (target.selectionAction === "blocked") {
745
+ ctx.ui.notify(target.blockedReason ?? "That target cannot be selected yet.", "warning");
746
+ return;
747
+ }
748
+ if (target.selectionAction === "attach_live") {
749
+ if (!fs.existsSync(target.sessionFile)) {
750
+ ctx.ui.notify(`Live subagent session file is missing: ${target.sessionFile}`, "error");
751
+ return;
752
+ }
753
+ const liveRuntime = liveRuntimeBySessionFile.get(target.sessionFile);
754
+ if (!liveRuntime) {
755
+ ctx.ui.notify("Live runtime is no longer available for this subagent. It may have completed.", "warning");
756
+ return;
757
+ }
758
+ const switched = await ctx.switchSession(target.sessionFile);
759
+ if (switched.cancelled) {
760
+ ctx.ui.notify("Session switch was cancelled.", "warning");
761
+ return;
762
+ }
763
+ 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");
764
+ return;
765
+ }
766
+ if (target.kind === "parent") {
767
+ if (!fs.existsSync(target.sessionFile)) {
768
+ ctx.ui.notify(`Parent session file not found: ${target.sessionFile}`, "error");
769
+ return;
770
+ }
771
+ const switched = await ctx.switchSession(target.sessionFile);
772
+ if (switched.cancelled) {
773
+ ctx.ui.notify("Session switch was cancelled.", "warning");
774
+ return;
775
+ }
776
+ ctx.ui.notify("Switched to parent session.", "info");
777
+ return;
778
+ }
779
+ if (!fs.existsSync(target.sessionFile)) {
780
+ ctx.ui.notify(`Subagent session file is missing: ${target.sessionFile}`, "error");
781
+ return;
782
+ }
783
+ const switched = await ctx.switchSession(target.sessionFile);
784
+ if (switched.cancelled) {
785
+ ctx.ui.notify("Session switch was cancelled.", "warning");
786
+ return;
787
+ }
788
+ updateAgentSessionLinkState(target.sessionFile, target.state === "failed" ? "failed" : "completed");
789
+ ctx.ui.notify(`Switched to subagent ${target.agentName}. This resumes the saved subagent session; use /agent parent to return.`, "info");
790
+ };
791
+ if (!arg) {
792
+ const subagentTargets = switchTargets.filter((target) => target.kind === "subagent");
793
+ if (ctx.hasUI) {
794
+ if (subagentTargets.length === 0 && !parentSessionFile) {
795
+ ctx.ui.notify("No tracked subagent sessions for this parent session yet. Run a single-mode subagent first (foreground or background).", "info");
796
+ return;
797
+ }
798
+ const selected = await showAgentSwitcher(ctx, switchTargets);
799
+ if (!selected)
800
+ return;
801
+ await applySwitchTarget(selected);
802
+ return;
803
+ }
804
+ if (subagentTargets.length === 0 && !parentSessionFile) {
805
+ ctx.ui.notify("No tracked subagent sessions for this parent session yet. Run a single-mode subagent first (foreground or background).", "info");
806
+ return;
807
+ }
808
+ const lines = ["Agent switch targets:"];
809
+ switchTargets.forEach((target, index) => {
810
+ lines.push(`${index + 1}. ${formatSwitchTargetSummary(target)}`);
811
+ });
812
+ lines.push("", "Use `/agent <index|id|name>` for explicit targeting, or `/agent parent`.");
813
+ ctx.ui.notify(lines.join("\n"), "info");
814
+ return;
815
+ }
816
+ if (arg === "parent" || arg === "main") {
817
+ if (!parentSessionFile) {
818
+ ctx.ui.notify("You are already in the parent/main session.", "info");
819
+ return;
820
+ }
821
+ if (!fs.existsSync(parentSessionFile)) {
822
+ ctx.ui.notify(`Parent session file not found: ${parentSessionFile}`, "error");
823
+ return;
824
+ }
825
+ const switched = await ctx.switchSession(parentSessionFile);
826
+ if (switched.cancelled) {
827
+ ctx.ui.notify("Session switch was cancelled.", "warning");
828
+ return;
829
+ }
830
+ ctx.ui.notify("Switched to parent session.", "info");
831
+ return;
832
+ }
833
+ let target;
834
+ if (/^\d+$/.test(arg)) {
835
+ const index = Number.parseInt(arg, 10) - 1;
836
+ target = tracked[index];
837
+ }
838
+ if (!target) {
839
+ target = tracked.find((entry) => entry.id === arg);
840
+ }
841
+ if (!target) {
842
+ target = tracked.find((entry) => entry.agentName === arg);
843
+ }
844
+ if (!target) {
845
+ target = tracked.find((entry) => path.basename(entry.subagentSessionFile) === arg);
846
+ }
847
+ if (!target) {
848
+ const runningTarget = switchTargets.find((entry) => entry.id === arg && entry.kind === "subagent");
849
+ if (runningTarget?.state === "running") {
850
+ ctx.ui.notify(runningTarget.blockedReason ?? "Selected subagent is still running. Live attach is not implemented yet.", "warning");
851
+ return;
852
+ }
853
+ ctx.ui.notify(`Unknown subagent target: ${arg}. Run /agent to list available targets.`, "warning");
854
+ return;
855
+ }
856
+ if (!fs.existsSync(target.subagentSessionFile)) {
857
+ ctx.ui.notify(`Subagent session file is missing: ${target.subagentSessionFile}`, "error");
858
+ return;
859
+ }
860
+ if (target.state === "running") {
861
+ const liveRuntime = liveRuntimeBySessionFile.get(target.subagentSessionFile);
862
+ if (!liveRuntime) {
863
+ ctx.ui.notify("Live runtime is no longer available for this subagent. It may have completed.", "warning");
864
+ return;
865
+ }
866
+ const switched = await ctx.switchSession(target.subagentSessionFile);
867
+ if (switched.cancelled) {
868
+ ctx.ui.notify("Session switch was cancelled.", "warning");
869
+ return;
870
+ }
871
+ 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");
872
+ return;
873
+ }
874
+ const switched = await ctx.switchSession(target.subagentSessionFile);
875
+ if (switched.cancelled) {
876
+ ctx.ui.notify("Session switch was cancelled.", "warning");
877
+ return;
878
+ }
879
+ updateAgentSessionLinkState(target.subagentSessionFile, target.state === "failed" ? "failed" : "completed");
880
+ ctx.ui.notify(`Switched to subagent ${target.agentName}. This resumes the saved subagent session; use /agent parent to return.`, "info");
881
+ },
882
+ });
798
883
  pi.registerShortcut(Key.ctrl("b"), {
799
884
  description: shortcutDesc("Move foreground subagent to background", "/subagents list"),
800
885
  handler: async (ctx) => {
@@ -809,7 +894,19 @@ export default function (pi) {
809
894
  running.claimed = true;
810
895
  let jobId;
811
896
  try {
812
- jobId = manager.adoptRunning(running.agentName, running.task, running.cwd, running.abortController, running.resultPromise);
897
+ if (running.handle) {
898
+ jobId = manager.adoptHandle(running.agentName, running.task, running.cwd, running.handle, running.resultPromise, {
899
+ parentSessionFile: running.parentSessionFile ?? ctx.sessionManager.getSessionFile(),
900
+ });
901
+ }
902
+ else if (running.abortController) {
903
+ jobId = manager.adoptRunning(running.agentName, running.task, running.cwd, running.abortController, running.resultPromise, {
904
+ parentSessionFile: running.parentSessionFile ?? ctx.sessionManager.getSessionFile(),
905
+ });
906
+ }
907
+ else {
908
+ throw new Error("Foreground subagent cannot be moved to background (missing runtime handle).");
909
+ }
813
910
  }
814
911
  catch (error) {
815
912
  running.claimed = false;
@@ -884,6 +981,15 @@ export default function (pi) {
884
981
  const confirmProjectAgents = params.confirmProjectAgents ?? false;
885
982
  const cmuxClient = CmuxClient.fromPreferences(loadEffectivePreferences()?.preferences);
886
983
  const cmuxSplitsEnabled = cmuxClient.getConfig().splits;
984
+ const invokingSessionFile = ctx.sessionManager.getSessionFile();
985
+ const invokingSessionId = ctx.sessionManager.getSessionId();
986
+ const invokingMetadata = getCurrentSessionSubagentMetadata(invokingSessionFile);
987
+ const currentSubagentName = invokingMetadata?.subagentName;
988
+ const trackedAncestry = inProcessSubagentAncestryBySessionId.get(invokingSessionId);
989
+ const currentAncestry = trackedAncestry ?? (currentSubagentName ? [currentSubagentName] : []);
990
+ const inferredCurrentDepth = currentSubagentName ? Math.max(currentAncestry.length, 1) : 0;
991
+ const currentSubagentDepth = Math.max(inProcessSubagentDepthBySessionId.get(invokingSessionId) ?? 0, inferredCurrentDepth);
992
+ const nextSubagentDepth = currentSubagentDepth + 1;
887
993
  // Resolve isolation mode
888
994
  const isolationMode = readIsolationMode();
889
995
  const useIsolation = Boolean(params.isolated) && isolationMode !== "none";
@@ -897,6 +1003,30 @@ export default function (pi) {
897
1003
  projectAgentsDir: discovery.projectAgentsDir,
898
1004
  results,
899
1005
  });
1006
+ const trackInProcessDepth = (started, depth, ancestry) => {
1007
+ const sessionId = started.handle.sessionId;
1008
+ if (!sessionId)
1009
+ return;
1010
+ inProcessSubagentDepthBySessionId.set(sessionId, depth);
1011
+ inProcessSubagentAncestryBySessionId.set(sessionId, ancestry);
1012
+ void started.resultPromise.finally(() => {
1013
+ inProcessSubagentDepthBySessionId.delete(sessionId);
1014
+ inProcessSubagentAncestryBySessionId.delete(sessionId);
1015
+ });
1016
+ };
1017
+ const buildChildAncestry = (childAgentName) => [...currentAncestry, childAgentName];
1018
+ const requestedAgentNames = hasSingle
1019
+ ? [params.agent]
1020
+ : hasChain
1021
+ ? (params.chain ?? []).map((step) => step.agent)
1022
+ : (params.tasks ?? []).map((task) => task.agent);
1023
+ if (currentSubagentName && requestedAgentNames.some((name) => name === currentSubagentName)) {
1024
+ return {
1025
+ content: [{ type: "text", text: `Subagent "${currentSubagentName}" cannot spawn another subagent with the same name as itself.` }],
1026
+ details: makeDetails(hasChain ? "chain" : hasTasks ? "parallel" : "single")([]),
1027
+ isError: true,
1028
+ };
1029
+ }
900
1030
  if (modeCount !== 1) {
901
1031
  const available = agents.map((a) => `${a.name} (${a.source})`).join(", ") || "none";
902
1032
  return {
@@ -916,6 +1046,20 @@ export default function (pi) {
916
1046
  isError: true,
917
1047
  };
918
1048
  }
1049
+ if (params.background && currentSubagentDepth > 0) {
1050
+ return {
1051
+ content: [{ type: "text", text: "Nested background subagent launches are not supported yet. Run the nested subagent in foreground mode." }],
1052
+ details: makeDetails("single")([]),
1053
+ isError: true,
1054
+ };
1055
+ }
1056
+ if (USE_IN_PROCESS_SUBAGENT && nextSubagentDepth > MAX_IN_PROCESS_SUBAGENT_DEPTH) {
1057
+ return {
1058
+ content: [{ type: "text", text: `Max subagent depth (${MAX_IN_PROCESS_SUBAGENT_DEPTH}) exceeded.` }],
1059
+ details: makeDetails(hasChain ? "chain" : hasTasks ? "parallel" : "single")([]),
1060
+ isError: true,
1061
+ };
1062
+ }
919
1063
  if ((agentScope === "project" || agentScope === "both") && confirmProjectAgents && ctx.hasUI) {
920
1064
  const requestedAgentNames = new Set();
921
1065
  if (params.chain)
@@ -960,7 +1104,13 @@ export default function (pi) {
960
1104
  }
961
1105
  }
962
1106
  : undefined;
963
- const result = await runSingleAgent(ctx.cwd, agents, step.agent, taskWithContext, step.cwd, i + 1, step.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, signal, chainUpdate, makeDetails("chain"));
1107
+ const result = USE_IN_PROCESS_SUBAGENT
1108
+ ? await (async () => {
1109
+ const started = await startInProcessSingleAgent(ctx.cwd, agents, step.agent, taskWithContext, step.cwd, i + 1, step.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, signal, chainUpdate, makeDetails("chain"), invokingSessionFile, nextSubagentDepth, invokingSessionId, currentAncestry);
1110
+ trackInProcessDepth(started, nextSubagentDepth, buildChildAncestry(step.agent));
1111
+ return started.resultPromise;
1112
+ })()
1113
+ : await runLegacySingleAgent(ctx.cwd, agents, step.agent, taskWithContext, step.cwd, i + 1, step.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, signal, chainUpdate, makeDetails("chain"), invokingSessionFile, false, undefined, undefined);
964
1114
  results.push(result);
965
1115
  const isError = result.exitCode !== 0 || result.stopReason === "error" || result.stopReason === "aborted";
966
1116
  if (isError) {
@@ -1022,12 +1172,23 @@ export default function (pi) {
1022
1172
  const gridSurfaces = [];
1023
1173
  const results = await mapWithConcurrencyLimit(params.tasks, MAX_CONCURRENCY, async (t, index) => {
1024
1174
  const workerId = registerWorker(t.agent, t.task, index, batchSize, batchId);
1025
- const runTask = () => runSingleAgent(ctx.cwd, agents, t.agent, t.task, t.cwd, undefined, t.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, signal, (partial) => {
1026
- if (partial.details?.results[0]) {
1027
- allResults[index] = partial.details.results[0];
1028
- emitParallelUpdate();
1029
- }
1030
- }, makeDetails("parallel"));
1175
+ const runTask = () => USE_IN_PROCESS_SUBAGENT
1176
+ ? (async () => {
1177
+ const started = await startInProcessSingleAgent(ctx.cwd, agents, t.agent, t.task, t.cwd, undefined, t.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, signal, (partial) => {
1178
+ if (partial.details?.results[0]) {
1179
+ allResults[index] = partial.details.results[0];
1180
+ emitParallelUpdate();
1181
+ }
1182
+ }, makeDetails("parallel"), invokingSessionFile, nextSubagentDepth, invokingSessionId, currentAncestry);
1183
+ trackInProcessDepth(started, nextSubagentDepth, buildChildAncestry(t.agent));
1184
+ return started.resultPromise;
1185
+ })()
1186
+ : runLegacySingleAgent(ctx.cwd, agents, t.agent, t.task, t.cwd, undefined, t.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, signal, (partial) => {
1187
+ if (partial.details?.results[0]) {
1188
+ allResults[index] = partial.details.results[0];
1189
+ emitParallelUpdate();
1190
+ }
1191
+ }, makeDetails("parallel"), invokingSessionFile, false, undefined);
1031
1192
  let result = await runTask();
1032
1193
  // Auto-retry failed tasks (likely API rate limit or transient error)
1033
1194
  const isFailed = result.exitCode !== 0 || (result.messages.length === 0 && !signal?.aborted);
@@ -1084,16 +1245,81 @@ export default function (pi) {
1084
1245
  const bgInferredModel = resolveSubagentModel({ name: agentForBg.name, model: bgResolvedModelCfg }, { overrideModel: params.model, parentModel: ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined });
1085
1246
  let jobId;
1086
1247
  try {
1087
- jobId = runSubagentInBackground(manager, agents, params.agent, params.task, params.cwd, params.model, { defaultCwd: ctx.cwd, model: ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined }, async (bgSignal) => {
1088
- const result = await runSingleAgent(ctx.cwd, agents, params.agent, params.task, params.cwd, undefined, params.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, bgSignal, undefined, // no streaming updates for background jobs
1089
- makeDetails("single"));
1090
- return {
1091
- exitCode: result.exitCode,
1092
- finalOutput: getFinalOutput(result.messages),
1248
+ if (USE_IN_PROCESS_SUBAGENT) {
1249
+ const started = await startInProcessSingleAgent(ctx.cwd, agents, params.agent, params.task, params.cwd, undefined, params.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, undefined, undefined, makeDetails("single"), invokingSessionFile, nextSubagentDepth, invokingSessionId, currentAncestry);
1250
+ trackInProcessDepth(started, nextSubagentDepth, buildChildAncestry(params.agent));
1251
+ const effectiveCwd = params.cwd ?? ctx.cwd;
1252
+ jobId = manager.adoptHandle(params.agent, params.task, effectiveCwd, started.handle, started.resultPromise.then((result) => ({
1253
+ summary: (getFinalOutput(result.messages) || "(no output)").slice(0, 300),
1093
1254
  stderr: result.stderr,
1255
+ exitCode: result.exitCode,
1094
1256
  model: result.model,
1095
- };
1096
- });
1257
+ sessionFile: result.sessionFile,
1258
+ parentSessionFile: result.parentSessionFile,
1259
+ })), {
1260
+ parentSessionFile: invokingSessionFile,
1261
+ model: bgInferredModel,
1262
+ });
1263
+ }
1264
+ else {
1265
+ jobId = runSubagentInBackground(manager, agents, params.agent, params.task, params.cwd, params.model, { defaultCwd: ctx.cwd, model: ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, parentSessionFile: invokingSessionFile }, async (bgSignal) => {
1266
+ let liveSessionFile;
1267
+ let liveRuntime;
1268
+ const result = await runLegacySingleAgent(ctx.cwd, agents, params.agent, params.task, params.cwd, undefined, params.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, bgSignal, undefined, // no streaming updates for background jobs
1269
+ makeDetails("single"), invokingSessionFile, true, (info) => {
1270
+ if (!invokingSessionFile || !info.sessionFile)
1271
+ return;
1272
+ upsertAgentSessionLink(params.agent, params.task, invokingSessionFile, info.sessionFile, "running");
1273
+ liveSessionFile = info.sessionFile;
1274
+ if (liveRuntime) {
1275
+ liveRuntime.sessionFile = info.sessionFile;
1276
+ liveRuntime.parentSessionFile = info.parentSessionFile ?? invokingSessionFile;
1277
+ liveRuntimeBySessionFile.set(info.sessionFile, liveRuntime);
1278
+ }
1279
+ }, (event, partial) => {
1280
+ const sessionFile = partial.sessionFile;
1281
+ if (!sessionFile || activeSessionFileForUi !== sessionFile)
1282
+ return;
1283
+ if (event?.type === "message_update" && event.assistantMessageEvent?.type === "text_delta") {
1284
+ const delta = String(event.assistantMessageEvent.delta ?? "");
1285
+ if (delta)
1286
+ pushLiveStreamDelta(sessionFile, delta);
1287
+ }
1288
+ if (event?.type === "message_end") {
1289
+ flushLiveStream(sessionFile);
1290
+ }
1291
+ }, {
1292
+ onStart: (control) => {
1293
+ if (!control.sendPrompt || !control.sendSteer || !control.sendFollowUp || !control.isBusy)
1294
+ return;
1295
+ liveRuntime = {
1296
+ sessionFile: liveSessionFile,
1297
+ parentSessionFile: invokingSessionFile,
1298
+ agentName: params.agent,
1299
+ isBusy: control.isBusy,
1300
+ sendPrompt: control.sendPrompt,
1301
+ sendSteer: control.sendSteer,
1302
+ sendFollowUp: control.sendFollowUp,
1303
+ };
1304
+ if (liveSessionFile) {
1305
+ liveRuntimeBySessionFile.set(liveSessionFile, liveRuntime);
1306
+ }
1307
+ },
1308
+ onFinish: () => {
1309
+ if (liveSessionFile)
1310
+ liveRuntimeBySessionFile.delete(liveSessionFile);
1311
+ },
1312
+ });
1313
+ return {
1314
+ exitCode: result.exitCode,
1315
+ finalOutput: getFinalOutput(result.messages),
1316
+ stderr: result.stderr,
1317
+ model: result.model,
1318
+ sessionFile: result.sessionFile,
1319
+ parentSessionFile: result.parentSessionFile,
1320
+ };
1321
+ });
1322
+ }
1097
1323
  }
1098
1324
  catch (err) {
1099
1325
  return {
@@ -1117,18 +1343,134 @@ export default function (pi) {
1117
1343
  const taskId = crypto.randomUUID();
1118
1344
  isolation = await createIsolation(effectiveCwd, taskId, isolationMode);
1119
1345
  }
1120
- const result = await runSingleAgent(ctx.cwd, agents, params.agent, params.task, isolation ? isolation.workDir : params.cwd, undefined, params.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, signal, onUpdate, makeDetails("single"), !isolation
1121
- ? {
1122
- onStart: (control) => {
1123
- activeForegroundSubagent = { ...control, claimed: false };
1124
- ctx.ui.setStatus(foregroundSubagentStatusKey, foregroundSubagentHint);
1125
- },
1126
- onFinish: () => {
1127
- activeForegroundSubagent = null;
1128
- ctx.ui.setStatus(foregroundSubagentStatusKey, undefined);
1346
+ let result;
1347
+ if (USE_IN_PROCESS_SUBAGENT && !isolation) {
1348
+ const started = await startInProcessSingleAgent(ctx.cwd, agents, params.agent, params.task, params.cwd, undefined, params.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, signal, onUpdate, makeDetails("single"), invokingSessionFile, nextSubagentDepth, invokingSessionId, currentAncestry);
1349
+ trackInProcessDepth(started, nextSubagentDepth, buildChildAncestry(params.agent));
1350
+ const effectiveRunCwd = params.cwd ?? ctx.cwd;
1351
+ let releaseToBackground;
1352
+ const movedToBackground = new Promise((resolve) => {
1353
+ releaseToBackground = resolve;
1354
+ });
1355
+ activeForegroundSubagent = {
1356
+ claimed: false,
1357
+ agentName: params.agent,
1358
+ task: params.task,
1359
+ cwd: effectiveRunCwd,
1360
+ parentSessionFile: invokingSessionFile,
1361
+ handle: started.handle,
1362
+ resultPromise: started.resultPromise.then((done) => ({
1363
+ summary: getFinalOutput(done.messages) || "(no output)",
1364
+ stderr: done.stderr,
1365
+ exitCode: done.exitCode,
1366
+ model: done.model,
1367
+ sessionFile: done.sessionFile,
1368
+ parentSessionFile: done.parentSessionFile,
1369
+ })),
1370
+ adoptToBackground: (jobId) => {
1371
+ if (!releaseToBackground)
1372
+ return false;
1373
+ releaseToBackground(jobId);
1374
+ releaseToBackground = undefined;
1375
+ return true;
1129
1376
  },
1377
+ sendPrompt: started.handle.prompt,
1378
+ sendSteer: started.handle.steer,
1379
+ sendFollowUp: started.handle.followUp,
1380
+ isBusy: started.handle.isBusy,
1381
+ };
1382
+ ctx.ui.setStatus(foregroundSubagentStatusKey, foregroundSubagentHint);
1383
+ const winner = await Promise.race([
1384
+ started.resultPromise.then((done) => ({ type: "done", done })),
1385
+ movedToBackground.then((jobId) => ({ type: "background", jobId })),
1386
+ ]);
1387
+ if (winner.type === "background") {
1388
+ activeForegroundSubagent = null;
1389
+ ctx.ui.setStatus(foregroundSubagentStatusKey, undefined);
1390
+ result = {
1391
+ ...started.currentResult,
1392
+ backgroundJobId: winner.jobId,
1393
+ };
1394
+ }
1395
+ else {
1396
+ activeForegroundSubagent = null;
1397
+ ctx.ui.setStatus(foregroundSubagentStatusKey, undefined);
1398
+ result = winner.done;
1130
1399
  }
1131
- : undefined);
1400
+ }
1401
+ else {
1402
+ let liveSessionFile;
1403
+ let liveRuntime;
1404
+ result = await runLegacySingleAgent(ctx.cwd, agents, params.agent, params.task, isolation ? isolation.workDir : params.cwd, undefined, params.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, signal, onUpdate, makeDetails("single"), invokingSessionFile, !isolation, !isolation
1405
+ ? (info) => {
1406
+ if (!invokingSessionFile || !info.sessionFile)
1407
+ return;
1408
+ upsertAgentSessionLink(params.agent, params.task, invokingSessionFile, info.sessionFile, "running");
1409
+ liveSessionFile = info.sessionFile;
1410
+ if (liveRuntime) {
1411
+ liveRuntime.sessionFile = info.sessionFile;
1412
+ liveRuntime.parentSessionFile = info.parentSessionFile ?? invokingSessionFile;
1413
+ liveRuntimeBySessionFile.set(info.sessionFile, liveRuntime);
1414
+ }
1415
+ }
1416
+ : undefined, !isolation
1417
+ ? (event, partial) => {
1418
+ const sessionFile = partial.sessionFile;
1419
+ if (!sessionFile || activeSessionFileForUi !== sessionFile)
1420
+ return;
1421
+ if (event?.type === "message_update" && event.assistantMessageEvent?.type === "text_delta") {
1422
+ const delta = String(event.assistantMessageEvent.delta ?? "");
1423
+ if (delta)
1424
+ pushLiveStreamDelta(sessionFile, delta);
1425
+ }
1426
+ if (event?.type === "message_end") {
1427
+ flushLiveStream(sessionFile);
1428
+ }
1429
+ }
1430
+ : undefined, !isolation
1431
+ ? {
1432
+ onStart: (control) => {
1433
+ activeForegroundSubagent = { ...control, claimed: false };
1434
+ ctx.ui.setStatus(foregroundSubagentStatusKey, foregroundSubagentHint);
1435
+ if (!control.sendPrompt || !control.sendSteer || !control.sendFollowUp || !control.isBusy)
1436
+ return;
1437
+ liveRuntime = {
1438
+ sessionFile: liveSessionFile,
1439
+ parentSessionFile: invokingSessionFile,
1440
+ agentName: params.agent,
1441
+ isBusy: control.isBusy,
1442
+ sendPrompt: control.sendPrompt,
1443
+ sendSteer: control.sendSteer,
1444
+ sendFollowUp: control.sendFollowUp,
1445
+ };
1446
+ if (liveSessionFile && liveRuntime) {
1447
+ liveRuntimeBySessionFile.set(liveSessionFile, liveRuntime);
1448
+ }
1449
+ },
1450
+ onFinish: () => {
1451
+ activeForegroundSubagent = null;
1452
+ ctx.ui.setStatus(foregroundSubagentStatusKey, undefined);
1453
+ if (liveSessionFile)
1454
+ liveRuntimeBySessionFile.delete(liveSessionFile);
1455
+ },
1456
+ }
1457
+ : undefined);
1458
+ }
1459
+ if (result.sessionFile && invokingSessionFile) {
1460
+ const existingParent = parentSessionByChild.get(result.sessionFile);
1461
+ if (!existingParent) {
1462
+ registerAgentSessionLink({
1463
+ agentName: result.agent,
1464
+ task: result.task,
1465
+ parentSessionFile: invokingSessionFile,
1466
+ subagentSessionFile: result.sessionFile,
1467
+ state: result.exitCode === 0 ? "completed" : "failed",
1468
+ });
1469
+ }
1470
+ else {
1471
+ updateAgentSessionLinkState(result.sessionFile, result.exitCode === 0 ? "completed" : "failed");
1472
+ }
1473
+ }
1132
1474
  if (result.backgroundJobId) {
1133
1475
  return {
1134
1476
  content: [{ type: "text", text: `Moved ${result.agent} to background as **${result.backgroundJobId}**. Use \`await_subagent\`, \`/subagents wait ${result.backgroundJobId}\`, or \`/subagents output ${result.backgroundJobId}\`.` }],
@@ -1143,10 +1485,11 @@ export default function (pi) {
1143
1485
  }
1144
1486
  }
1145
1487
  const isError = result.exitCode !== 0 || result.stopReason === "error" || result.stopReason === "aborted";
1488
+ const agentSwitchHint = result.sessionFile ? "\n\nTip: run `/agent` to switch focus to this subagent session." : "";
1146
1489
  if (isError) {
1147
1490
  const errorMsg = result.errorMessage || result.stderr || getFinalOutput(result.messages) || "(no output)";
1148
1491
  return {
1149
- content: [{ type: "text", text: `Agent ${result.stopReason || "failed"}: ${errorMsg}` }],
1492
+ content: [{ type: "text", text: `Agent ${result.stopReason || "failed"}: ${errorMsg}${agentSwitchHint}` }],
1150
1493
  details: makeDetails("single")([result]),
1151
1494
  isError: true,
1152
1495
  };
@@ -1155,6 +1498,8 @@ export default function (pi) {
1155
1498
  if (mergeResult && !mergeResult.success) {
1156
1499
  outputText += `\n\n⚠ Patch merge failed: ${mergeResult.error || "unknown error"}`;
1157
1500
  }
1501
+ if (agentSwitchHint)
1502
+ outputText += agentSwitchHint;
1158
1503
  return {
1159
1504
  content: [{ type: "text", text: outputText }],
1160
1505
  details: makeDetails("single")([result]),