lsd-pi 1.3.2 → 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 (169) hide show
  1. package/dist/resources/extensions/browser-tools/tools/codegen.js +5 -5
  2. package/dist/resources/extensions/browser-tools/tools/navigation.js +107 -178
  3. package/dist/resources/extensions/browser-tools/tools/network-mock.js +112 -167
  4. package/dist/resources/extensions/browser-tools/tools/pages.js +182 -234
  5. package/dist/resources/extensions/browser-tools/tools/refs.js +202 -461
  6. package/dist/resources/extensions/browser-tools/tools/session.js +176 -323
  7. package/dist/resources/extensions/browser-tools/tools/state-persistence.js +91 -154
  8. package/dist/resources/extensions/browser-tools/utils.js +1 -1
  9. package/dist/resources/extensions/slash-commands/extension-manifest.json +2 -2
  10. package/dist/resources/extensions/slash-commands/fast.js +73 -0
  11. package/dist/resources/extensions/slash-commands/index.js +2 -0
  12. package/dist/resources/extensions/slash-commands/plan.js +37 -12
  13. package/dist/resources/extensions/subagent/background-job-manager.js +13 -0
  14. package/dist/resources/extensions/subagent/in-process-runner.js +387 -0
  15. package/dist/resources/extensions/subagent/index.js +278 -626
  16. package/dist/resources/extensions/subagent/legacy-runner.js +503 -0
  17. package/dist/resources/extensions/voice/index.js +96 -36
  18. package/dist/resources/extensions/voice/push-to-talk.js +26 -0
  19. package/package.json +1 -1
  20. package/packages/pi-agent-core/dist/agent.d.ts +19 -0
  21. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  22. package/packages/pi-agent-core/dist/agent.js +16 -0
  23. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  24. package/packages/pi-agent-core/src/agent.ts +32 -2
  25. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts +34 -1
  26. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
  27. package/packages/pi-ai/dist/providers/openai-codex-responses.js +32 -4
  28. package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  29. package/packages/pi-ai/dist/providers/openai-codex-responses.test.js +127 -16
  30. package/packages/pi-ai/dist/providers/openai-codex-responses.test.js.map +1 -1
  31. package/packages/pi-ai/dist/providers/openai-responses.d.ts +8 -1
  32. package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  33. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.d.ts +2 -0
  34. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.d.ts.map +1 -0
  35. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.js +67 -0
  36. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.js.map +1 -0
  37. package/packages/pi-ai/dist/providers/openai-responses.js +21 -3
  38. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  39. package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  40. package/packages/pi-ai/dist/providers/simple-options.js +2 -0
  41. package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
  42. package/packages/pi-ai/dist/types.d.ts +5 -0
  43. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  44. package/packages/pi-ai/dist/types.js.map +1 -1
  45. package/packages/pi-ai/src/providers/openai-codex-responses.test.ts +143 -20
  46. package/packages/pi-ai/src/providers/openai-codex-responses.ts +47 -4
  47. package/packages/pi-ai/src/providers/openai-responses.fast-mode.test.ts +73 -0
  48. package/packages/pi-ai/src/providers/openai-responses.ts +26 -3
  49. package/packages/pi-ai/src/providers/simple-options.ts +2 -0
  50. package/packages/pi-ai/src/types.ts +5 -0
  51. package/packages/pi-coding-agent/dist/core/keybindings.d.ts +1 -1
  52. package/packages/pi-coding-agent/dist/core/keybindings.d.ts.map +1 -1
  53. package/packages/pi-coding-agent/dist/core/keybindings.js +2 -0
  54. package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
  55. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  56. package/packages/pi-coding-agent/dist/core/sdk.js +4 -2
  57. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  58. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -0
  59. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  60. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.d.ts +2 -0
  61. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.d.ts.map +1 -0
  62. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.js +35 -0
  63. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.js.map +1 -0
  64. package/packages/pi-coding-agent/dist/core/settings-manager.js +12 -0
  65. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  66. package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
  67. package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
  68. package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
  69. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  70. package/packages/pi-coding-agent/dist/core/system-prompt.js +6 -1
  71. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  72. package/packages/pi-coding-agent/dist/core/tool-priority.d.ts +4 -0
  73. package/packages/pi-coding-agent/dist/core/tool-priority.d.ts.map +1 -0
  74. package/packages/pi-coding-agent/dist/core/tool-priority.js +18 -0
  75. package/packages/pi-coding-agent/dist/core/tool-priority.js.map +1 -0
  76. package/packages/pi-coding-agent/dist/core/tool-priority.test.d.ts +2 -0
  77. package/packages/pi-coding-agent/dist/core/tool-priority.test.d.ts.map +1 -0
  78. package/packages/pi-coding-agent/dist/core/tool-priority.test.js +27 -0
  79. package/packages/pi-coding-agent/dist/core/tool-priority.test.js.map +1 -0
  80. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.d.ts +2 -0
  81. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.d.ts.map +1 -0
  82. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js +26 -0
  83. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js.map +1 -0
  84. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.d.ts +45 -0
  85. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.d.ts.map +1 -0
  86. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.js +314 -0
  87. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.js.map +1 -0
  88. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.d.ts +2 -0
  89. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.d.ts.map +1 -0
  90. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.js +122 -0
  91. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.js.map +1 -0
  92. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts +2 -0
  93. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  94. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +7 -0
  95. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  96. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +4 -0
  97. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  98. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +26 -2
  99. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  100. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +6 -0
  101. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  102. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +18 -4
  103. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  104. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts +13 -0
  105. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts.map +1 -0
  106. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js +49 -0
  107. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js.map +1 -0
  108. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.d.ts +2 -0
  109. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.d.ts.map +1 -0
  110. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js +197 -0
  111. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js.map +1 -0
  112. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  113. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +97 -0
  114. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  115. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  116. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +7 -0
  117. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +3 -0
  119. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  120. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  121. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
  122. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  123. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +35 -0
  124. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  125. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
  126. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +41 -0
  127. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  128. package/packages/pi-coding-agent/package.json +1 -1
  129. package/packages/pi-coding-agent/src/core/keybindings.ts +4 -1
  130. package/packages/pi-coding-agent/src/core/sdk.ts +4 -2
  131. package/packages/pi-coding-agent/src/core/settings-manager.fast-mode.test.ts +46 -0
  132. package/packages/pi-coding-agent/src/core/settings-manager.ts +18 -0
  133. package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
  134. package/packages/pi-coding-agent/src/core/system-prompt.ts +6 -1
  135. package/packages/pi-coding-agent/src/core/tool-priority.test.ts +30 -0
  136. package/packages/pi-coding-agent/src/core/tool-priority.ts +17 -0
  137. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-summary-line.test.ts +31 -0
  138. package/packages/pi-coding-agent/src/modes/interactive/components/btw-overlay.test.ts +172 -0
  139. package/packages/pi-coding-agent/src/modes/interactive/components/btw-overlay.ts +402 -0
  140. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +8 -0
  141. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +32 -2
  142. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +1154 -1136
  143. package/packages/pi-coding-agent/src/modes/interactive/components/tool-summary-line.ts +64 -0
  144. package/packages/pi-coding-agent/src/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.ts +228 -0
  145. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +494 -398
  146. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +7 -0
  147. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +3 -0
  148. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +38 -0
  149. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +60 -1
  150. package/pkg/package.json +1 -1
  151. package/src/resources/extensions/browser-tools/tools/codegen.ts +5 -5
  152. package/src/resources/extensions/browser-tools/tools/navigation.ts +118 -196
  153. package/src/resources/extensions/browser-tools/tools/network-mock.ts +114 -205
  154. package/src/resources/extensions/browser-tools/tools/pages.ts +183 -237
  155. package/src/resources/extensions/browser-tools/tools/refs.ts +193 -507
  156. package/src/resources/extensions/browser-tools/tools/session.ts +182 -321
  157. package/src/resources/extensions/browser-tools/tools/state-persistence.ts +94 -172
  158. package/src/resources/extensions/browser-tools/utils.ts +1 -1
  159. package/src/resources/extensions/slash-commands/extension-manifest.json +2 -2
  160. package/src/resources/extensions/slash-commands/fast.ts +89 -0
  161. package/src/resources/extensions/slash-commands/index.ts +2 -0
  162. package/src/resources/extensions/slash-commands/plan.ts +42 -12
  163. package/src/resources/extensions/subagent/background-job-manager.ts +28 -0
  164. package/src/resources/extensions/subagent/in-process-runner.ts +534 -0
  165. package/src/resources/extensions/subagent/index.ts +489 -799
  166. package/src/resources/extensions/subagent/legacy-runner.ts +607 -0
  167. package/src/resources/extensions/voice/index.ts +308 -238
  168. package/src/resources/extensions/voice/push-to-talk.ts +42 -0
  169. package/src/resources/extensions/voice/tests/push-to-talk.test.ts +109 -0
