lsd-pi 1.2.4 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) 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/mac-tools/index.js +19 -34
  27. package/dist/resources/extensions/memory/index.js +20 -2
  28. package/dist/resources/extensions/shared/interview-ui.js +103 -20
  29. package/dist/resources/extensions/slash-commands/plan.js +18 -17
  30. package/dist/resources/extensions/slash-commands/tools.js +40 -4
  31. package/dist/resources/extensions/subagent/agent-switcher-component.js +208 -0
  32. package/dist/resources/extensions/subagent/agent-switcher-model.js +107 -0
  33. package/dist/resources/extensions/subagent/background-job-manager.js +11 -6
  34. package/dist/resources/extensions/subagent/background-runner.js +4 -0
  35. package/dist/resources/extensions/subagent/index.js +714 -21
  36. package/dist/resources/extensions/subagent/launch-helpers.js +19 -5
  37. package/dist/shared-paths.d.ts +4 -0
  38. package/dist/shared-paths.js +4 -0
  39. package/dist/shared-preferences.d.ts +4 -0
  40. package/dist/shared-preferences.js +4 -0
  41. package/dist/startup-model-validation.d.ts +1 -1
  42. package/dist/startup-timings.d.ts +4 -0
  43. package/dist/startup-timings.js +4 -0
  44. package/dist/update-check.d.ts +4 -0
  45. package/dist/update-check.js +4 -0
  46. package/dist/update-cmd.d.ts +4 -0
  47. package/dist/update-cmd.js +4 -0
  48. package/dist/welcome-screen.js +4 -4
  49. package/dist/wizard.d.ts +4 -0
  50. package/dist/wizard.js +4 -0
  51. package/package.json +1 -1
  52. package/packages/pi-agent-core/dist/agent.d.ts +9 -0
  53. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  54. package/packages/pi-agent-core/dist/agent.js +89 -5
  55. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  56. package/packages/pi-agent-core/dist/types.d.ts +13 -2
  57. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  58. package/packages/pi-agent-core/dist/types.js.map +1 -1
  59. package/packages/pi-agent-core/src/agent.ts +110 -4
  60. package/packages/pi-agent-core/src/types.ts +12 -3
  61. package/packages/pi-ai/dist/adaptive/classifier.d.ts +29 -0
  62. package/packages/pi-ai/dist/adaptive/classifier.d.ts.map +1 -0
  63. package/packages/pi-ai/dist/adaptive/classifier.js +72 -0
  64. package/packages/pi-ai/dist/adaptive/classifier.js.map +1 -0
  65. package/packages/pi-ai/dist/adaptive/classifier.test.d.ts +2 -0
  66. package/packages/pi-ai/dist/adaptive/classifier.test.d.ts.map +1 -0
  67. package/packages/pi-ai/dist/adaptive/classifier.test.js +32 -0
  68. package/packages/pi-ai/dist/adaptive/classifier.test.js.map +1 -0
  69. package/packages/pi-ai/dist/index.d.ts +1 -0
  70. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  71. package/packages/pi-ai/dist/index.js +1 -0
  72. package/packages/pi-ai/dist/index.js.map +1 -1
  73. package/packages/pi-ai/dist/providers/amazon-bedrock.js +0 -2
  74. package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
  75. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  76. package/packages/pi-ai/dist/providers/anthropic-shared.js +0 -2
  77. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  78. package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts +1 -1
  79. package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
  80. package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
  81. package/packages/pi-ai/dist/providers/google-gemini-cli.d.ts.map +1 -1
  82. package/packages/pi-ai/dist/providers/google-gemini-cli.js +0 -4
  83. package/packages/pi-ai/dist/providers/google-gemini-cli.js.map +1 -1
  84. package/packages/pi-ai/dist/providers/google-vertex.js +0 -5
  85. package/packages/pi-ai/dist/providers/google-vertex.js.map +1 -1
  86. package/packages/pi-ai/dist/providers/google.js +0 -5
  87. package/packages/pi-ai/dist/providers/google.js.map +1 -1
  88. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts +1 -1
  89. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
  90. package/packages/pi-ai/dist/providers/openai-codex-responses.js +0 -2
  91. package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  92. package/packages/pi-ai/dist/providers/openai-completions.d.ts +1 -1
  93. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  94. package/packages/pi-ai/dist/providers/openai-completions.js +0 -1
  95. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  96. package/packages/pi-ai/dist/providers/openai-responses.d.ts +1 -1
  97. package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  98. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  99. package/packages/pi-ai/dist/providers/openai-shared.d.ts +0 -1
  100. package/packages/pi-ai/dist/providers/openai-shared.d.ts.map +1 -1
  101. package/packages/pi-ai/dist/providers/openai-shared.js +0 -4
  102. package/packages/pi-ai/dist/providers/openai-shared.js.map +1 -1
  103. package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  104. package/packages/pi-ai/dist/providers/simple-options.js +0 -1
  105. package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
  106. package/packages/pi-ai/dist/types.d.ts +1 -2
  107. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  108. package/packages/pi-ai/dist/types.js.map +1 -1
  109. package/packages/pi-ai/src/adaptive/classifier.test.ts +38 -0
  110. package/packages/pi-ai/src/adaptive/classifier.ts +107 -0
  111. package/packages/pi-ai/src/index.ts +1 -0
  112. package/packages/pi-ai/src/providers/amazon-bedrock.ts +0 -2
  113. package/packages/pi-ai/src/providers/anthropic-shared.ts +0 -2
  114. package/packages/pi-ai/src/providers/azure-openai-responses.ts +1 -1
  115. package/packages/pi-ai/src/providers/google-gemini-cli.ts +0 -4
  116. package/packages/pi-ai/src/providers/google-vertex.ts +0 -5
  117. package/packages/pi-ai/src/providers/google.ts +0 -5
  118. package/packages/pi-ai/src/providers/openai-codex-responses.ts +1 -3
  119. package/packages/pi-ai/src/providers/openai-completions.ts +1 -2
  120. package/packages/pi-ai/src/providers/openai-responses.ts +1 -1
  121. package/packages/pi-ai/src/providers/openai-shared.ts +0 -3
  122. package/packages/pi-ai/src/providers/simple-options.ts +0 -1
  123. package/packages/pi-ai/src/types.ts +1 -2
  124. package/packages/pi-coding-agent/dist/cli/args.js +2 -2
  125. package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
  126. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +7 -2
  127. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  128. package/packages/pi-coding-agent/dist/core/agent-session.js +53 -20
  129. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  130. package/packages/pi-coding-agent/dist/core/lsp/lsp.md +3 -1
  131. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/sdk.js +32 -6
  133. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/core/sdk.test.js +37 -0
  135. package/packages/pi-coding-agent/dist/core/sdk.test.js.map +1 -1
  136. package/packages/pi-coding-agent/dist/core/session-manager.d.ts +8 -0
  137. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  138. package/packages/pi-coding-agent/dist/core/session-manager.js +4 -0
  139. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  140. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +12 -7
  141. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  142. package/packages/pi-coding-agent/dist/core/settings-manager.js +20 -2
  143. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  144. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  145. package/packages/pi-coding-agent/dist/core/skills.js +4 -1
  146. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  147. package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -1
  148. package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
  149. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  150. package/packages/pi-coding-agent/dist/core/system-prompt.js +6 -2
  151. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  152. package/packages/pi-coding-agent/dist/core/tools/grep.js +1 -1
  153. package/packages/pi-coding-agent/dist/core/tools/grep.js.map +1 -1
  154. package/packages/pi-coding-agent/dist/core/tools/index.d.ts +2 -0
  155. package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
  156. package/packages/pi-coding-agent/dist/core/tools/index.js +2 -0
  157. package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
  158. package/packages/pi-coding-agent/dist/core/tools/pty.d.ts +10 -1
  159. package/packages/pi-coding-agent/dist/core/tools/pty.d.ts.map +1 -1
  160. package/packages/pi-coding-agent/dist/core/tools/pty.js +29 -3
  161. package/packages/pi-coding-agent/dist/core/tools/pty.js.map +1 -1
  162. package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.js +1 -1
  163. package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.js.map +1 -1
  164. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  165. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +12 -2
  166. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  167. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +7 -2
  168. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  169. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +23 -4
  170. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  171. package/packages/pi-coding-agent/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  172. package/packages/pi-coding-agent/dist/modes/interactive/components/thinking-selector.js +1 -2
  173. package/packages/pi-coding-agent/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  174. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  175. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +9 -0
  176. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  177. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  178. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +53 -2
  179. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  180. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts +2 -2
  181. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  182. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +10 -6
  183. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
  184. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +1 -1
  185. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  186. package/packages/pi-coding-agent/dist/modes/print-mode.d.ts.map +1 -1
  187. package/packages/pi-coding-agent/dist/modes/print-mode.js +6 -0
  188. package/packages/pi-coding-agent/dist/modes/print-mode.js.map +1 -1
  189. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  190. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +20 -0
  191. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  192. package/packages/pi-coding-agent/dist/tests/path-display.test.js +15 -0
  193. package/packages/pi-coding-agent/dist/tests/path-display.test.js.map +1 -1
  194. package/packages/pi-coding-agent/package.json +1 -1
  195. package/packages/pi-coding-agent/src/cli/args.ts +2 -2
  196. package/packages/pi-coding-agent/src/core/agent-session.ts +58 -21
  197. package/packages/pi-coding-agent/src/core/lsp/lsp.md +3 -1
  198. package/packages/pi-coding-agent/src/core/sdk.test.ts +45 -0
  199. package/packages/pi-coding-agent/src/core/sdk.ts +35 -6
  200. package/packages/pi-coding-agent/src/core/session-manager.ts +12 -0
  201. package/packages/pi-coding-agent/src/core/settings-manager.ts +32 -9
  202. package/packages/pi-coding-agent/src/core/skills.ts +4 -1
  203. package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -1
  204. package/packages/pi-coding-agent/src/core/system-prompt.ts +8 -2
  205. package/packages/pi-coding-agent/src/core/tools/grep.ts +1 -1
  206. package/packages/pi-coding-agent/src/core/tools/index.ts +3 -0
  207. package/packages/pi-coding-agent/src/core/tools/pty.ts +45 -6
  208. package/packages/pi-coding-agent/src/modes/interactive/components/embedded-terminal.ts +1 -1
  209. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +10 -2
  210. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +31 -7
  211. package/packages/pi-coding-agent/src/modes/interactive/components/thinking-selector.ts +1 -2
  212. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +9 -0
  213. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +65 -3
  214. package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +11 -7
  215. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +1 -1
  216. package/packages/pi-coding-agent/src/modes/print-mode.ts +6 -0
  217. package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +29 -0
  218. package/packages/pi-coding-agent/src/tests/path-display.test.ts +17 -0
  219. package/packages/pi-tui/dist/components/loader.d.ts +5 -2
  220. package/packages/pi-tui/dist/components/loader.d.ts.map +1 -1
  221. package/packages/pi-tui/dist/components/loader.js +33 -3
  222. package/packages/pi-tui/dist/components/loader.js.map +1 -1
  223. package/packages/pi-tui/src/components/loader.ts +31 -3
  224. package/packages/rpc-client/src/index.ts +1 -1
  225. package/packages/rpc-client/src/rpc-client.ts +29 -0
  226. package/packages/rpc-client/src/rpc-types.ts +1 -1
  227. package/pkg/dist/modes/interactive/theme/theme.d.ts +2 -2
  228. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  229. package/pkg/dist/modes/interactive/theme/theme.js +10 -6
  230. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
  231. package/pkg/dist/modes/interactive/theme/themes.js +1 -1
  232. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  233. package/pkg/package.json +1 -1
  234. package/src/resources/agents/generic.md +1 -0
  235. package/src/resources/agents/scout.md +8 -1
  236. package/src/resources/agents/worker.md +1 -0
  237. package/src/resources/extensions/ask-user-questions.ts +88 -0
  238. package/src/resources/extensions/bg-shell/bg-shell-tool.ts +6 -16
  239. package/src/resources/extensions/mac-tools/index.ts +19 -34
  240. package/src/resources/extensions/memory/index.ts +22 -2
  241. package/src/resources/extensions/shared/interview-ui.ts +108 -15
  242. package/src/resources/extensions/shared/tests/ask-user-freetext.test.ts +61 -0
  243. package/src/resources/extensions/shared/tests/custom-ui-fallbacks.test.ts +46 -0
  244. package/src/resources/extensions/slash-commands/plan.ts +18 -19
  245. package/src/resources/extensions/slash-commands/tools.ts +43 -4
  246. package/src/resources/extensions/subagent/agent-switcher-component.ts +228 -0
  247. package/src/resources/extensions/subagent/agent-switcher-model.ts +160 -0
  248. package/src/resources/extensions/subagent/background-job-manager.ts +29 -6
  249. package/src/resources/extensions/subagent/background-runner.ts +8 -0
  250. package/src/resources/extensions/subagent/background-types.ts +4 -0
  251. package/src/resources/extensions/subagent/index.ts +834 -19
  252. package/src/resources/extensions/subagent/launch-helpers.ts +15 -4
@@ -18,7 +18,7 @@ import * as fs from "node:fs";
18
18
  import * as os from "node:os";
19
19
  import * as path from "node:path";
20
20
  import type { AgentToolResult } from "@gsd/pi-agent-core";
21
- import type { Message } from "@gsd/pi-ai";
21
+ import type { ImageContent, Message } from "@gsd/pi-ai";
22
22
  import { StringEnum } from "@gsd/pi-ai";
23
23
  import {
24
24
  type ExtensionAPI,
@@ -49,6 +49,11 @@ import { loadEffectivePreferences } from "../shared/preferences.js";
49
49
  import { CmuxClient, shellEscape } from "../cmux/index.js";
50
50
  import { BackgroundJobManager, type BackgroundSubagentJob } from "./background-job-manager.js";
51
51
  import { runSubagentInBackground } from "./background-runner.js";
52
+ import { showAgentSwitcher } from "./agent-switcher-component.js";
53
+ import {
54
+ buildAgentSwitchTargets,
55
+ type AgentSwitchTarget,
56
+ } from "./agent-switcher-model.js";
52
57
 
53
58
  const MAX_PARALLEL_TASKS = 8;
54
59
  const MAX_CONCURRENCY = 4;
@@ -56,6 +61,207 @@ const COLLAPSED_ITEM_COUNT = 10;
56
61
  const DEFAULT_AWAIT_SUBAGENT_TIMEOUT_SECONDS = 120;
57
62
  const liveSubagentProcesses = new Set<ChildProcess>();
58
63
 
64
+ type AgentSessionState = "running" | "completed" | "failed";
65
+
66
+ interface AgentSessionLink {
67
+ id: string;
68
+ agentName: string;
69
+ task: string;
70
+ parentSessionFile: string;
71
+ subagentSessionFile: string;
72
+ createdAt: number;
73
+ updatedAt: number;
74
+ state: AgentSessionState;
75
+ }
76
+
77
+ const agentSessionLinksById = new Map<string, AgentSessionLink>();
78
+ const agentSessionIdsByParent = new Map<string, string[]>();
79
+ const parentSessionByChild = new Map<string, string>();
80
+
81
+ interface LiveSubagentRuntime {
82
+ sessionFile?: string;
83
+ parentSessionFile?: string;
84
+ agentName: string;
85
+ isBusy: () => boolean;
86
+ sendPrompt: (text: string, images?: ImageContent[]) => Promise<void>;
87
+ sendSteer: (text: string, images?: ImageContent[]) => Promise<void>;
88
+ sendFollowUp: (text: string, images?: ImageContent[]) => Promise<void>;
89
+ }
90
+
91
+ const liveRuntimeBySessionFile = new Map<string, LiveSubagentRuntime>();
92
+ let agentSessionLinkCounter = 0;
93
+
94
+ function listSessionFiles(sessionDir: string): string[] {
95
+ if (!fs.existsSync(sessionDir)) return [];
96
+ try {
97
+ return fs
98
+ .readdirSync(sessionDir)
99
+ .filter((name) => name.endsWith(".jsonl"))
100
+ .map((name) => path.join(sessionDir, name));
101
+ } catch {
102
+ return [];
103
+ }
104
+ }
105
+
106
+ function detectNewSubagentSessionFile(sessionDir: string, before: Set<string>, startedAt: number): string | undefined {
107
+ const after = listSessionFiles(sessionDir);
108
+ const created = after.filter((file) => !before.has(file));
109
+ const candidates = created.length > 0 ? created : after;
110
+ const ranked = candidates
111
+ .map((file) => {
112
+ let mtime = 0;
113
+ try {
114
+ mtime = fs.statSync(file).mtimeMs;
115
+ } catch {
116
+ mtime = 0;
117
+ }
118
+ return { file, mtime };
119
+ })
120
+ .filter((entry) => entry.mtime >= startedAt - 5000)
121
+ .sort((a, b) => b.mtime - a.mtime);
122
+ return ranked[0]?.file;
123
+ }
124
+
125
+ function registerAgentSessionLink(link: Omit<AgentSessionLink, "id" | "createdAt" | "updatedAt">): AgentSessionLink {
126
+ const now = Date.now();
127
+ const id = `agent-${++agentSessionLinkCounter}`;
128
+ const full: AgentSessionLink = { ...link, id, createdAt: now, updatedAt: now };
129
+ agentSessionLinksById.set(id, full);
130
+ const list = agentSessionIdsByParent.get(link.parentSessionFile) ?? [];
131
+ list.push(id);
132
+ agentSessionIdsByParent.set(link.parentSessionFile, list);
133
+ parentSessionByChild.set(link.subagentSessionFile, link.parentSessionFile);
134
+ return full;
135
+ }
136
+
137
+ function updateAgentSessionLinkState(subagentSessionFile: string, state: AgentSessionState): void {
138
+ for (const link of agentSessionLinksById.values()) {
139
+ if (link.subagentSessionFile === subagentSessionFile) {
140
+ link.state = state;
141
+ link.updatedAt = Date.now();
142
+ return;
143
+ }
144
+ }
145
+ }
146
+
147
+ function upsertAgentSessionLink(
148
+ agentName: string,
149
+ task: string,
150
+ parentSessionFile: string,
151
+ subagentSessionFile: string,
152
+ state: AgentSessionState,
153
+ ): void {
154
+ const existingParent = parentSessionByChild.get(subagentSessionFile);
155
+ if (!existingParent) {
156
+ registerAgentSessionLink({
157
+ agentName,
158
+ task,
159
+ parentSessionFile,
160
+ subagentSessionFile,
161
+ state,
162
+ });
163
+ return;
164
+ }
165
+
166
+ updateAgentSessionLinkState(subagentSessionFile, state);
167
+ }
168
+
169
+ function getAgentSessionLinksForParent(parentSessionFile: string): AgentSessionLink[] {
170
+ const ids = agentSessionIdsByParent.get(parentSessionFile) ?? [];
171
+ return ids
172
+ .map((id) => agentSessionLinksById.get(id))
173
+ .filter((entry): entry is AgentSessionLink => Boolean(entry))
174
+ .sort((a, b) => b.updatedAt - a.updatedAt);
175
+ }
176
+
177
+ function readSessionHeader(sessionFile: string): {
178
+ parentSession?: string;
179
+ subagentName?: string;
180
+ subagentTask?: string;
181
+ subagentSystemPrompt?: string;
182
+ subagentTools?: string[];
183
+ } | null {
184
+ try {
185
+ const content = fs.readFileSync(sessionFile, "utf-8");
186
+ const firstLine = content.split("\n").find((line) => line.trim().length > 0);
187
+ if (!firstLine) return null;
188
+ const parsed = JSON.parse(firstLine);
189
+ if (!parsed || parsed.type !== "session") return null;
190
+ return {
191
+ parentSession: typeof parsed.parentSession === "string" ? parsed.parentSession : undefined,
192
+ subagentName: typeof parsed.subagentName === "string" ? parsed.subagentName : undefined,
193
+ subagentTask: typeof parsed.subagentTask === "string" ? parsed.subagentTask : undefined,
194
+ subagentSystemPrompt: typeof parsed.subagentSystemPrompt === "string" ? parsed.subagentSystemPrompt : undefined,
195
+ subagentTools: Array.isArray(parsed.subagentTools)
196
+ ? parsed.subagentTools.filter((tool: unknown): tool is string => typeof tool === "string")
197
+ : undefined,
198
+ };
199
+ } catch {
200
+ return null;
201
+ }
202
+ }
203
+
204
+ function backfillAgentSessionLinksForParent(parentSessionFile: string, sessionDir: string): AgentSessionLink[] {
205
+ for (const sessionFile of listSessionFiles(sessionDir)) {
206
+ if (sessionFile === parentSessionFile) continue;
207
+ const header = readSessionHeader(sessionFile);
208
+ if (header?.parentSession !== parentSessionFile) continue;
209
+ const existingParent = parentSessionByChild.get(sessionFile);
210
+ if (!existingParent) {
211
+ registerAgentSessionLink({
212
+ agentName: header.subagentName ?? "subagent",
213
+ task: header.subagentTask ?? "Recovered from persisted session lineage",
214
+ parentSessionFile,
215
+ subagentSessionFile: sessionFile,
216
+ state: "completed",
217
+ });
218
+ }
219
+ }
220
+ return getAgentSessionLinksForParent(parentSessionFile);
221
+ }
222
+
223
+ function formatSwitchTargetSummary(target: AgentSwitchTarget): string {
224
+ const current = target.isCurrent ? " (current)" : "";
225
+ if (target.kind === "parent") {
226
+ return `● parent — main session${current}`;
227
+ }
228
+
229
+ const icon = target.state === "running" ? "▶" : target.state === "failed" ? "✗" : "✓";
230
+ return `${icon} ${target.agentName} — ${target.taskPreview}${current}`;
231
+ }
232
+
233
+ function buildSwitchTargetsForParent(
234
+ parentSessionFile: string,
235
+ currentSessionFile: string,
236
+ currentCwd: string,
237
+ trackedLinks: AgentSessionLink[],
238
+ runningJobs: BackgroundSubagentJob[],
239
+ ): AgentSwitchTarget[] {
240
+ return buildAgentSwitchTargets({
241
+ currentSessionFile,
242
+ rootParentSessionFile: parentSessionFile,
243
+ currentCwd,
244
+ trackedLinks: trackedLinks.map((link) => ({
245
+ id: link.id,
246
+ agentName: link.agentName,
247
+ task: link.task,
248
+ parentSessionFile: link.parentSessionFile,
249
+ subagentSessionFile: link.subagentSessionFile,
250
+ updatedAt: link.updatedAt,
251
+ state: link.state,
252
+ })),
253
+ runningJobs: runningJobs.map((job) => ({
254
+ id: job.id,
255
+ agentName: job.agentName,
256
+ task: job.task,
257
+ startedAt: job.startedAt,
258
+ parentSessionFile: job.parentSessionFile,
259
+ sessionFile: job.sessionFile,
260
+ cwd: job.cwd,
261
+ })),
262
+ });
263
+ }
264
+
59
265
  const AwaitSubagentParams = Type.Object({
60
266
  jobs: Type.Optional(
61
267
  Type.Array(Type.String(), {
@@ -325,15 +531,31 @@ interface SingleResult {
325
531
  errorMessage?: string;
326
532
  step?: number;
327
533
  backgroundJobId?: string;
534
+ sessionFile?: string;
535
+ parentSessionFile?: string;
328
536
  }
329
537
 
538
+ type BackgroundResultPayload = {
539
+ summary: string;
540
+ stderr: string;
541
+ exitCode: number;
542
+ model?: string;
543
+ sessionFile?: string;
544
+ parentSessionFile?: string;
545
+ };
546
+
330
547
  interface ForegroundSingleRunControl {
331
548
  agentName: string;
332
549
  task: string;
333
550
  cwd: string;
551
+ parentSessionFile?: string;
334
552
  abortController: AbortController;
335
- resultPromise: Promise<{ summary: string; stderr: string; exitCode: number; model?: string }>;
553
+ resultPromise: Promise<BackgroundResultPayload>;
336
554
  adoptToBackground: (jobId: string) => boolean;
555
+ sendPrompt?: (text: string, images?: ImageContent[]) => Promise<void>;
556
+ sendSteer?: (text: string, images?: ImageContent[]) => Promise<void>;
557
+ sendFollowUp?: (text: string, images?: ImageContent[]) => Promise<void>;
558
+ isBusy?: () => boolean;
337
559
  }
338
560
 
339
561
  interface ForegroundSingleRunHooks {
@@ -447,7 +669,10 @@ function processSubagentEventLine(
447
669
  line: string,
448
670
  currentResult: SingleResult,
449
671
  emitUpdate: () => void,
450
- proc?: ChildProcess,
672
+ proc: ChildProcess | undefined,
673
+ onSessionInfo?: (info: { sessionFile?: string; parentSessionFile?: string }) => void,
674
+ onEventType?: (eventType: string) => void,
675
+ onParsedEvent?: (event: any) => void,
451
676
  ): boolean {
452
677
  if (!line.trim()) return false;
453
678
  let event: any;
@@ -457,6 +682,29 @@ function processSubagentEventLine(
457
682
  return false;
458
683
  }
459
684
 
685
+ const eventType = typeof event.type === "string" ? event.type : "unknown";
686
+ onEventType?.(eventType);
687
+ onParsedEvent?.(event);
688
+
689
+ if (event.type === "subagent_session_info") {
690
+ let changed = false;
691
+ if (typeof event.sessionFile === "string" && event.sessionFile) {
692
+ if (currentResult.sessionFile !== event.sessionFile) changed = true;
693
+ currentResult.sessionFile = event.sessionFile;
694
+ }
695
+ if (typeof event.parentSessionFile === "string" && event.parentSessionFile) {
696
+ if (currentResult.parentSessionFile !== event.parentSessionFile) changed = true;
697
+ currentResult.parentSessionFile = event.parentSessionFile;
698
+ }
699
+ if (changed) {
700
+ onSessionInfo?.({
701
+ sessionFile: currentResult.sessionFile,
702
+ parentSessionFile: currentResult.parentSessionFile,
703
+ });
704
+ }
705
+ return false;
706
+ }
707
+
460
708
  if (proc && isSubagentPermissionRequest(event)) {
461
709
  void handleSubagentPermissionRequest(event, proc);
462
710
  return false;
@@ -516,6 +764,10 @@ async function runSingleAgent(
516
764
  signal: AbortSignal | undefined,
517
765
  onUpdate: OnUpdateCallback | undefined,
518
766
  makeDetails: (results: SingleResult[]) => SubagentDetails,
767
+ parentSessionFile: string | undefined,
768
+ attachableSession: boolean,
769
+ onSessionInfo?: (info: { sessionFile?: string; parentSessionFile?: string }) => void,
770
+ onSubagentEvent?: (event: any, currentResult: SingleResult) => void,
519
771
  foregroundHooks?: ForegroundSingleRunHooks,
520
772
  ): Promise<SingleResult> {
521
773
  const agent = agents.find((a) => a.name === agentName);
@@ -555,6 +807,7 @@ async function runSingleAgent(
555
807
  usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
556
808
  model: inferredModel,
557
809
  step,
810
+ parentSessionFile,
558
811
  };
559
812
 
560
813
  const emitUpdate = () => {
@@ -593,12 +846,23 @@ async function runSingleAgent(
593
846
  tmpPromptDir = tmp.dir;
594
847
  tmpPromptPath = tmp.filePath;
595
848
  }
596
- const args = buildSubagentProcessArgs(agent, task, tmpPromptPath, inferredModel);
849
+ const effectiveCwd = cwd ?? defaultCwd;
850
+ const subagentSessionDir = parentSessionFile ? path.dirname(parentSessionFile) : undefined;
851
+ const sessionFilesBefore = attachableSession && subagentSessionDir
852
+ ? new Set(listSessionFiles(subagentSessionDir))
853
+ : undefined;
854
+ const launchStartedAt = Date.now();
855
+
856
+ const args = buildSubagentProcessArgs(agent, task, tmpPromptPath, inferredModel, {
857
+ noSession: !attachableSession,
858
+ parentSessionFile: parentSessionFile,
859
+ mode: attachableSession ? "rpc" : "json",
860
+ });
597
861
 
598
862
  const exitCode = await new Promise<number>((resolve) => {
599
863
  const bundledPaths = getBundledExtensionPathsFromEnv();
600
864
  const extensionArgs = bundledPaths.flatMap((p) => ["--extension", p]);
601
- const cliPath = resolveSubagentCliPath(cwd ?? defaultCwd);
865
+ const cliPath = resolveSubagentCliPath(effectiveCwd);
602
866
  if (!cliPath) {
603
867
  currentResult.stderr += "Unable to resolve LSD/GSD CLI path for subagent launch.";
604
868
  resolve(1);
@@ -607,7 +871,7 @@ async function runSingleAgent(
607
871
  const proc = spawn(
608
872
  process.execPath,
609
873
  [cliPath, ...extensionArgs, ...args],
610
- { cwd: cwd ?? defaultCwd, shell: false, stdio: ["pipe", "pipe", "pipe"] },
874
+ { cwd: effectiveCwd, shell: false, stdio: ["pipe", "pipe", "pipe"] },
611
875
  );
612
876
  // Keep stdin open so approval/classifier responses can be proxied back
613
877
  // into the child process. Closing it here can leave the subagent stuck
@@ -617,14 +881,26 @@ async function runSingleAgent(
617
881
  let completionSeen = false;
618
882
  let resolved = false;
619
883
  let foregroundReleased = false;
884
+ let isBusy = false;
885
+ let commandSeq = 0;
886
+ const pendingCommandResponses = new Map<string, { resolve: (data: any) => void; reject: (error: Error) => void }>();
620
887
  const procAbortController = new AbortController();
621
- let resolveBackgroundResult: ((value: { summary: string; stderr: string; exitCode: number; model?: string }) => void) | undefined;
888
+ let resolveBackgroundResult: ((value: BackgroundResultPayload) => void) | undefined;
622
889
  let rejectBackgroundResult: ((reason?: unknown) => void) | undefined;
623
- const backgroundResultPromise = new Promise<{ summary: string; stderr: string; exitCode: number; model?: string }>((resolveBg, rejectBg) => {
890
+ const backgroundResultPromise = new Promise<BackgroundResultPayload>((resolveBg, rejectBg) => {
624
891
  resolveBackgroundResult = resolveBg;
625
892
  rejectBackgroundResult = rejectBg;
626
893
  });
627
894
 
895
+ const sendRpcCommand = async (command: Record<string, unknown>): Promise<any> => {
896
+ const id = `sa_cmd_${++commandSeq}`;
897
+ if (!proc.stdin) throw new Error("Subagent RPC stdin is not available.");
898
+ return new Promise((resolveCmd, rejectCmd) => {
899
+ pendingCommandResponses.set(id, { resolve: resolveCmd, reject: rejectCmd });
900
+ proc.stdin!.write(JSON.stringify({ id, ...command }) + "\n");
901
+ });
902
+ };
903
+
628
904
  const finishForeground = (code: number) => {
629
905
  if (resolved) return;
630
906
  resolved = true;
@@ -648,9 +924,26 @@ async function runSingleAgent(
648
924
  agentName,
649
925
  task,
650
926
  cwd: cwd ?? defaultCwd,
927
+ parentSessionFile,
651
928
  abortController: procAbortController,
652
929
  resultPromise: backgroundResultPromise,
653
930
  adoptToBackground,
931
+ sendPrompt: attachableSession
932
+ ? async (text: string, images?: ImageContent[]) => {
933
+ await sendRpcCommand({ type: "prompt", message: text, images });
934
+ }
935
+ : undefined,
936
+ sendSteer: attachableSession
937
+ ? async (text: string, images?: ImageContent[]) => {
938
+ await sendRpcCommand({ type: "steer", message: text, images });
939
+ }
940
+ : undefined,
941
+ sendFollowUp: attachableSession
942
+ ? async (text: string, images?: ImageContent[]) => {
943
+ await sendRpcCommand({ type: "follow_up", message: text, images });
944
+ }
945
+ : undefined,
946
+ isBusy: attachableSession ? () => isBusy : undefined,
654
947
  });
655
948
 
656
949
  proc.stdout.on("data", (data) => {
@@ -658,7 +951,30 @@ async function runSingleAgent(
658
951
  const lines = buffer.split("\n");
659
952
  buffer = lines.pop() || "";
660
953
  for (const line of lines) {
661
- if (processSubagentEventLine(line, currentResult, emitUpdate, proc)) {
954
+ const trimmed = line.trim();
955
+ if (!trimmed) continue;
956
+ if (attachableSession) {
957
+ try {
958
+ const parsed = JSON.parse(trimmed);
959
+ if (parsed?.type === "response" && typeof parsed.id === "string" && pendingCommandResponses.has(parsed.id)) {
960
+ const pending = pendingCommandResponses.get(parsed.id)!;
961
+ pendingCommandResponses.delete(parsed.id);
962
+ if (parsed.success === false) {
963
+ pending.reject(new Error(typeof parsed.error === "string" ? parsed.error : "Subagent RPC command failed."));
964
+ } else {
965
+ pending.resolve(parsed.data);
966
+ }
967
+ continue;
968
+ }
969
+ } catch {
970
+ // Fall through to generic event processing.
971
+ }
972
+ }
973
+
974
+ if (processSubagentEventLine(trimmed, currentResult, emitUpdate, proc, onSessionInfo, (eventType) => {
975
+ if (eventType === "agent_start") isBusy = true;
976
+ if (eventType === "agent_end") isBusy = false;
977
+ }, (event) => onSubagentEvent?.(event, currentResult))) {
662
978
  completionSeen = true;
663
979
  try {
664
980
  proc.kill("SIGTERM");
@@ -676,16 +992,33 @@ async function runSingleAgent(
676
992
  proc.on("close", (code) => {
677
993
  liveSubagentProcesses.delete(proc);
678
994
  if (buffer.trim()) {
679
- const completedOnFlush = processSubagentEventLine(buffer, currentResult, emitUpdate, proc);
995
+ const completedOnFlush = processSubagentEventLine(buffer, currentResult, emitUpdate, proc, onSessionInfo, (eventType) => {
996
+ if (eventType === "agent_start") isBusy = true;
997
+ if (eventType === "agent_end") isBusy = false;
998
+ }, (event) => onSubagentEvent?.(event, currentResult));
680
999
  completionSeen = completionSeen || completedOnFlush;
681
1000
  }
1001
+ isBusy = false;
1002
+ for (const pending of pendingCommandResponses.values()) {
1003
+ pending.reject(new Error("Subagent process closed before command response."));
1004
+ }
1005
+ pendingCommandResponses.clear();
1006
+
682
1007
  const finalExitCode = completionSeen && (code === null || code === 143 || code === 15) ? 0 : (code ?? 0);
683
1008
  currentResult.exitCode = finalExitCode;
1009
+
1010
+ if (attachableSession && sessionFilesBefore && subagentSessionDir && !currentResult.sessionFile) {
1011
+ const detected = detectNewSubagentSessionFile(subagentSessionDir, sessionFilesBefore, launchStartedAt);
1012
+ if (detected) currentResult.sessionFile = detected;
1013
+ }
1014
+
684
1015
  resolveBackgroundResult?.({
685
1016
  summary: getFinalOutput(currentResult.messages),
686
1017
  stderr: currentResult.stderr,
687
1018
  exitCode: finalExitCode,
688
1019
  model: currentResult.model,
1020
+ sessionFile: currentResult.sessionFile,
1021
+ parentSessionFile: currentResult.parentSessionFile,
689
1022
  });
690
1023
  foregroundHooks?.onFinish?.();
691
1024
  finishForeground(finalExitCode);
@@ -693,12 +1026,32 @@ async function runSingleAgent(
693
1026
 
694
1027
  proc.on("error", (error) => {
695
1028
  liveSubagentProcesses.delete(proc);
1029
+ isBusy = false;
1030
+ for (const pending of pendingCommandResponses.values()) {
1031
+ pending.reject(error instanceof Error ? error : new Error(String(error)));
1032
+ }
1033
+ pendingCommandResponses.clear();
696
1034
  rejectBackgroundResult?.(error);
697
1035
  foregroundHooks?.onFinish?.();
698
1036
  finishForeground(1);
699
1037
  });
700
1038
 
1039
+ if (attachableSession) {
1040
+ void sendRpcCommand({ type: "prompt", message: task }).catch((error) => {
1041
+ currentResult.stderr += error instanceof Error ? error.message : String(error);
1042
+ try {
1043
+ proc.kill("SIGTERM");
1044
+ } catch {
1045
+ /* ignore */
1046
+ }
1047
+ });
1048
+ }
1049
+
701
1050
  const killProc = () => {
1051
+ // If the process has been adopted to the background (e.g. via Ctrl+B or
1052
+ // /agent attach_live), foregroundReleased is true and the process should
1053
+ // survive the parent session's abort — don't kill it.
1054
+ if (foregroundReleased) return;
702
1055
  wasAborted = true;
703
1056
  procAbortController.abort();
704
1057
  proc.kill("SIGTERM");
@@ -725,6 +1078,12 @@ async function runSingleAgent(
725
1078
  });
726
1079
 
727
1080
  currentResult.exitCode = exitCode;
1081
+ if (attachableSession && sessionFilesBefore && subagentSessionDir) {
1082
+ const detected = detectNewSubagentSessionFile(subagentSessionDir, sessionFilesBefore, launchStartedAt);
1083
+ if (detected) {
1084
+ currentResult.sessionFile = detected;
1085
+ }
1086
+ }
728
1087
  if (wasAborted) throw new Error("Subagent was aborted");
729
1088
  return currentResult;
730
1089
  } finally {
@@ -805,15 +1164,68 @@ export default function(pi: ExtensionAPI) {
805
1164
  const foregroundSubagentHint = "Ctrl+B: move foreground subagent to background";
806
1165
  type ActiveForegroundSubagent = ForegroundSingleRunControl & { claimed: boolean };
807
1166
  let activeForegroundSubagent: ActiveForegroundSubagent | null = null;
1167
+ let activeSessionFileForUi: string | undefined;
1168
+ const liveStreamBufferBySession = new Map<string, string>();
1169
+
1170
+ function flushLiveStream(sessionFile: string): void {
1171
+ const buffered = liveStreamBufferBySession.get(sessionFile);
1172
+ if (!buffered || !buffered.trim()) return;
1173
+ liveStreamBufferBySession.set(sessionFile, "");
1174
+ pi.sendMessage(
1175
+ {
1176
+ customType: "live_subagent_stream",
1177
+ content: buffered,
1178
+ display: true,
1179
+ },
1180
+ { deliverAs: "followUp" },
1181
+ );
1182
+ }
1183
+
1184
+ function pushLiveStreamDelta(sessionFile: string, delta: string): void {
1185
+ const prev = liveStreamBufferBySession.get(sessionFile) ?? "";
1186
+ const next = prev + delta;
1187
+ liveStreamBufferBySession.set(sessionFile, next);
1188
+ if (next.length >= 120 || next.includes("\n")) {
1189
+ flushLiveStream(sessionFile);
1190
+ }
1191
+ }
1192
+
1193
+ function getCurrentSessionSubagentMetadata(sessionFile: string | undefined) {
1194
+ if (!sessionFile) return null;
1195
+ return readSessionHeader(sessionFile);
1196
+ }
1197
+
1198
+ function applyCurrentSessionSubagentTools(ctx: any): void {
1199
+ const metadata = getCurrentSessionSubagentMetadata(ctx.sessionManager.getSessionFile());
1200
+ if (metadata?.subagentTools && metadata.subagentTools.length > 0) {
1201
+ ctx.setActiveTools(metadata.subagentTools);
1202
+ }
1203
+ }
808
1204
 
809
1205
  function getBgManager(): BackgroundJobManager {
810
1206
  if (!bgManager) throw new Error("BackgroundJobManager not initialized.");
811
1207
  return bgManager;
812
1208
  }
813
1209
 
814
- pi.on("session_start", async () => {
1210
+ pi.on("session_start", async (_event, ctx) => {
1211
+ activeSessionFileForUi = ctx.sessionManager.getSessionFile();
815
1212
  bgManager = new BackgroundJobManager({
816
1213
  onJobComplete: (job) => {
1214
+ if (job.sessionFile && job.parentSessionFile) {
1215
+ const existingParent = parentSessionByChild.get(job.sessionFile);
1216
+ if (!existingParent) {
1217
+ registerAgentSessionLink({
1218
+ agentName: job.agentName,
1219
+ task: job.task,
1220
+ parentSessionFile: job.parentSessionFile,
1221
+ subagentSessionFile: job.sessionFile,
1222
+ state: job.status === "failed" ? "failed" : "completed",
1223
+ });
1224
+ } else {
1225
+ updateAgentSessionLinkState(job.sessionFile, job.status === "failed" ? "failed" : "completed");
1226
+ }
1227
+ }
1228
+
817
1229
  if (job.awaited) return;
818
1230
  const statusEmoji = job.status === "completed" ? "✓" : job.status === "cancelled" ? "✗ cancelled" : "✗ failed";
819
1231
  const elapsed = ((Date.now() - job.startedAt) / 1000).toFixed(1);
@@ -839,25 +1251,82 @@ export default function(pi: ExtensionAPI) {
839
1251
  );
840
1252
  },
841
1253
  });
1254
+ applyCurrentSessionSubagentTools(ctx);
842
1255
  });
843
1256
 
1257
+ pi.on("session_switch", async (_event, ctx) => {
1258
+ activeSessionFileForUi = ctx.sessionManager.getSessionFile();
1259
+ applyCurrentSessionSubagentTools(ctx);
1260
+ });
844
1261
 
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);
1262
+ pi.on("before_agent_start", async (event, ctx) => {
1263
+ const metadata = getCurrentSessionSubagentMetadata(ctx.sessionManager.getSessionFile());
1264
+ if (!metadata?.subagentSystemPrompt) return;
1265
+ const subagentName = metadata.subagentName ?? "subagent";
1266
+ const taskNote = metadata.subagentTask
1267
+ ? `Original delegated task: ${metadata.subagentTask}`
1268
+ : "Continue operating as the delegated subagent for this session.";
1269
+ const antiRecursion = [
1270
+ `You are already the ${subagentName} subagent for this session.`,
1271
+ "Do not spawn or delegate to another subagent with the same name as yourself.",
1272
+ `If the user asks you to continue ${subagentName} work, do that work directly in this session.`,
1273
+ taskNote,
1274
+ "IMPORTANT: There is NO human available to answer questions in this session. Do NOT call ask_user_questions. Make all decisions autonomously based on the task and context.",
1275
+ ].join("\n");
1276
+ return {
1277
+ systemPrompt: `${event.systemPrompt}\n\n${antiRecursion}\n\n${metadata.subagentSystemPrompt}`,
1278
+ };
1279
+ });
1280
+ pi.on("input", async (event, ctx) => {
1281
+ const sessionFile = ctx.sessionManager.getSessionFile();
1282
+ if (!sessionFile) return;
1283
+ const runtime = liveRuntimeBySessionFile.get(sessionFile);
1284
+ if (!runtime) return;
1285
+
1286
+ const text = event.text?.trim();
1287
+ if (!text) return { action: "handled" as const };
1288
+
1289
+ const isSlashCommand = text.startsWith("/");
1290
+ if (isSlashCommand) return;
1291
+
1292
+ try {
1293
+ if (runtime.isBusy()) {
1294
+ await runtime.sendSteer(text, event.images);
1295
+ ctx.ui.notify(`Sent steer to running subagent ${runtime.agentName}.`, "info");
1296
+ } else {
1297
+ await runtime.sendPrompt(text, event.images);
1298
+ ctx.ui.notify(`Sent prompt to live subagent ${runtime.agentName}.`, "info");
850
1299
  }
1300
+ return { action: "handled" as const };
1301
+ } catch (error) {
1302
+ ctx.ui.notify(
1303
+ `Failed to send input to live subagent ${runtime.agentName}: ${error instanceof Error ? error.message : String(error)}`,
1304
+ "error",
1305
+ );
1306
+ return { action: "handled" as const };
851
1307
  }
852
1308
  });
853
1309
 
1310
+
1311
+ pi.on("session_before_switch", async () => {
1312
+ if (activeSessionFileForUi) flushLiveStream(activeSessionFileForUi);
1313
+ activeForegroundSubagent = null;
1314
+ });
1315
+
854
1316
  pi.on("session_shutdown", async () => {
1317
+ if (activeSessionFileForUi) flushLiveStream(activeSessionFileForUi);
1318
+ activeSessionFileForUi = undefined;
855
1319
  activeForegroundSubagent = null;
856
1320
  await stopLiveSubagents();
857
1321
  if (bgManager) {
858
1322
  bgManager.shutdown();
859
1323
  bgManager = null;
860
1324
  }
1325
+ agentSessionLinksById.clear();
1326
+ agentSessionIdsByParent.clear();
1327
+ parentSessionByChild.clear();
1328
+ liveRuntimeBySessionFile.clear();
1329
+ liveStreamBufferBySession.clear();
861
1330
  });
862
1331
 
863
1332
  // /subagents command
@@ -972,6 +1441,221 @@ export default function(pi: ExtensionAPI) {
972
1441
  },
973
1442
  });
974
1443
 
1444
+ // /agent command - switch to the parent or a tracked subagent session
1445
+ pi.registerCommand("agent", {
1446
+ description: "Switch focus to parent/subagent sessions (/agent picker, /agent <id|index|name>, /agent parent)",
1447
+ handler: async (args: string, ctx) => {
1448
+ const currentSessionFile = ctx.sessionManager.getSessionFile();
1449
+ if (!currentSessionFile) {
1450
+ ctx.ui.notify("Current session is in-memory only; /agent requires a persisted session file.", "warning");
1451
+ return;
1452
+ }
1453
+
1454
+ const arg = args.trim();
1455
+ const parentSessionFile = parentSessionByChild.get(currentSessionFile);
1456
+ const currentParent = parentSessionFile ?? currentSessionFile;
1457
+ const currentSessionDir = path.dirname(currentParent);
1458
+
1459
+ let tracked = getAgentSessionLinksForParent(currentParent).filter((entry) => fs.existsSync(entry.subagentSessionFile));
1460
+ if (tracked.length === 0) {
1461
+ tracked = backfillAgentSessionLinksForParent(currentParent, currentSessionDir)
1462
+ .filter((entry) => fs.existsSync(entry.subagentSessionFile));
1463
+ }
1464
+
1465
+ const runningJobs = bgManager?.getRunningJobs() ?? [];
1466
+ const switchTargets = buildSwitchTargetsForParent(
1467
+ currentParent,
1468
+ currentSessionFile,
1469
+ ctx.cwd,
1470
+ tracked,
1471
+ runningJobs,
1472
+ );
1473
+
1474
+ const applySwitchTarget = async (target: AgentSwitchTarget): Promise<void> => {
1475
+ if (target.selectionAction === "blocked") {
1476
+ ctx.ui.notify(target.blockedReason ?? "That target cannot be selected yet.", "warning");
1477
+ return;
1478
+ }
1479
+
1480
+ if (target.selectionAction === "attach_live") {
1481
+ if (!fs.existsSync(target.sessionFile)) {
1482
+ ctx.ui.notify(`Live subagent session file is missing: ${target.sessionFile}`, "error");
1483
+ return;
1484
+ }
1485
+ const liveRuntime = liveRuntimeBySessionFile.get(target.sessionFile);
1486
+ if (!liveRuntime) {
1487
+ ctx.ui.notify("Live runtime is no longer available for this subagent. It may have completed.", "warning");
1488
+ return;
1489
+ }
1490
+
1491
+ // Adopt the foreground subagent to background before switching sessions.
1492
+ // switchSession calls abort() which would fire the tool signal and SIGTERM
1493
+ // the running subagent process. Adopting to background detaches the process
1494
+ // from the foreground abort chain so it survives the session switch.
1495
+ const foreground = activeForegroundSubagent;
1496
+ if (foreground && !foreground.claimed && bgManager) {
1497
+ foreground.claimed = true;
1498
+ try {
1499
+ const jobId = bgManager.adoptRunning(
1500
+ foreground.agentName,
1501
+ foreground.task,
1502
+ foreground.cwd,
1503
+ foreground.abortController,
1504
+ foreground.resultPromise,
1505
+ {
1506
+ parentSessionFile: foreground.parentSessionFile ?? ctx.sessionManager.getSessionFile(),
1507
+ },
1508
+ );
1509
+ const released = foreground.adoptToBackground(jobId);
1510
+ if (!released) {
1511
+ foreground.claimed = false;
1512
+ bgManager.cancel(jobId);
1513
+ } else {
1514
+ activeForegroundSubagent = null;
1515
+ ctx.ui.setStatus(foregroundSubagentStatusKey, undefined);
1516
+ }
1517
+ } catch {
1518
+ foreground.claimed = false;
1519
+ }
1520
+ }
1521
+
1522
+ const switched = await ctx.switchSession(target.sessionFile);
1523
+ if (switched.cancelled) {
1524
+ ctx.ui.notify("Session switch was cancelled.", "warning");
1525
+ return;
1526
+ }
1527
+ 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");
1528
+ return;
1529
+ }
1530
+
1531
+ if (target.kind === "parent") {
1532
+ if (!fs.existsSync(target.sessionFile)) {
1533
+ ctx.ui.notify(`Parent session file not found: ${target.sessionFile}`, "error");
1534
+ return;
1535
+ }
1536
+ const switched = await ctx.switchSession(target.sessionFile);
1537
+ if (switched.cancelled) {
1538
+ ctx.ui.notify("Session switch was cancelled.", "warning");
1539
+ return;
1540
+ }
1541
+ ctx.ui.notify("Switched to parent session.", "info");
1542
+ return;
1543
+ }
1544
+
1545
+ if (!fs.existsSync(target.sessionFile)) {
1546
+ ctx.ui.notify(`Subagent session file is missing: ${target.sessionFile}`, "error");
1547
+ return;
1548
+ }
1549
+
1550
+ const switched = await ctx.switchSession(target.sessionFile);
1551
+ if (switched.cancelled) {
1552
+ ctx.ui.notify("Session switch was cancelled.", "warning");
1553
+ return;
1554
+ }
1555
+ updateAgentSessionLinkState(target.sessionFile, target.state === "failed" ? "failed" : "completed");
1556
+ ctx.ui.notify(`Switched to subagent ${target.agentName}. This resumes the saved subagent session; use /agent parent to return.`, "info");
1557
+ };
1558
+
1559
+ if (!arg) {
1560
+ const subagentTargets = switchTargets.filter((target) => target.kind === "subagent");
1561
+ if (ctx.hasUI) {
1562
+ if (subagentTargets.length === 0 && !parentSessionFile) {
1563
+ ctx.ui.notify("No tracked subagent sessions for this parent session yet. Run a single-mode subagent first (foreground or background).", "info");
1564
+ return;
1565
+ }
1566
+
1567
+ const selected = await showAgentSwitcher(ctx, switchTargets);
1568
+ if (!selected) return;
1569
+ await applySwitchTarget(selected);
1570
+ return;
1571
+ }
1572
+
1573
+ if (subagentTargets.length === 0 && !parentSessionFile) {
1574
+ ctx.ui.notify("No tracked subagent sessions for this parent session yet. Run a single-mode subagent first (foreground or background).", "info");
1575
+ return;
1576
+ }
1577
+
1578
+ const lines = ["Agent switch targets:"];
1579
+ switchTargets.forEach((target, index) => {
1580
+ lines.push(`${index + 1}. ${formatSwitchTargetSummary(target)}`);
1581
+ });
1582
+ lines.push("", "Use `/agent <index|id|name>` for explicit targeting, or `/agent parent`.");
1583
+ ctx.ui.notify(lines.join("\n"), "info");
1584
+ return;
1585
+ }
1586
+
1587
+ if (arg === "parent" || arg === "main") {
1588
+ if (!parentSessionFile) {
1589
+ ctx.ui.notify("You are already in the parent/main session.", "info");
1590
+ return;
1591
+ }
1592
+ if (!fs.existsSync(parentSessionFile)) {
1593
+ ctx.ui.notify(`Parent session file not found: ${parentSessionFile}`, "error");
1594
+ return;
1595
+ }
1596
+ const switched = await ctx.switchSession(parentSessionFile);
1597
+ if (switched.cancelled) {
1598
+ ctx.ui.notify("Session switch was cancelled.", "warning");
1599
+ return;
1600
+ }
1601
+ ctx.ui.notify("Switched to parent session.", "info");
1602
+ return;
1603
+ }
1604
+
1605
+ let target: AgentSessionLink | undefined;
1606
+ if (/^\d+$/.test(arg)) {
1607
+ const index = Number.parseInt(arg, 10) - 1;
1608
+ target = tracked[index];
1609
+ }
1610
+ if (!target) {
1611
+ target = tracked.find((entry) => entry.id === arg);
1612
+ }
1613
+ if (!target) {
1614
+ target = tracked.find((entry) => entry.agentName === arg);
1615
+ }
1616
+ if (!target) {
1617
+ target = tracked.find((entry) => path.basename(entry.subagentSessionFile) === arg);
1618
+ }
1619
+
1620
+ if (!target) {
1621
+ const runningTarget = switchTargets.find((entry) => entry.id === arg && entry.kind === "subagent");
1622
+ if (runningTarget?.state === "running") {
1623
+ ctx.ui.notify(runningTarget.blockedReason ?? "Selected subagent is still running. Live attach is not implemented yet.", "warning");
1624
+ return;
1625
+ }
1626
+ ctx.ui.notify(`Unknown subagent target: ${arg}. Run /agent to list available targets.`, "warning");
1627
+ return;
1628
+ }
1629
+
1630
+ if (!fs.existsSync(target.subagentSessionFile)) {
1631
+ ctx.ui.notify(`Subagent session file is missing: ${target.subagentSessionFile}`, "error");
1632
+ return;
1633
+ }
1634
+
1635
+ if (target.state === "running") {
1636
+ const liveRuntime = liveRuntimeBySessionFile.get(target.subagentSessionFile);
1637
+ if (!liveRuntime) {
1638
+ ctx.ui.notify("Live runtime is no longer available for this subagent. It may have completed.", "warning");
1639
+ return;
1640
+ }
1641
+ const switched = await ctx.switchSession(target.subagentSessionFile);
1642
+ if (switched.cancelled) {
1643
+ ctx.ui.notify("Session switch was cancelled.", "warning");
1644
+ return;
1645
+ }
1646
+ 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");
1647
+ return;
1648
+ }
1649
+
1650
+ const switched = await ctx.switchSession(target.subagentSessionFile);
1651
+ if (switched.cancelled) {
1652
+ ctx.ui.notify("Session switch was cancelled.", "warning");
1653
+ return;
1654
+ }
1655
+ updateAgentSessionLinkState(target.subagentSessionFile, target.state === "failed" ? "failed" : "completed");
1656
+ ctx.ui.notify(`Switched to subagent ${target.agentName}. This resumes the saved subagent session; use /agent parent to return.`, "info");
1657
+ },
1658
+ });
975
1659
  pi.registerShortcut(Key.ctrl("b"), {
976
1660
  description: shortcutDesc("Move foreground subagent to background", "/subagents list"),
977
1661
  handler: async (ctx) => {
@@ -992,6 +1676,9 @@ export default function(pi: ExtensionAPI) {
992
1676
  running.cwd,
993
1677
  running.abortController,
994
1678
  running.resultPromise,
1679
+ {
1680
+ parentSessionFile: running.parentSessionFile ?? ctx.sessionManager.getSessionFile(),
1681
+ },
995
1682
  );
996
1683
  } catch (error) {
997
1684
  running.claimed = false;
@@ -1063,7 +1750,7 @@ export default function(pi: ExtensionAPI) {
1063
1750
  "For broad review or audit requests, use scout only as a prep step; the parent model or a reviewer should make the final judgments.",
1064
1751
  "Skip scout when the user already named the exact file/function to inspect or the task is obviously narrow.",
1065
1752
  "Use parallel mode when tasks are independent and don't need each other's output.",
1066
- "Use background: true when the user wants to keep chatting while a long-running agent works in parallel.",
1753
+ "Default to foreground (background: false) for single-mode subagents. Only set background: true when the user explicitly asks to run it in the background or to keep chatting while it runs.",
1067
1754
  "If the user wants to wait for a background subagent result, use await_subagent.",
1068
1755
  ],
1069
1756
  parameters: SubagentParams,
@@ -1075,6 +1762,7 @@ export default function(pi: ExtensionAPI) {
1075
1762
  const confirmProjectAgents = params.confirmProjectAgents ?? false;
1076
1763
  const cmuxClient = CmuxClient.fromPreferences(loadEffectivePreferences()?.preferences);
1077
1764
  const cmuxSplitsEnabled = cmuxClient.getConfig().splits;
1765
+ const invokingSessionFile = ctx.sessionManager.getSessionFile();
1078
1766
 
1079
1767
  // Resolve isolation mode
1080
1768
  const isolationMode = readIsolationMode();
@@ -1175,6 +1863,10 @@ export default function(pi: ExtensionAPI) {
1175
1863
  signal,
1176
1864
  chainUpdate,
1177
1865
  makeDetails("chain"),
1866
+ invokingSessionFile,
1867
+ false,
1868
+ undefined,
1869
+ undefined,
1178
1870
  );
1179
1871
  results.push(result);
1180
1872
 
@@ -1263,6 +1955,9 @@ export default function(pi: ExtensionAPI) {
1263
1955
  }
1264
1956
  },
1265
1957
  makeDetails("parallel"),
1958
+ invokingSessionFile,
1959
+ false,
1960
+ undefined,
1266
1961
  );
1267
1962
  let result = await runTask();
1268
1963
 
@@ -1337,8 +2032,10 @@ export default function(pi: ExtensionAPI) {
1337
2032
  params.task,
1338
2033
  params.cwd,
1339
2034
  params.model,
1340
- { defaultCwd: ctx.cwd, model: ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined },
2035
+ { defaultCwd: ctx.cwd, model: ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, parentSessionFile: invokingSessionFile },
1341
2036
  async (bgSignal) => {
2037
+ let liveSessionFile: string | undefined;
2038
+ let liveRuntime: LiveSubagentRuntime | undefined;
1342
2039
  const result = await runSingleAgent(
1343
2040
  ctx.cwd,
1344
2041
  agents,
@@ -1351,12 +2048,63 @@ export default function(pi: ExtensionAPI) {
1351
2048
  bgSignal,
1352
2049
  undefined, // no streaming updates for background jobs
1353
2050
  makeDetails("single"),
2051
+ invokingSessionFile,
2052
+ true,
2053
+ (info) => {
2054
+ if (!invokingSessionFile || !info.sessionFile) return;
2055
+ upsertAgentSessionLink(
2056
+ params.agent!,
2057
+ params.task!,
2058
+ invokingSessionFile,
2059
+ info.sessionFile,
2060
+ "running",
2061
+ );
2062
+ liveSessionFile = info.sessionFile;
2063
+ if (liveRuntime) {
2064
+ liveRuntime.sessionFile = info.sessionFile;
2065
+ liveRuntime.parentSessionFile = info.parentSessionFile ?? invokingSessionFile;
2066
+ liveRuntimeBySessionFile.set(info.sessionFile, liveRuntime);
2067
+ }
2068
+ },
2069
+ (event, partial) => {
2070
+ const sessionFile = partial.sessionFile;
2071
+ if (!sessionFile || activeSessionFileForUi !== sessionFile) return;
2072
+ if (event?.type === "message_update" && event.assistantMessageEvent?.type === "text_delta") {
2073
+ const delta = String(event.assistantMessageEvent.delta ?? "");
2074
+ if (delta) pushLiveStreamDelta(sessionFile, delta);
2075
+ }
2076
+ if (event?.type === "message_end") {
2077
+ flushLiveStream(sessionFile);
2078
+ }
2079
+ },
2080
+ {
2081
+ onStart: (control) => {
2082
+ if (!control.sendPrompt || !control.sendSteer || !control.sendFollowUp || !control.isBusy) return;
2083
+ liveRuntime = {
2084
+ sessionFile: liveSessionFile,
2085
+ parentSessionFile: invokingSessionFile,
2086
+ agentName: params.agent!,
2087
+ isBusy: control.isBusy,
2088
+ sendPrompt: control.sendPrompt,
2089
+ sendSteer: control.sendSteer,
2090
+ sendFollowUp: control.sendFollowUp,
2091
+ };
2092
+ if (liveSessionFile) {
2093
+ liveRuntimeBySessionFile.set(liveSessionFile, liveRuntime);
2094
+ }
2095
+ },
2096
+ onFinish: () => {
2097
+ if (liveSessionFile) liveRuntimeBySessionFile.delete(liveSessionFile);
2098
+ },
2099
+ },
1354
2100
  );
1355
2101
  return {
1356
2102
  exitCode: result.exitCode,
1357
2103
  finalOutput: getFinalOutput(result.messages),
1358
2104
  stderr: result.stderr,
1359
2105
  model: result.model,
2106
+ sessionFile: result.sessionFile,
2107
+ parentSessionFile: result.parentSessionFile,
1360
2108
  };
1361
2109
  },
1362
2110
  );
@@ -1387,6 +2135,8 @@ export default function(pi: ExtensionAPI) {
1387
2135
  isolation = await createIsolation(effectiveCwd, taskId, isolationMode);
1388
2136
  }
1389
2137
 
2138
+ let liveSessionFile: string | undefined;
2139
+ let liveRuntime: LiveSubagentRuntime | undefined;
1390
2140
  const result = await runSingleAgent(
1391
2141
  ctx.cwd,
1392
2142
  agents,
@@ -1399,20 +2149,83 @@ export default function(pi: ExtensionAPI) {
1399
2149
  signal,
1400
2150
  onUpdate,
1401
2151
  makeDetails("single"),
2152
+ invokingSessionFile,
2153
+ !isolation,
2154
+ !isolation
2155
+ ? (info) => {
2156
+ if (!invokingSessionFile || !info.sessionFile) return;
2157
+ upsertAgentSessionLink(
2158
+ params.agent!,
2159
+ params.task!,
2160
+ invokingSessionFile,
2161
+ info.sessionFile,
2162
+ "running",
2163
+ );
2164
+ liveSessionFile = info.sessionFile;
2165
+ if (liveRuntime) {
2166
+ liveRuntime.sessionFile = info.sessionFile;
2167
+ liveRuntime.parentSessionFile = info.parentSessionFile ?? invokingSessionFile;
2168
+ liveRuntimeBySessionFile.set(info.sessionFile, liveRuntime);
2169
+ }
2170
+ }
2171
+ : undefined,
2172
+ !isolation
2173
+ ? (event, partial) => {
2174
+ const sessionFile = partial.sessionFile;
2175
+ if (!sessionFile || activeSessionFileForUi !== sessionFile) return;
2176
+ if (event?.type === "message_update" && event.assistantMessageEvent?.type === "text_delta") {
2177
+ const delta = String(event.assistantMessageEvent.delta ?? "");
2178
+ if (delta) pushLiveStreamDelta(sessionFile, delta);
2179
+ }
2180
+ if (event?.type === "message_end") {
2181
+ flushLiveStream(sessionFile);
2182
+ }
2183
+ }
2184
+ : undefined,
1402
2185
  !isolation
1403
2186
  ? {
1404
2187
  onStart: (control) => {
1405
2188
  activeForegroundSubagent = { ...control, claimed: false };
1406
2189
  ctx.ui.setStatus(foregroundSubagentStatusKey, foregroundSubagentHint);
2190
+
2191
+ if (!control.sendPrompt || !control.sendSteer || !control.sendFollowUp || !control.isBusy) return;
2192
+ liveRuntime = {
2193
+ sessionFile: liveSessionFile,
2194
+ parentSessionFile: invokingSessionFile,
2195
+ agentName: params.agent!,
2196
+ isBusy: control.isBusy,
2197
+ sendPrompt: control.sendPrompt,
2198
+ sendSteer: control.sendSteer,
2199
+ sendFollowUp: control.sendFollowUp,
2200
+ };
2201
+ if (liveSessionFile && liveRuntime) {
2202
+ liveRuntimeBySessionFile.set(liveSessionFile, liveRuntime);
2203
+ }
1407
2204
  },
1408
2205
  onFinish: () => {
1409
2206
  activeForegroundSubagent = null;
1410
2207
  ctx.ui.setStatus(foregroundSubagentStatusKey, undefined);
2208
+ if (liveSessionFile) liveRuntimeBySessionFile.delete(liveSessionFile);
1411
2209
  },
1412
2210
  }
1413
2211
  : undefined,
1414
2212
  );
1415
2213
 
2214
+ if (result.sessionFile && invokingSessionFile) {
2215
+ const existingParent = parentSessionByChild.get(result.sessionFile);
2216
+ if (!existingParent) {
2217
+ registerAgentSessionLink({
2218
+ agentName: result.agent,
2219
+ task: result.task,
2220
+ parentSessionFile: invokingSessionFile,
2221
+ subagentSessionFile: result.sessionFile,
2222
+ state: result.exitCode === 0 ? "completed" : "failed",
2223
+ });
2224
+ } else {
2225
+ updateAgentSessionLinkState(result.sessionFile, result.exitCode === 0 ? "completed" : "failed");
2226
+ }
2227
+ }
2228
+
1416
2229
  if (result.backgroundJobId) {
1417
2230
  return {
1418
2231
  content: [{ type: "text", text: `Moved ${result.agent} to background as **${result.backgroundJobId}**. Use \`await_subagent\`, \`/subagents wait ${result.backgroundJobId}\`, or \`/subagents output ${result.backgroundJobId}\`.` }],
@@ -1429,11 +2242,12 @@ export default function(pi: ExtensionAPI) {
1429
2242
  }
1430
2243
 
1431
2244
  const isError = result.exitCode !== 0 || result.stopReason === "error" || result.stopReason === "aborted";
2245
+ const agentSwitchHint = result.sessionFile ? "\n\nTip: run `/agent` to switch focus to this subagent session." : "";
1432
2246
  if (isError) {
1433
2247
  const errorMsg =
1434
2248
  result.errorMessage || result.stderr || getFinalOutput(result.messages) || "(no output)";
1435
2249
  return {
1436
- content: [{ type: "text", text: `Agent ${result.stopReason || "failed"}: ${errorMsg}` }],
2250
+ content: [{ type: "text", text: `Agent ${result.stopReason || "failed"}: ${errorMsg}${agentSwitchHint}` }],
1437
2251
  details: makeDetails("single")([result]),
1438
2252
  isError: true,
1439
2253
  };
@@ -1444,6 +2258,7 @@ export default function(pi: ExtensionAPI) {
1444
2258
  if (mergeResult && !mergeResult.success) {
1445
2259
  outputText += `\n\n⚠ Patch merge failed: ${mergeResult.error || "unknown error"}`;
1446
2260
  }
2261
+ if (agentSwitchHint) outputText += agentSwitchHint;
1447
2262
  return {
1448
2263
  content: [{ type: "text", text: outputText }],
1449
2264
  details: makeDetails("single")([result]),