@@ -12,7 +12,6 @@
12
12
  * Uses JSON mode to capture structured output from subagents.
13
13
  */
14
14
 
15
- import { spawn, execFileSync, type ChildProcess } from "node:child_process";
16
15
  import * as crypto from "node:crypto";
17
16
  import * as fs from "node:fs";
18
17
  import * as os from "node:os";
@@ -22,30 +21,33 @@ import type { ImageContent, Message } from "@gsd/pi-ai";
22
21
  import { StringEnum } from "@gsd/pi-ai";
23
22
  import {
24
23
  type ExtensionAPI,
25
- getAgentDir,
26
24
  getMarkdownTheme,
27
25
  } from "@gsd/pi-coding-agent";
28
26
  import { Container, Key, Markdown, Spacer, Text } from "@gsd/pi-tui";
29
27
  import { Type } from "@sinclair/typebox";
30
28
  import { formatTokenCount, shortcutDesc } from "../shared/mod.js";
31
29
  import { type AgentConfig, type AgentScope, discoverAgents } from "./agents.js";
32
- import { buildSubagentProcessArgs, getBundledExtensionPathsFromEnv } from "./launch-helpers.js";
33
30
  import {
34
31
  type IsolationEnvironment,
35
- type IsolationMode,
36
32
  type MergeResult,
37
33
  createIsolation,
38
34
  mergeDeltaPatches,
39
35
  readIsolationMode,
40
36
  } from "./isolation.js";
41
37
  import { registerWorker, updateWorker } from "./worker-registry.js";
42
- import { handleSubagentPermissionRequest, isSubagentPermissionRequest } from "./approval-proxy.js";
43
38
  import { resolveConfiguredSubagentModel } from "./configured-model.js";
44
- import {
45
- normalizeSubagentModel,
46
- resolveSubagentModel,
47
- } from "./model-resolution.js";
39
+ import { resolveSubagentModel } from "./model-resolution.js";
48
40
  import { loadEffectivePreferences } from "../shared/preferences.js";
41
+ import {
42
+ readBudgetSubagentModelFromSettings,
43
+ runLegacySingleAgent,
44
+ stopLegacySubagents,
45
+ } from "./legacy-runner.js";
46
+ import {
47
+ MAX_IN_PROCESS_SUBAGENT_DEPTH,
48
+ startInProcessSingleAgent,
49
+ type SubagentHandle,
50
+ } from "./in-process-runner.js";
49
51
  import { CmuxClient, shellEscape } from "../cmux/index.js";
50
52
  import { BackgroundJobManager, type BackgroundSubagentJob } from "./background-job-manager.js";
51
53
  import { runSubagentInBackground } from "./background-runner.js";
@@ -59,7 +61,7 @@ const MAX_PARALLEL_TASKS = 8;
59
61
  const MAX_CONCURRENCY = 4;
60
62
  const COLLAPSED_ITEM_COUNT = 10;
61
63
  const DEFAULT_AWAIT_SUBAGENT_TIMEOUT_SECONDS = 120;
62
- const liveSubagentProcesses = new Set<ChildProcess>();
64
+ const USE_IN_PROCESS_SUBAGENT = process.env.LSD_SUBAGENT_IN_PROCESS !== "0";
63
65
 
64
66
  type AgentSessionState = "running" | "completed" | "failed";
65
67
 
@@ -89,6 +91,8 @@ interface LiveSubagentRuntime {
89
91
  }
90
92
 
91
93
  const liveRuntimeBySessionFile = new Map<string, LiveSubagentRuntime>();
94
+ const inProcessSubagentDepthBySessionId = new Map<string, number>();
95
+ const inProcessSubagentAncestryBySessionId = new Map<string, string[]>();
92
96
  let agentSessionLinkCounter = 0;
93
97
 
94
98
  function listSessionFiles(sessionDir: string): string[] {
@@ -103,25 +107,6 @@ function listSessionFiles(sessionDir: string): string[] {
103
107
  }
104
108
  }
105
109
 
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
110
  function registerAgentSessionLink(link: Omit<AgentSessionLink, "id" | "createdAt" | "updatedAt">): AgentSessionLink {
126
111
  const now = Date.now();
127
112
  const id = `agent-${++agentSessionLinkCounter}`;
@@ -278,40 +263,7 @@ const AwaitSubagentParams = Type.Object({
278
263
  });
279
264
 
280
265
  export async function stopLiveSubagents(): Promise<void> {
281
- const active = Array.from(liveSubagentProcesses);
282
- if (active.length === 0) return;
283
-
284
- for (const proc of active) {
285
- try {
286
- proc.kill("SIGTERM");
287
- } catch {
288
- /* ignore */
289
- }
290
- }
291
-
292
- await Promise.all(
293
- active.map(
294
- (proc) =>
295
- new Promise<void>((resolve) => {
296
- const done = () => resolve();
297
- const timer = setTimeout(done, 500);
298
- proc.once("exit", () => {
299
- clearTimeout(timer);
300
- resolve();
301
- });
302
- }),
303
- ),
304
- );
305
-
306
- for (const proc of active) {
307
- if (proc.exitCode === null) {
308
- try {
309
- proc.kill("SIGKILL");
310
- } catch {
311
- /* ignore */
312
- }
313
- }
314
- }
266
+ await stopLegacySubagents();
315
267
  }
316
268
 
317
269
  function formatBackgroundSubagentResults(jobs: BackgroundSubagentJob[]): string {
@@ -535,34 +487,6 @@ interface SingleResult {
535
487
  parentSessionFile?: string;
536
488
  }
537
489
 
538
- type BackgroundResultPayload = {
539
- summary: string;
540
- stderr: string;
541
- exitCode: number;
542
- model?: string;
543
- sessionFile?: string;
544
- parentSessionFile?: string;
545
- };
546
-
547
- interface ForegroundSingleRunControl {
548
- agentName: string;
549
- task: string;
550
- cwd: string;
551
- parentSessionFile?: string;
552
- abortController: AbortController;
553
- resultPromise: Promise<BackgroundResultPayload>;
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;
559
- }
560
-
561
- interface ForegroundSingleRunHooks {
562
- onStart?: (control: ForegroundSingleRunControl) => void;
563
- onFinish?: () => void;
564
- }
565
-
566
490
  interface SubagentDetails {
567
491
  mode: "single" | "parallel" | "chain";
568
492
  agentScope: AgentScope;
@@ -617,480 +541,8 @@ async function mapWithConcurrencyLimit<TIn, TOut>(
617
541
  return results;
618
542
  }
619
543
 
620
- function writePromptToTempFile(agentName: string, prompt: string): { dir: string; filePath: string } {
621
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
622
- const safeName = agentName.replace(/[^\w.-]+/g, "_");
623
- const filePath = path.join(tmpDir, `prompt-${safeName}.md`);
624
- fs.writeFileSync(filePath, prompt, { encoding: "utf-8", mode: 0o600 });
625
- return { dir: tmpDir, filePath };
626
- }
627
-
628
- function readBudgetSubagentModelFromSettings(): string | undefined {
629
- try {
630
- const settingsPath = path.join(getAgentDir(), "settings.json");
631
- if (!fs.existsSync(settingsPath)) return undefined;
632
- const raw = fs.readFileSync(settingsPath, "utf-8");
633
- const parsed = JSON.parse(raw) as { budgetSubagentModel?: unknown };
634
- return typeof parsed.budgetSubagentModel === "string"
635
- ? normalizeSubagentModel(parsed.budgetSubagentModel)
636
- : undefined;
637
- } catch {
638
- return undefined;
639
- }
640
- }
641
-
642
- function resolveSubagentCliPath(defaultCwd: string): string | null {
643
- const candidates = [process.env.GSD_BIN_PATH, process.env.LSD_BIN_PATH, process.argv[1]]
644
- .map((value) => value?.trim())
645
- .filter((value): value is string => Boolean(value && value !== "undefined"));
646
-
647
- for (const candidate of candidates) {
648
- if (path.isAbsolute(candidate) && fs.existsSync(candidate)) return candidate;
649
- }
650
-
651
- const cwdCandidates = [path.join(defaultCwd, "dist", "loader.js"), path.join(defaultCwd, "scripts", "dev-cli.js")];
652
- for (const candidate of cwdCandidates) {
653
- if (fs.existsSync(candidate)) return candidate;
654
- }
655
-
656
- for (const binName of ["lsd", "gsd"]) {
657
- try {
658
- const resolved = execFileSync("which", [binName], { encoding: "utf-8" }).trim();
659
- if (resolved) return resolved;
660
- } catch {
661
- /* ignore */
662
- }
663
- }
664
-
665
- return null;
666
- }
667
-
668
- function processSubagentEventLine(
669
- line: string,
670
- currentResult: SingleResult,
671
- emitUpdate: () => void,
672
- proc: ChildProcess | undefined,
673
- onSessionInfo?: (info: { sessionFile?: string; parentSessionFile?: string }) => void,
674
- onEventType?: (eventType: string) => void,
675
- onParsedEvent?: (event: any) => void,
676
- ): boolean {
677
- if (!line.trim()) return false;
678
- let event: any;
679
- try {
680
- event = JSON.parse(line);
681
- } catch {
682
- return false;
683
- }
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
-
708
- if (proc && isSubagentPermissionRequest(event)) {
709
- void handleSubagentPermissionRequest(event, proc);
710
- return false;
711
- }
712
-
713
- if ((event.type === "message_end" || event.type === "turn_end") && event.message) {
714
- const msg = event.message as Message;
715
- currentResult.messages.push(msg);
716
-
717
- if (msg.role === "assistant") {
718
- currentResult.usage.turns++;
719
- const usage = msg.usage;
720
- if (usage) {
721
- currentResult.usage.input += usage.input || 0;
722
- currentResult.usage.output += usage.output || 0;
723
- currentResult.usage.cacheRead += usage.cacheRead || 0;
724
- currentResult.usage.cacheWrite += usage.cacheWrite || 0;
725
- currentResult.usage.cost += usage.cost?.total || 0;
726
- currentResult.usage.contextTokens = usage.totalTokens || 0;
727
- }
728
- if (msg.model && (!currentResult.model || msg.model.includes("/"))) currentResult.model = msg.model;
729
- if (msg.stopReason) currentResult.stopReason = msg.stopReason;
730
- if (msg.errorMessage) currentResult.errorMessage = msg.errorMessage;
731
- }
732
- emitUpdate();
733
- }
734
-
735
- if (event.type === "tool_result_end" && event.message) {
736
- currentResult.messages.push(event.message as Message);
737
- emitUpdate();
738
- }
739
-
740
- return event.type === "agent_end";
741
- }
742
-
743
- async function waitForFile(filePath: string, signal: AbortSignal | undefined, timeoutMs = 30 * 60 * 1000): Promise<boolean> {
744
- const started = Date.now();
745
- while (Date.now() - started < timeoutMs) {
746
- if (signal?.aborted) return false;
747
- if (fs.existsSync(filePath)) return true;
748
- await new Promise((resolve) => setTimeout(resolve, 150));
749
- }
750
- return false;
751
- }
752
-
753
544
  type OnUpdateCallback = (partial: AgentToolResult<SubagentDetails>) => void;
754
545
 
755
- async function runSingleAgent(
756
- defaultCwd: string,
757
- agents: AgentConfig[],
758
- agentName: string,
759
- task: string,
760
- cwd: string | undefined,
761
- step: number | undefined,
762
- modelOverride: string | undefined,
763
- parentModel: { provider: string; id: string } | undefined,
764
- signal: AbortSignal | undefined,
765
- onUpdate: OnUpdateCallback | undefined,
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,
771
- foregroundHooks?: ForegroundSingleRunHooks,
772
- ): Promise<SingleResult> {
773
- const agent = agents.find((a) => a.name === agentName);
774
-
775
- if (!agent) {
776
- const available = agents.map((a) => `"${a.name}"`).join(", ") || "none";
777
- return {
778
- agent: agentName,
779
- agentSource: "unknown",
780
- task,
781
- exitCode: 1,
782
- messages: [],
783
- stderr: `Unknown agent: "${agentName}". Available agents: ${available}.`,
784
- usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
785
- step,
786
- };
787
- }
788
-
789
- let tmpPromptDir: string | null = null;
790
- let tmpPromptPath: string | null = null;
791
-
792
- const preferences = loadEffectivePreferences()?.preferences;
793
- const settingsBudgetModel = readBudgetSubagentModelFromSettings();
794
- const resolvedModel = resolveConfiguredSubagentModel(agent, preferences, settingsBudgetModel);
795
- const inferredModel = resolveSubagentModel(
796
- { name: agent.name, model: resolvedModel },
797
- { overrideModel: modelOverride, parentModel },
798
- );
799
-
800
- const currentResult: SingleResult = {
801
- agent: agentName,
802
- agentSource: agent.source,
803
- task,
804
- exitCode: 0,
805
- messages: [],
806
- stderr: "",
807
- usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
808
- model: inferredModel,
809
- step,
810
- parentSessionFile,
811
- };
812
-
813
- const emitUpdate = () => {
814
- if (onUpdate) {
815
- onUpdate({
816
- content: [{ type: "text", text: getFinalOutput(currentResult.messages) || "(running...)" }],
817
- details: makeDetails([currentResult]),
818
- });
819
- }
820
- };
821
-
822
- let wasAborted = false;
823
- let deferTempPromptCleanup = false;
824
- let tempPromptCleanupDone = false;
825
-
826
- const cleanupTempPromptFiles = () => {
827
- if (tempPromptCleanupDone) return;
828
- tempPromptCleanupDone = true;
829
- if (tmpPromptPath)
830
- try {
831
- fs.unlinkSync(tmpPromptPath);
832
- } catch {
833
- /* ignore */
834
- }
835
- if (tmpPromptDir)
836
- try {
837
- fs.rmdirSync(tmpPromptDir);
838
- } catch {
839
- /* ignore */
840
- }
841
- };
842
-
843
- try {
844
- if (agent.systemPrompt.trim()) {
845
- const tmp = writePromptToTempFile(agent.name, agent.systemPrompt);
846
- tmpPromptDir = tmp.dir;
847
- tmpPromptPath = tmp.filePath;
848
- }
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
- });
861
-
862
- const exitCode = await new Promise<number>((resolve) => {
863
- const bundledPaths = getBundledExtensionPathsFromEnv();
864
- const extensionArgs = bundledPaths.flatMap((p) => ["--extension", p]);
865
- const cliPath = resolveSubagentCliPath(effectiveCwd);
866
- if (!cliPath) {
867
- currentResult.stderr += "Unable to resolve LSD/GSD CLI path for subagent launch.";
868
- resolve(1);
869
- return;
870
- }
871
- const proc = spawn(
872
- process.execPath,
873
- [cliPath, ...extensionArgs, ...args],
874
- { cwd: effectiveCwd, shell: false, stdio: ["pipe", "pipe", "pipe"] },
875
- );
876
- // Keep stdin open so approval/classifier responses can be proxied back
877
- // into the child process. Closing it here can leave the subagent stuck
878
- // after its first turn when it requests permission for a tool call.
879
- liveSubagentProcesses.add(proc);
880
- let buffer = "";
881
- let completionSeen = false;
882
- let resolved = false;
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 }>();
887
- const procAbortController = new AbortController();
888
- let resolveBackgroundResult: ((value: BackgroundResultPayload) => void) | undefined;
889
- let rejectBackgroundResult: ((reason?: unknown) => void) | undefined;
890
- const backgroundResultPromise = new Promise<BackgroundResultPayload>((resolveBg, rejectBg) => {
891
- resolveBackgroundResult = resolveBg;
892
- rejectBackgroundResult = rejectBg;
893
- });
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
-
904
- const finishForeground = (code: number) => {
905
- if (resolved) return;
906
- resolved = true;
907
- resolve(code);
908
- };
909
-
910
- const adoptToBackground = (jobId: string): boolean => {
911
- if (resolved || foregroundReleased) return false;
912
- foregroundReleased = true;
913
- deferTempPromptCleanup = true;
914
- currentResult.backgroundJobId = jobId;
915
- finishForeground(0);
916
- return true;
917
- };
918
-
919
- backgroundResultPromise.finally(() => {
920
- if (deferTempPromptCleanup) cleanupTempPromptFiles();
921
- });
922
-
923
- foregroundHooks?.onStart?.({
924
- agentName,
925
- task,
926
- cwd: cwd ?? defaultCwd,
927
- parentSessionFile,
928
- abortController: procAbortController,
929
- resultPromise: backgroundResultPromise,
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,
947
- });
948
-
949
- proc.stdout.on("data", (data) => {
950
- buffer += data.toString();
951
- const lines = buffer.split("\n");
952
- buffer = lines.pop() || "";
953
- for (const line of lines) {
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))) {
978
- completionSeen = true;
979
- try {
980
- proc.kill("SIGTERM");
981
- } catch {
982
- /* ignore */
983
- }
984
- }
985
- }
986
- });
987
-
988
- proc.stderr.on("data", (data) => {
989
- currentResult.stderr += data.toString();
990
- });
991
-
992
- proc.on("close", (code) => {
993
- liveSubagentProcesses.delete(proc);
994
- if (buffer.trim()) {
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));
999
- completionSeen = completionSeen || completedOnFlush;
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
-
1007
- const finalExitCode = completionSeen && (code === null || code === 143 || code === 15) ? 0 : (code ?? 0);
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
-
1015
- resolveBackgroundResult?.({
1016
- summary: getFinalOutput(currentResult.messages),
1017
- stderr: currentResult.stderr,
1018
- exitCode: finalExitCode,
1019
- model: currentResult.model,
1020
- sessionFile: currentResult.sessionFile,
1021
- parentSessionFile: currentResult.parentSessionFile,
1022
- });
1023
- foregroundHooks?.onFinish?.();
1024
- finishForeground(finalExitCode);
1025
- });
1026
-
1027
- proc.on("error", (error) => {
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();
1034
- rejectBackgroundResult?.(error);
1035
- foregroundHooks?.onFinish?.();
1036
- finishForeground(1);
1037
- });
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
-
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;
1055
- wasAborted = true;
1056
- procAbortController.abort();
1057
- proc.kill("SIGTERM");
1058
- setTimeout(() => {
1059
- if (!proc.killed) proc.kill("SIGKILL");
1060
- }, 5000);
1061
- };
1062
-
1063
- if (signal) {
1064
- if (signal.aborted) killProc();
1065
- else signal.addEventListener("abort", killProc, { once: true });
1066
- }
1067
-
1068
- if (procAbortController.signal.aborted) {
1069
- killProc();
1070
- } else {
1071
- procAbortController.signal.addEventListener("abort", () => {
1072
- proc.kill("SIGTERM");
1073
- setTimeout(() => {
1074
- if (!proc.killed) proc.kill("SIGKILL");
1075
- }, 5000);
1076
- }, { once: true });
1077
- }
1078
- });
1079
-
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
- }
1087
- if (wasAborted) throw new Error("Subagent was aborted");
1088
- return currentResult;
1089
- } finally {
1090
- if (!deferTempPromptCleanup) cleanupTempPromptFiles();
1091
- }
1092
- }
1093
-
1094
546
  const TaskItem = Type.Object({
1095
547
  agent: Type.String({ description: "Name of the agent to invoke" }),
1096
548
  task: Type.String({ description: "Task to delegate to the agent" }),
@@ -1162,7 +614,28 @@ export default function(pi: ExtensionAPI) {
1162
614
  let bgManager: BackgroundJobManager | null = null;
1163
615
  const foregroundSubagentStatusKey = "foreground-subagent";
1164
616
  const foregroundSubagentHint = "Ctrl+B: move foreground subagent to background";
1165
- type ActiveForegroundSubagent = ForegroundSingleRunControl & { claimed: boolean };
617
+ type ActiveForegroundSubagent = {
618
+ claimed: boolean;
619
+ agentName: string;
620
+ task: string;
621
+ cwd: string;
622
+ parentSessionFile?: string;
623
+ resultPromise: Promise<{
624
+ summary: string;
625
+ stderr: string;
626
+ exitCode: number;
627
+ model?: string;
628
+ sessionFile?: string;
629
+ parentSessionFile?: string;
630
+ }>;
631
+ adoptToBackground: (jobId: string) => boolean;
632
+ abortController?: AbortController;
633
+ handle?: SubagentHandle;
634
+ sendPrompt?: (text: string, images?: ImageContent[]) => Promise<void>;
635
+ sendSteer?: (text: string, images?: ImageContent[]) => Promise<void>;
636
+ sendFollowUp?: (text: string, images?: ImageContent[]) => Promise<void>;
637
+ isBusy?: () => boolean;
638
+ };
1166
639
  let activeForegroundSubagent: ActiveForegroundSubagent | null = null;
1167
640
  let activeSessionFileForUi: string | undefined;
1168
641
  const liveStreamBufferBySession = new Map<string, string>();
@@ -1271,7 +744,6 @@ export default function(pi: ExtensionAPI) {
1271
744
  "Do not spawn or delegate to another subagent with the same name as yourself.",
1272
745
  `If the user asks you to continue ${subagentName} work, do that work directly in this session.`,
1273
746
  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
747
  ].join("\n");
1276
748
  return {
1277
749
  systemPrompt: `${event.systemPrompt}\n\n${antiRecursion}\n\n${metadata.subagentSystemPrompt}`,
@@ -1279,8 +751,19 @@ export default function(pi: ExtensionAPI) {
1279
751
  });
1280
752
  pi.on("input", async (event, ctx) => {
1281
753
  const sessionFile = ctx.sessionManager.getSessionFile();
1282
- if (!sessionFile) return;
1283
- const runtime = liveRuntimeBySessionFile.get(sessionFile);
754
+ const runtimeFromSession = sessionFile ? liveRuntimeBySessionFile.get(sessionFile) : undefined;
755
+ const runtimeFromForeground = (!runtimeFromSession && activeForegroundSubagent && !activeForegroundSubagent.claimed &&
756
+ activeForegroundSubagent.sendPrompt && activeForegroundSubagent.sendSteer && activeForegroundSubagent.sendFollowUp && activeForegroundSubagent.isBusy &&
757
+ sessionFile && activeForegroundSubagent.parentSessionFile === sessionFile)
758
+ ? {
759
+ agentName: activeForegroundSubagent.agentName,
760
+ isBusy: activeForegroundSubagent.isBusy,
761
+ sendPrompt: activeForegroundSubagent.sendPrompt,
762
+ sendSteer: activeForegroundSubagent.sendSteer,
763
+ sendFollowUp: activeForegroundSubagent.sendFollowUp,
764
+ }
765
+ : undefined;
766
+ const runtime = runtimeFromSession ?? runtimeFromForeground;
1284
767
  if (!runtime) return;
1285
768
 
1286
769
  const text = event.text?.trim();
@@ -1326,6 +809,8 @@ export default function(pi: ExtensionAPI) {
1326
809
  agentSessionIdsByParent.clear();
1327
810
  parentSessionByChild.clear();
1328
811
  liveRuntimeBySessionFile.clear();
812
+ inProcessSubagentDepthBySessionId.clear();
813
+ inProcessSubagentAncestryBySessionId.clear();
1329
814
  liveStreamBufferBySession.clear();
1330
815
  });
1331
816
 
@@ -1487,38 +972,6 @@ export default function(pi: ExtensionAPI) {
1487
972
  ctx.ui.notify("Live runtime is no longer available for this subagent. It may have completed.", "warning");
1488
973
  return;
1489
974
  }
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
975
  const switched = await ctx.switchSession(target.sessionFile);
1523
976
  if (switched.cancelled) {
1524
977
  ctx.ui.notify("Session switch was cancelled.", "warning");
@@ -1670,16 +1123,31 @@ export default function(pi: ExtensionAPI) {
1670
1123
  running.claimed = true;
1671
1124
  let jobId: string;
1672
1125
  try {
1673
- jobId = manager.adoptRunning(
1674
- running.agentName,
1675
- running.task,
1676
- running.cwd,
1677
- running.abortController,
1678
- running.resultPromise,
1679
- {
1680
- parentSessionFile: running.parentSessionFile ?? ctx.sessionManager.getSessionFile(),
1681
- },
1682
- );
1126
+ if (running.handle) {
1127
+ jobId = manager.adoptHandle(
1128
+ running.agentName,
1129
+ running.task,
1130
+ running.cwd,
1131
+ running.handle,
1132
+ running.resultPromise,
1133
+ {
1134
+ parentSessionFile: running.parentSessionFile ?? ctx.sessionManager.getSessionFile(),
1135
+ },
1136
+ );
1137
+ } else if (running.abortController) {
1138
+ jobId = manager.adoptRunning(
1139
+ running.agentName,
1140
+ running.task,
1141
+ running.cwd,
1142
+ running.abortController,
1143
+ running.resultPromise,
1144
+ {
1145
+ parentSessionFile: running.parentSessionFile ?? ctx.sessionManager.getSessionFile(),
1146
+ },
1147
+ );
1148
+ } else {
1149
+ throw new Error("Foreground subagent cannot be moved to background (missing runtime handle).");
1150
+ }
1683
1151
  } catch (error) {
1684
1152
  running.claimed = false;
1685
1153
  ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
@@ -1750,7 +1218,7 @@ export default function(pi: ExtensionAPI) {
1750
1218
  "For broad review or audit requests, use scout only as a prep step; the parent model or a reviewer should make the final judgments.",
1751
1219
  "Skip scout when the user already named the exact file/function to inspect or the task is obviously narrow.",
1752
1220
  "Use parallel mode when tasks are independent and don't need each other's output.",
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.",
1221
+ "Use background: true when the user wants to keep chatting while a long-running agent works in parallel.",
1754
1222
  "If the user wants to wait for a background subagent result, use await_subagent.",
1755
1223
  ],
1756
1224
  parameters: SubagentParams,
@@ -1763,6 +1231,17 @@ export default function(pi: ExtensionAPI) {
1763
1231
  const cmuxClient = CmuxClient.fromPreferences(loadEffectivePreferences()?.preferences);
1764
1232
  const cmuxSplitsEnabled = cmuxClient.getConfig().splits;
1765
1233
  const invokingSessionFile = ctx.sessionManager.getSessionFile();
1234
+ const invokingSessionId = ctx.sessionManager.getSessionId();
1235
+ const invokingMetadata = getCurrentSessionSubagentMetadata(invokingSessionFile);
1236
+ const currentSubagentName = invokingMetadata?.subagentName;
1237
+ const trackedAncestry = inProcessSubagentAncestryBySessionId.get(invokingSessionId);
1238
+ const currentAncestry = trackedAncestry ?? (currentSubagentName ? [currentSubagentName] : []);
1239
+ const inferredCurrentDepth = currentSubagentName ? Math.max(currentAncestry.length, 1) : 0;
1240
+ const currentSubagentDepth = Math.max(
1241
+ inProcessSubagentDepthBySessionId.get(invokingSessionId) ?? 0,
1242
+ inferredCurrentDepth,
1243
+ );
1244
+ const nextSubagentDepth = currentSubagentDepth + 1;
1766
1245
 
1767
1246
  // Resolve isolation mode
1768
1247
  const isolationMode = readIsolationMode();
@@ -1782,6 +1261,37 @@ export default function(pi: ExtensionAPI) {
1782
1261
  results,
1783
1262
  });
1784
1263
 
1264
+ const trackInProcessDepth = (
1265
+ started: { handle: SubagentHandle; resultPromise: Promise<SingleResult> },
1266
+ depth: number,
1267
+ ancestry: string[],
1268
+ ) => {
1269
+ const sessionId = started.handle.sessionId;
1270
+ if (!sessionId) return;
1271
+ inProcessSubagentDepthBySessionId.set(sessionId, depth);
1272
+ inProcessSubagentAncestryBySessionId.set(sessionId, ancestry);
1273
+ void started.resultPromise.finally(() => {
1274
+ inProcessSubagentDepthBySessionId.delete(sessionId);
1275
+ inProcessSubagentAncestryBySessionId.delete(sessionId);
1276
+ });
1277
+ };
1278
+
1279
+ const buildChildAncestry = (childAgentName: string): string[] => [...currentAncestry, childAgentName];
1280
+
1281
+ const requestedAgentNames = hasSingle
1282
+ ? [params.agent!]
1283
+ : hasChain
1284
+ ? (params.chain ?? []).map((step) => step.agent)
1285
+ : (params.tasks ?? []).map((task) => task.agent);
1286
+
1287
+ if (currentSubagentName && requestedAgentNames.some((name) => name === currentSubagentName)) {
1288
+ return {
1289
+ content: [{ type: "text", text: `Subagent "${currentSubagentName}" cannot spawn another subagent with the same name as itself.` }],
1290
+ details: makeDetails(hasChain ? "chain" : hasTasks ? "parallel" : "single")([]),
1291
+ isError: true,
1292
+ };
1293
+ }
1294
+
1785
1295
  if (modeCount !== 1) {
1786
1296
  const available = agents.map((a) => `${a.name} (${a.source})`).join(", ") || "none";
1787
1297
  return {
@@ -1803,6 +1313,22 @@ export default function(pi: ExtensionAPI) {
1803
1313
  };
1804
1314
  }
1805
1315
 
1316
+ if (params.background && currentSubagentDepth > 0) {
1317
+ return {
1318
+ content: [{ type: "text", text: "Nested background subagent launches are not supported yet. Run the nested subagent in foreground mode." }],
1319
+ details: makeDetails("single")([]),
1320
+ isError: true,
1321
+ };
1322
+ }
1323
+
1324
+ if (USE_IN_PROCESS_SUBAGENT && nextSubagentDepth > MAX_IN_PROCESS_SUBAGENT_DEPTH) {
1325
+ return {
1326
+ content: [{ type: "text", text: `Max subagent depth (${MAX_IN_PROCESS_SUBAGENT_DEPTH}) exceeded.` }],
1327
+ details: makeDetails(hasChain ? "chain" : hasTasks ? "parallel" : "single")([]),
1328
+ isError: true,
1329
+ };
1330
+ }
1331
+
1806
1332
  if ((agentScope === "project" || agentScope === "both") && confirmProjectAgents && ctx.hasUI) {
1807
1333
  const requestedAgentNames = new Set<string>();
1808
1334
  if (params.chain) for (const step of params.chain) requestedAgentNames.add(step.agent);
@@ -1851,23 +1377,45 @@ export default function(pi: ExtensionAPI) {
1851
1377
  }
1852
1378
  : undefined;
1853
1379
 
1854
- const result = await runSingleAgent(
1855
- ctx.cwd,
1856
- agents,
1857
- step.agent,
1858
- taskWithContext,
1859
- step.cwd,
1860
- i + 1,
1861
- step.model,
1862
- ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
1863
- signal,
1864
- chainUpdate,
1865
- makeDetails("chain"),
1866
- invokingSessionFile,
1867
- false,
1868
- undefined,
1869
- undefined,
1870
- );
1380
+ const result = USE_IN_PROCESS_SUBAGENT
1381
+ ? await (async () => {
1382
+ const started = await startInProcessSingleAgent(
1383
+ ctx.cwd,
1384
+ agents,
1385
+ step.agent,
1386
+ taskWithContext,
1387
+ step.cwd,
1388
+ i + 1,
1389
+ step.model,
1390
+ ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
1391
+ signal,
1392
+ chainUpdate,
1393
+ makeDetails("chain"),
1394
+ invokingSessionFile,
1395
+ nextSubagentDepth,
1396
+ invokingSessionId,
1397
+ currentAncestry,
1398
+ );
1399
+ trackInProcessDepth(started, nextSubagentDepth, buildChildAncestry(step.agent));
1400
+ return started.resultPromise;
1401
+ })()
1402
+ : await runLegacySingleAgent(
1403
+ ctx.cwd,
1404
+ agents,
1405
+ step.agent,
1406
+ taskWithContext,
1407
+ step.cwd,
1408
+ i + 1,
1409
+ step.model,
1410
+ ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
1411
+ signal,
1412
+ chainUpdate,
1413
+ makeDetails("chain"),
1414
+ invokingSessionFile,
1415
+ false,
1416
+ undefined,
1417
+ undefined,
1418
+ );
1871
1419
  results.push(result);
1872
1420
 
1873
1421
  const isError =
@@ -1938,27 +1486,54 @@ export default function(pi: ExtensionAPI) {
1938
1486
  const results = await mapWithConcurrencyLimit(params.tasks, MAX_CONCURRENCY, async (t, index) => {
1939
1487
  const workerId = registerWorker(t.agent, t.task, index, batchSize, batchId);
1940
1488
  const runTask = () =>
1941
- runSingleAgent(
1942
- ctx.cwd,
1943
- agents,
1944
- t.agent,
1945
- t.task,
1946
- t.cwd,
1947
- undefined,
1948
- t.model,
1949
- ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
1950
- signal,
1951
- (partial) => {
1952
- if (partial.details?.results[0]) {
1953
- allResults[index] = partial.details.results[0];
1954
- emitParallelUpdate();
1955
- }
1956
- },
1957
- makeDetails("parallel"),
1958
- invokingSessionFile,
1959
- false,
1960
- undefined,
1961
- );
1489
+ USE_IN_PROCESS_SUBAGENT
1490
+ ? (async () => {
1491
+ const started = await startInProcessSingleAgent(
1492
+ ctx.cwd,
1493
+ agents,
1494
+ t.agent,
1495
+ t.task,
1496
+ t.cwd,
1497
+ undefined,
1498
+ t.model,
1499
+ ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
1500
+ signal,
1501
+ (partial) => {
1502
+ if (partial.details?.results[0]) {
1503
+ allResults[index] = partial.details.results[0];
1504
+ emitParallelUpdate();
1505
+ }
1506
+ },
1507
+ makeDetails("parallel"),
1508
+ invokingSessionFile,
1509
+ nextSubagentDepth,
1510
+ invokingSessionId,
1511
+ currentAncestry,
1512
+ );
1513
+ trackInProcessDepth(started, nextSubagentDepth, buildChildAncestry(t.agent));
1514
+ return started.resultPromise;
1515
+ })()
1516
+ : runLegacySingleAgent(
1517
+ ctx.cwd,
1518
+ agents,
1519
+ t.agent,
1520
+ t.task,
1521
+ t.cwd,
1522
+ undefined,
1523
+ t.model,
1524
+ ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
1525
+ signal,
1526
+ (partial) => {
1527
+ if (partial.details?.results[0]) {
1528
+ allResults[index] = partial.details.results[0];
1529
+ emitParallelUpdate();
1530
+ }
1531
+ },
1532
+ makeDetails("parallel"),
1533
+ invokingSessionFile,
1534
+ false,
1535
+ undefined,
1536
+ );
1962
1537
  let result = await runTask();
1963
1538
 
1964
1539
  // Auto-retry failed tasks (likely API rate limit or transient error)
@@ -2025,89 +1600,130 @@ export default function(pi: ExtensionAPI) {
2025
1600
 
2026
1601
  let jobId: string;
2027
1602
  try {
2028
- jobId = runSubagentInBackground(
2029
- manager,
2030
- agents,
2031
- params.agent,
2032
- params.task,
2033
- params.cwd,
2034
- params.model,
2035
- { defaultCwd: ctx.cwd, model: ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, parentSessionFile: invokingSessionFile },
2036
- async (bgSignal) => {
2037
- let liveSessionFile: string | undefined;
2038
- let liveRuntime: LiveSubagentRuntime | undefined;
2039
- const result = await runSingleAgent(
2040
- ctx.cwd,
2041
- agents,
2042
- params.agent!,
2043
- params.task!,
2044
- params.cwd,
2045
- undefined,
2046
- params.model,
2047
- ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
2048
- bgSignal,
2049
- undefined, // no streaming updates for background jobs
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
- },
2100
- );
2101
- return {
2102
- exitCode: result.exitCode,
2103
- finalOutput: getFinalOutput(result.messages),
1603
+ if (USE_IN_PROCESS_SUBAGENT) {
1604
+ const started = await startInProcessSingleAgent(
1605
+ ctx.cwd,
1606
+ agents,
1607
+ params.agent,
1608
+ params.task,
1609
+ params.cwd,
1610
+ undefined,
1611
+ params.model,
1612
+ ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
1613
+ undefined,
1614
+ undefined,
1615
+ makeDetails("single"),
1616
+ invokingSessionFile,
1617
+ nextSubagentDepth,
1618
+ invokingSessionId,
1619
+ currentAncestry,
1620
+ );
1621
+ trackInProcessDepth(started, nextSubagentDepth, buildChildAncestry(params.agent));
1622
+
1623
+ const effectiveCwd = params.cwd ?? ctx.cwd;
1624
+ jobId = manager.adoptHandle(
1625
+ params.agent,
1626
+ params.task,
1627
+ effectiveCwd,
1628
+ started.handle,
1629
+ started.resultPromise.then((result) => ({
1630
+ summary: (getFinalOutput(result.messages) || "(no output)").slice(0, 300),
2104
1631
  stderr: result.stderr,
1632
+ exitCode: result.exitCode,
2105
1633
  model: result.model,
2106
1634
  sessionFile: result.sessionFile,
2107
1635
  parentSessionFile: result.parentSessionFile,
2108
- };
2109
- },
2110
- );
1636
+ })),
1637
+ {
1638
+ parentSessionFile: invokingSessionFile,
1639
+ model: bgInferredModel,
1640
+ },
1641
+ );
1642
+ } else {
1643
+ jobId = runSubagentInBackground(
1644
+ manager,
1645
+ agents,
1646
+ params.agent,
1647
+ params.task,
1648
+ params.cwd,
1649
+ params.model,
1650
+ { defaultCwd: ctx.cwd, model: ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, parentSessionFile: invokingSessionFile },
1651
+ async (bgSignal) => {
1652
+ let liveSessionFile: string | undefined;
1653
+ let liveRuntime: LiveSubagentRuntime | undefined;
1654
+ const result = await runLegacySingleAgent(
1655
+ ctx.cwd,
1656
+ agents,
1657
+ params.agent!,
1658
+ params.task!,
1659
+ params.cwd,
1660
+ undefined,
1661
+ params.model,
1662
+ ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
1663
+ bgSignal,
1664
+ undefined, // no streaming updates for background jobs
1665
+ makeDetails("single"),
1666
+ invokingSessionFile,
1667
+ true,
1668
+ (info) => {
1669
+ if (!invokingSessionFile || !info.sessionFile) return;
1670
+ upsertAgentSessionLink(
1671
+ params.agent!,
1672
+ params.task!,
1673
+ invokingSessionFile,
1674
+ info.sessionFile,
1675
+ "running",
1676
+ );
1677
+ liveSessionFile = info.sessionFile;
1678
+ if (liveRuntime) {
1679
+ liveRuntime.sessionFile = info.sessionFile;
1680
+ liveRuntime.parentSessionFile = info.parentSessionFile ?? invokingSessionFile;
1681
+ liveRuntimeBySessionFile.set(info.sessionFile, liveRuntime);
1682
+ }
1683
+ },
1684
+ (event, partial) => {
1685
+ const sessionFile = partial.sessionFile;
1686
+ if (!sessionFile || activeSessionFileForUi !== sessionFile) return;
1687
+ if (event?.type === "message_update" && event.assistantMessageEvent?.type === "text_delta") {
1688
+ const delta = String(event.assistantMessageEvent.delta ?? "");
1689
+ if (delta) pushLiveStreamDelta(sessionFile, delta);
1690
+ }
1691
+ if (event?.type === "message_end") {
1692
+ flushLiveStream(sessionFile);
1693
+ }
1694
+ },
1695
+ {
1696
+ onStart: (control) => {
1697
+ if (!control.sendPrompt || !control.sendSteer || !control.sendFollowUp || !control.isBusy) return;
1698
+ liveRuntime = {
1699
+ sessionFile: liveSessionFile,
1700
+ parentSessionFile: invokingSessionFile,
1701
+ agentName: params.agent!,
1702
+ isBusy: control.isBusy,
1703
+ sendPrompt: control.sendPrompt,
1704
+ sendSteer: control.sendSteer,
1705
+ sendFollowUp: control.sendFollowUp,
1706
+ };
1707
+ if (liveSessionFile) {
1708
+ liveRuntimeBySessionFile.set(liveSessionFile, liveRuntime);
1709
+ }
1710
+ },
1711
+ onFinish: () => {
1712
+ if (liveSessionFile) liveRuntimeBySessionFile.delete(liveSessionFile);
1713
+ },
1714
+ },
1715
+ );
1716
+ return {
1717
+ exitCode: result.exitCode,
1718
+ finalOutput: getFinalOutput(result.messages),
1719
+ stderr: result.stderr,
1720
+ model: result.model,
1721
+ sessionFile: result.sessionFile,
1722
+ parentSessionFile: result.parentSessionFile,
1723
+ };
1724
+ },
1725
+ );
1726
+ }
2111
1727
  } catch (err) {
2112
1728
  return {
2113
1729
  content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }],
@@ -2135,81 +1751,155 @@ export default function(pi: ExtensionAPI) {
2135
1751
  isolation = await createIsolation(effectiveCwd, taskId, isolationMode);
2136
1752
  }
2137
1753
 
2138
- let liveSessionFile: string | undefined;
2139
- let liveRuntime: LiveSubagentRuntime | undefined;
2140
- const result = await runSingleAgent(
2141
- ctx.cwd,
2142
- agents,
2143
- params.agent,
2144
- params.task,
2145
- isolation ? isolation.workDir : params.cwd,
2146
- undefined,
2147
- params.model,
2148
- ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
2149
- signal,
2150
- onUpdate,
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);
1754
+ let result: SingleResult;
1755
+ if (USE_IN_PROCESS_SUBAGENT && !isolation) {
1756
+ const started = await startInProcessSingleAgent(
1757
+ ctx.cwd,
1758
+ agents,
1759
+ params.agent,
1760
+ params.task,
1761
+ params.cwd,
1762
+ undefined,
1763
+ params.model,
1764
+ ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
1765
+ signal,
1766
+ onUpdate,
1767
+ makeDetails("single"),
1768
+ invokingSessionFile,
1769
+ nextSubagentDepth,
1770
+ invokingSessionId,
1771
+ currentAncestry,
1772
+ );
1773
+ trackInProcessDepth(started, nextSubagentDepth, buildChildAncestry(params.agent));
1774
+
1775
+ const effectiveRunCwd = params.cwd ?? ctx.cwd;
1776
+ let releaseToBackground: ((jobId: string) => void) | undefined;
1777
+ const movedToBackground = new Promise<string>((resolve) => {
1778
+ releaseToBackground = resolve;
1779
+ });
1780
+
1781
+ activeForegroundSubagent = {
1782
+ claimed: false,
1783
+ agentName: params.agent,
1784
+ task: params.task,
1785
+ cwd: effectiveRunCwd,
1786
+ parentSessionFile: invokingSessionFile,
1787
+ handle: started.handle,
1788
+ resultPromise: started.resultPromise.then((done) => ({
1789
+ summary: getFinalOutput(done.messages) || "(no output)",
1790
+ stderr: done.stderr,
1791
+ exitCode: done.exitCode,
1792
+ model: done.model,
1793
+ sessionFile: done.sessionFile,
1794
+ parentSessionFile: done.parentSessionFile,
1795
+ })),
1796
+ adoptToBackground: (jobId: string) => {
1797
+ if (!releaseToBackground) return false;
1798
+ releaseToBackground(jobId);
1799
+ releaseToBackground = undefined;
1800
+ return true;
1801
+ },
1802
+ sendPrompt: started.handle.prompt,
1803
+ sendSteer: started.handle.steer,
1804
+ sendFollowUp: started.handle.followUp,
1805
+ isBusy: started.handle.isBusy,
1806
+ };
1807
+ ctx.ui.setStatus(foregroundSubagentStatusKey, foregroundSubagentHint);
1808
+
1809
+ const winner = await Promise.race([
1810
+ started.resultPromise.then((done) => ({ type: "done" as const, done })),
1811
+ movedToBackground.then((jobId) => ({ type: "background" as const, jobId })),
1812
+ ]);
1813
+
1814
+ if (winner.type === "background") {
1815
+ activeForegroundSubagent = null;
1816
+ ctx.ui.setStatus(foregroundSubagentStatusKey, undefined);
1817
+ result = {
1818
+ ...started.currentResult,
1819
+ backgroundJobId: winner.jobId,
1820
+ };
1821
+ } else {
1822
+ activeForegroundSubagent = null;
1823
+ ctx.ui.setStatus(foregroundSubagentStatusKey, undefined);
1824
+ result = winner.done;
1825
+ }
1826
+ } else {
1827
+ let liveSessionFile: string | undefined;
1828
+ let liveRuntime: LiveSubagentRuntime | undefined;
1829
+ result = await runLegacySingleAgent(
1830
+ ctx.cwd,
1831
+ agents,
1832
+ params.agent,
1833
+ params.task,
1834
+ isolation ? isolation.workDir : params.cwd,
1835
+ undefined,
1836
+ params.model,
1837
+ ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
1838
+ signal,
1839
+ onUpdate,
1840
+ makeDetails("single"),
1841
+ invokingSessionFile,
1842
+ !isolation,
1843
+ !isolation
1844
+ ? (info) => {
1845
+ if (!invokingSessionFile || !info.sessionFile) return;
1846
+ upsertAgentSessionLink(
1847
+ params.agent!,
1848
+ params.task!,
1849
+ invokingSessionFile,
1850
+ info.sessionFile,
1851
+ "running",
1852
+ );
1853
+ liveSessionFile = info.sessionFile;
1854
+ if (liveRuntime) {
1855
+ liveRuntime.sessionFile = info.sessionFile;
1856
+ liveRuntime.parentSessionFile = info.parentSessionFile ?? invokingSessionFile;
1857
+ liveRuntimeBySessionFile.set(info.sessionFile, liveRuntime);
1858
+ }
2169
1859
  }
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);
1860
+ : undefined,
1861
+ !isolation
1862
+ ? (event, partial) => {
1863
+ const sessionFile = partial.sessionFile;
1864
+ if (!sessionFile || activeSessionFileForUi !== sessionFile) return;
1865
+ if (event?.type === "message_update" && event.assistantMessageEvent?.type === "text_delta") {
1866
+ const delta = String(event.assistantMessageEvent.delta ?? "");
1867
+ if (delta) pushLiveStreamDelta(sessionFile, delta);
1868
+ }
1869
+ if (event?.type === "message_end") {
1870
+ flushLiveStream(sessionFile);
1871
+ }
2179
1872
  }
2180
- if (event?.type === "message_end") {
2181
- flushLiveStream(sessionFile);
1873
+ : undefined,
1874
+ !isolation
1875
+ ? {
1876
+ onStart: (control) => {
1877
+ activeForegroundSubagent = { ...control, claimed: false };
1878
+ ctx.ui.setStatus(foregroundSubagentStatusKey, foregroundSubagentHint);
1879
+
1880
+ if (!control.sendPrompt || !control.sendSteer || !control.sendFollowUp || !control.isBusy) return;
1881
+ liveRuntime = {
1882
+ sessionFile: liveSessionFile,
1883
+ parentSessionFile: invokingSessionFile,
1884
+ agentName: params.agent!,
1885
+ isBusy: control.isBusy,
1886
+ sendPrompt: control.sendPrompt,
1887
+ sendSteer: control.sendSteer,
1888
+ sendFollowUp: control.sendFollowUp,
1889
+ };
1890
+ if (liveSessionFile && liveRuntime) {
1891
+ liveRuntimeBySessionFile.set(liveSessionFile, liveRuntime);
1892
+ }
1893
+ },
1894
+ onFinish: () => {
1895
+ activeForegroundSubagent = null;
1896
+ ctx.ui.setStatus(foregroundSubagentStatusKey, undefined);
1897
+ if (liveSessionFile) liveRuntimeBySessionFile.delete(liveSessionFile);
1898
+ },
2182
1899
  }
2183
- }
2184
- : undefined,
2185
- !isolation
2186
- ? {
2187
- onStart: (control) => {
2188
- activeForegroundSubagent = { ...control, claimed: false };
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
- }
2204
- },
2205
- onFinish: () => {
2206
- activeForegroundSubagent = null;
2207
- ctx.ui.setStatus(foregroundSubagentStatusKey, undefined);
2208
- if (liveSessionFile) liveRuntimeBySessionFile.delete(liveSessionFile);
2209
- },
2210
- }
2211
- : undefined,
2212
- );
1900
+ : undefined,
1901
+ );
1902
+ }
2213
1903
 
2214
1904
  if (result.sessionFile && invokingSessionFile) {
2215
1905
  const existingParent = parentSessionByChild.get(result.sessionFile);