lsd-pi 1.3.2 → 1.3.7
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.
- package/dist/cli.js +2 -1
- package/dist/lsd-settings-manager.d.ts +2 -0
- package/dist/lsd-settings-manager.js +5 -0
- package/dist/resource-loader.js +33 -3
- package/dist/resources/extensions/browser-tools/tools/codegen.js +5 -5
- package/dist/resources/extensions/browser-tools/tools/navigation.js +107 -178
- package/dist/resources/extensions/browser-tools/tools/network-mock.js +112 -167
- package/dist/resources/extensions/browser-tools/tools/pages.js +182 -234
- package/dist/resources/extensions/browser-tools/tools/refs.js +202 -461
- package/dist/resources/extensions/browser-tools/tools/session.js +176 -323
- package/dist/resources/extensions/browser-tools/tools/state-persistence.js +91 -154
- package/dist/resources/extensions/browser-tools/utils.js +1 -1
- package/dist/resources/extensions/cache-timer/index.js +3 -2
- package/dist/resources/extensions/slash-commands/extension-manifest.json +2 -2
- package/dist/resources/extensions/slash-commands/fast.js +73 -0
- package/dist/resources/extensions/slash-commands/index.js +2 -0
- package/dist/resources/extensions/slash-commands/plan.js +37 -12
- package/dist/resources/extensions/subagent/background-job-manager.js +13 -0
- package/dist/resources/extensions/subagent/in-process-runner.js +387 -0
- package/dist/resources/extensions/subagent/index.js +278 -626
- package/dist/resources/extensions/subagent/legacy-runner.js +503 -0
- package/dist/resources/extensions/voice/index.js +96 -36
- package/dist/resources/extensions/voice/push-to-talk.js +26 -0
- package/dist/welcome-screen.js +2 -2
- package/package.json +1 -1
- package/packages/pi-agent-core/dist/agent.d.ts +19 -0
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +16 -0
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/src/agent.ts +32 -2
- package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts +34 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js +32 -4
- package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.test.js +127 -16
- package/packages/pi-ai/dist/providers/openai-codex-responses.test.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.d.ts +8 -1
- package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.js +67 -0
- package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.js.map +1 -0
- package/packages/pi-ai/dist/providers/openai-responses.js +21 -3
- package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.js +2 -0
- package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +5 -0
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/src/providers/openai-codex-responses.test.ts +143 -20
- package/packages/pi-ai/src/providers/openai-codex-responses.ts +47 -4
- package/packages/pi-ai/src/providers/openai-responses.fast-mode.test.ts +73 -0
- package/packages/pi-ai/src/providers/openai-responses.ts +26 -3
- package/packages/pi-ai/src/providers/simple-options.ts +2 -0
- package/packages/pi-ai/src/types.ts +5 -0
- package/packages/pi-coding-agent/dist/core/keybindings.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.js +2 -0
- package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +4 -2
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.js +35 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +12 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.js +35 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js +24 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
- package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +6 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tool-priority.d.ts +4 -0
- package/packages/pi-coding-agent/dist/core/tool-priority.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tool-priority.js +18 -0
- package/packages/pi-coding-agent/dist/core/tool-priority.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/tool-priority.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/tool-priority.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tool-priority.test.js +27 -0
- package/packages/pi-coding-agent/dist/core/tool-priority.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts +5 -0
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.js +21 -0
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +16 -1
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js +34 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.d.ts +45 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.js +314 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.js +122 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts +7 -5
- package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js +86 -28
- package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +23 -10
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +8 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +52 -6
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +19 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +127 -14
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts +14 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js +93 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js +328 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +123 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +7 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +9 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +103 -23
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +41 -0
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +4 -4
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/keybindings.ts +4 -1
- package/packages/pi-coding-agent/src/core/sdk.ts +4 -2
- package/packages/pi-coding-agent/src/core/settings-manager.collapse-tool-calls.test.ts +46 -0
- package/packages/pi-coding-agent/src/core/settings-manager.fast-mode.test.ts +46 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +36 -0
- package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
- package/packages/pi-coding-agent/src/core/system-prompt.ts +6 -1
- package/packages/pi-coding-agent/src/core/tool-priority.test.ts +30 -0
- package/packages/pi-coding-agent/src/core/tool-priority.ts +17 -0
- package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +20 -0
- package/packages/pi-coding-agent/src/core/tools/edit-diff.ts +26 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-summary-line.test.ts +41 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/btw-overlay.test.ts +172 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/btw-overlay.ts +402 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/diff.ts +105 -28
- package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +21 -6
- package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +63 -6
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +1262 -1138
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-summary-line.ts +120 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.ts +396 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +530 -398
- package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +7 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +4 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +109 -23
- package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +60 -1
- package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +4 -4
- package/packages/pi-tui/dist/components/editor.js +3 -3
- package/packages/pi-tui/dist/components/editor.js.map +1 -1
- package/packages/pi-tui/src/components/editor.ts +3 -3
- package/pkg/dist/modes/interactive/theme/themes.js +4 -4
- package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/browser-tools/tools/codegen.ts +5 -5
- package/src/resources/extensions/browser-tools/tools/navigation.ts +118 -196
- package/src/resources/extensions/browser-tools/tools/network-mock.ts +114 -205
- package/src/resources/extensions/browser-tools/tools/pages.ts +183 -237
- package/src/resources/extensions/browser-tools/tools/refs.ts +193 -507
- package/src/resources/extensions/browser-tools/tools/session.ts +182 -321
- package/src/resources/extensions/browser-tools/tools/state-persistence.ts +94 -172
- package/src/resources/extensions/browser-tools/utils.ts +1 -1
- package/src/resources/extensions/cache-timer/index.ts +3 -2
- package/src/resources/extensions/slash-commands/extension-manifest.json +2 -2
- package/src/resources/extensions/slash-commands/fast.ts +89 -0
- package/src/resources/extensions/slash-commands/index.ts +2 -0
- package/src/resources/extensions/slash-commands/plan.ts +42 -12
- package/src/resources/extensions/subagent/background-job-manager.ts +28 -0
- package/src/resources/extensions/subagent/in-process-runner.ts +534 -0
- package/src/resources/extensions/subagent/index.ts +489 -799
- package/src/resources/extensions/subagent/legacy-runner.ts +607 -0
- package/src/resources/extensions/voice/index.ts +308 -238
- package/src/resources/extensions/voice/push-to-talk.ts +42 -0
- package/src/resources/extensions/voice/tests/push-to-talk.test.ts +109 -0
|
@@ -11,24 +11,23 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Uses JSON mode to capture structured output from subagents.
|
|
13
13
|
*/
|
|
14
|
-
import { spawn, execFileSync } from "node:child_process";
|
|
15
14
|
import * as crypto from "node:crypto";
|
|
16
15
|
import * as fs from "node:fs";
|
|
17
16
|
import * as os from "node:os";
|
|
18
17
|
import * as path from "node:path";
|
|
19
18
|
import { StringEnum } from "@gsd/pi-ai";
|
|
20
|
-
import {
|
|
19
|
+
import { getMarkdownTheme, } from "@gsd/pi-coding-agent";
|
|
21
20
|
import { Container, Key, Markdown, Spacer, Text } from "@gsd/pi-tui";
|
|
22
21
|
import { Type } from "@sinclair/typebox";
|
|
23
22
|
import { formatTokenCount, shortcutDesc } from "../shared/mod.js";
|
|
24
23
|
import { discoverAgents } from "./agents.js";
|
|
25
|
-
import { buildSubagentProcessArgs, getBundledExtensionPathsFromEnv } from "./launch-helpers.js";
|
|
26
24
|
import { createIsolation, mergeDeltaPatches, readIsolationMode, } from "./isolation.js";
|
|
27
25
|
import { registerWorker, updateWorker } from "./worker-registry.js";
|
|
28
|
-
import { handleSubagentPermissionRequest, isSubagentPermissionRequest } from "./approval-proxy.js";
|
|
29
26
|
import { resolveConfiguredSubagentModel } from "./configured-model.js";
|
|
30
|
-
import {
|
|
27
|
+
import { resolveSubagentModel } from "./model-resolution.js";
|
|
31
28
|
import { loadEffectivePreferences } from "../shared/preferences.js";
|
|
29
|
+
import { readBudgetSubagentModelFromSettings, runLegacySingleAgent, stopLegacySubagents, } from "./legacy-runner.js";
|
|
30
|
+
import { MAX_IN_PROCESS_SUBAGENT_DEPTH, startInProcessSingleAgent, } from "./in-process-runner.js";
|
|
32
31
|
import { CmuxClient } from "../cmux/index.js";
|
|
33
32
|
import { BackgroundJobManager } from "./background-job-manager.js";
|
|
34
33
|
import { runSubagentInBackground } from "./background-runner.js";
|
|
@@ -38,11 +37,13 @@ const MAX_PARALLEL_TASKS = 8;
|
|
|
38
37
|
const MAX_CONCURRENCY = 4;
|
|
39
38
|
const COLLAPSED_ITEM_COUNT = 10;
|
|
40
39
|
const DEFAULT_AWAIT_SUBAGENT_TIMEOUT_SECONDS = 120;
|
|
41
|
-
const
|
|
40
|
+
const USE_IN_PROCESS_SUBAGENT = process.env.LSD_SUBAGENT_IN_PROCESS !== "0";
|
|
42
41
|
const agentSessionLinksById = new Map();
|
|
43
42
|
const agentSessionIdsByParent = new Map();
|
|
44
43
|
const parentSessionByChild = new Map();
|
|
45
44
|
const liveRuntimeBySessionFile = new Map();
|
|
45
|
+
const inProcessSubagentDepthBySessionId = new Map();
|
|
46
|
+
const inProcessSubagentAncestryBySessionId = new Map();
|
|
46
47
|
let agentSessionLinkCounter = 0;
|
|
47
48
|
function listSessionFiles(sessionDir) {
|
|
48
49
|
if (!fs.existsSync(sessionDir))
|
|
@@ -57,25 +58,6 @@ function listSessionFiles(sessionDir) {
|
|
|
57
58
|
return [];
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
|
-
function detectNewSubagentSessionFile(sessionDir, before, startedAt) {
|
|
61
|
-
const after = listSessionFiles(sessionDir);
|
|
62
|
-
const created = after.filter((file) => !before.has(file));
|
|
63
|
-
const candidates = created.length > 0 ? created : after;
|
|
64
|
-
const ranked = candidates
|
|
65
|
-
.map((file) => {
|
|
66
|
-
let mtime = 0;
|
|
67
|
-
try {
|
|
68
|
-
mtime = fs.statSync(file).mtimeMs;
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
mtime = 0;
|
|
72
|
-
}
|
|
73
|
-
return { file, mtime };
|
|
74
|
-
})
|
|
75
|
-
.filter((entry) => entry.mtime >= startedAt - 5000)
|
|
76
|
-
.sort((a, b) => b.mtime - a.mtime);
|
|
77
|
-
return ranked[0]?.file;
|
|
78
|
-
}
|
|
79
61
|
function registerAgentSessionLink(link) {
|
|
80
62
|
const now = Date.now();
|
|
81
63
|
const id = `agent-${++agentSessionLinkCounter}`;
|
|
@@ -203,35 +185,7 @@ const AwaitSubagentParams = Type.Object({
|
|
|
203
185
|
})),
|
|
204
186
|
});
|
|
205
187
|
export async function stopLiveSubagents() {
|
|
206
|
-
|
|
207
|
-
if (active.length === 0)
|
|
208
|
-
return;
|
|
209
|
-
for (const proc of active) {
|
|
210
|
-
try {
|
|
211
|
-
proc.kill("SIGTERM");
|
|
212
|
-
}
|
|
213
|
-
catch {
|
|
214
|
-
/* ignore */
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
await Promise.all(active.map((proc) => new Promise((resolve) => {
|
|
218
|
-
const done = () => resolve();
|
|
219
|
-
const timer = setTimeout(done, 500);
|
|
220
|
-
proc.once("exit", () => {
|
|
221
|
-
clearTimeout(timer);
|
|
222
|
-
resolve();
|
|
223
|
-
});
|
|
224
|
-
})));
|
|
225
|
-
for (const proc of active) {
|
|
226
|
-
if (proc.exitCode === null) {
|
|
227
|
-
try {
|
|
228
|
-
proc.kill("SIGKILL");
|
|
229
|
-
}
|
|
230
|
-
catch {
|
|
231
|
-
/* ignore */
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
188
|
+
await stopLegacySubagents();
|
|
235
189
|
}
|
|
236
190
|
function formatBackgroundSubagentResults(jobs) {
|
|
237
191
|
if (jobs.length === 0)
|
|
@@ -449,442 +403,6 @@ async function mapWithConcurrencyLimit(items, concurrency, fn) {
|
|
|
449
403
|
await Promise.all(workers);
|
|
450
404
|
return results;
|
|
451
405
|
}
|
|
452
|
-
function writePromptToTempFile(agentName, prompt) {
|
|
453
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
|
|
454
|
-
const safeName = agentName.replace(/[^\w.-]+/g, "_");
|
|
455
|
-
const filePath = path.join(tmpDir, `prompt-${safeName}.md`);
|
|
456
|
-
fs.writeFileSync(filePath, prompt, { encoding: "utf-8", mode: 0o600 });
|
|
457
|
-
return { dir: tmpDir, filePath };
|
|
458
|
-
}
|
|
459
|
-
function readBudgetSubagentModelFromSettings() {
|
|
460
|
-
try {
|
|
461
|
-
const settingsPath = path.join(getAgentDir(), "settings.json");
|
|
462
|
-
if (!fs.existsSync(settingsPath))
|
|
463
|
-
return undefined;
|
|
464
|
-
const raw = fs.readFileSync(settingsPath, "utf-8");
|
|
465
|
-
const parsed = JSON.parse(raw);
|
|
466
|
-
return typeof parsed.budgetSubagentModel === "string"
|
|
467
|
-
? normalizeSubagentModel(parsed.budgetSubagentModel)
|
|
468
|
-
: undefined;
|
|
469
|
-
}
|
|
470
|
-
catch {
|
|
471
|
-
return undefined;
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
function resolveSubagentCliPath(defaultCwd) {
|
|
475
|
-
const candidates = [process.env.GSD_BIN_PATH, process.env.LSD_BIN_PATH, process.argv[1]]
|
|
476
|
-
.map((value) => value?.trim())
|
|
477
|
-
.filter((value) => Boolean(value && value !== "undefined"));
|
|
478
|
-
for (const candidate of candidates) {
|
|
479
|
-
if (path.isAbsolute(candidate) && fs.existsSync(candidate))
|
|
480
|
-
return candidate;
|
|
481
|
-
}
|
|
482
|
-
const cwdCandidates = [path.join(defaultCwd, "dist", "loader.js"), path.join(defaultCwd, "scripts", "dev-cli.js")];
|
|
483
|
-
for (const candidate of cwdCandidates) {
|
|
484
|
-
if (fs.existsSync(candidate))
|
|
485
|
-
return candidate;
|
|
486
|
-
}
|
|
487
|
-
for (const binName of ["lsd", "gsd"]) {
|
|
488
|
-
try {
|
|
489
|
-
const resolved = execFileSync("which", [binName], { encoding: "utf-8" }).trim();
|
|
490
|
-
if (resolved)
|
|
491
|
-
return resolved;
|
|
492
|
-
}
|
|
493
|
-
catch {
|
|
494
|
-
/* ignore */
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
return null;
|
|
498
|
-
}
|
|
499
|
-
function processSubagentEventLine(line, currentResult, emitUpdate, proc, onSessionInfo, onEventType, onParsedEvent) {
|
|
500
|
-
if (!line.trim())
|
|
501
|
-
return false;
|
|
502
|
-
let event;
|
|
503
|
-
try {
|
|
504
|
-
event = JSON.parse(line);
|
|
505
|
-
}
|
|
506
|
-
catch {
|
|
507
|
-
return false;
|
|
508
|
-
}
|
|
509
|
-
const eventType = typeof event.type === "string" ? event.type : "unknown";
|
|
510
|
-
onEventType?.(eventType);
|
|
511
|
-
onParsedEvent?.(event);
|
|
512
|
-
if (event.type === "subagent_session_info") {
|
|
513
|
-
let changed = false;
|
|
514
|
-
if (typeof event.sessionFile === "string" && event.sessionFile) {
|
|
515
|
-
if (currentResult.sessionFile !== event.sessionFile)
|
|
516
|
-
changed = true;
|
|
517
|
-
currentResult.sessionFile = event.sessionFile;
|
|
518
|
-
}
|
|
519
|
-
if (typeof event.parentSessionFile === "string" && event.parentSessionFile) {
|
|
520
|
-
if (currentResult.parentSessionFile !== event.parentSessionFile)
|
|
521
|
-
changed = true;
|
|
522
|
-
currentResult.parentSessionFile = event.parentSessionFile;
|
|
523
|
-
}
|
|
524
|
-
if (changed) {
|
|
525
|
-
onSessionInfo?.({
|
|
526
|
-
sessionFile: currentResult.sessionFile,
|
|
527
|
-
parentSessionFile: currentResult.parentSessionFile,
|
|
528
|
-
});
|
|
529
|
-
}
|
|
530
|
-
return false;
|
|
531
|
-
}
|
|
532
|
-
if (proc && isSubagentPermissionRequest(event)) {
|
|
533
|
-
void handleSubagentPermissionRequest(event, proc);
|
|
534
|
-
return false;
|
|
535
|
-
}
|
|
536
|
-
if ((event.type === "message_end" || event.type === "turn_end") && event.message) {
|
|
537
|
-
const msg = event.message;
|
|
538
|
-
currentResult.messages.push(msg);
|
|
539
|
-
if (msg.role === "assistant") {
|
|
540
|
-
currentResult.usage.turns++;
|
|
541
|
-
const usage = msg.usage;
|
|
542
|
-
if (usage) {
|
|
543
|
-
currentResult.usage.input += usage.input || 0;
|
|
544
|
-
currentResult.usage.output += usage.output || 0;
|
|
545
|
-
currentResult.usage.cacheRead += usage.cacheRead || 0;
|
|
546
|
-
currentResult.usage.cacheWrite += usage.cacheWrite || 0;
|
|
547
|
-
currentResult.usage.cost += usage.cost?.total || 0;
|
|
548
|
-
currentResult.usage.contextTokens = usage.totalTokens || 0;
|
|
549
|
-
}
|
|
550
|
-
if (msg.model && (!currentResult.model || msg.model.includes("/")))
|
|
551
|
-
currentResult.model = msg.model;
|
|
552
|
-
if (msg.stopReason)
|
|
553
|
-
currentResult.stopReason = msg.stopReason;
|
|
554
|
-
if (msg.errorMessage)
|
|
555
|
-
currentResult.errorMessage = msg.errorMessage;
|
|
556
|
-
}
|
|
557
|
-
emitUpdate();
|
|
558
|
-
}
|
|
559
|
-
if (event.type === "tool_result_end" && event.message) {
|
|
560
|
-
currentResult.messages.push(event.message);
|
|
561
|
-
emitUpdate();
|
|
562
|
-
}
|
|
563
|
-
return event.type === "agent_end";
|
|
564
|
-
}
|
|
565
|
-
async function waitForFile(filePath, signal, timeoutMs = 30 * 60 * 1000) {
|
|
566
|
-
const started = Date.now();
|
|
567
|
-
while (Date.now() - started < timeoutMs) {
|
|
568
|
-
if (signal?.aborted)
|
|
569
|
-
return false;
|
|
570
|
-
if (fs.existsSync(filePath))
|
|
571
|
-
return true;
|
|
572
|
-
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
573
|
-
}
|
|
574
|
-
return false;
|
|
575
|
-
}
|
|
576
|
-
async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, modelOverride, parentModel, signal, onUpdate, makeDetails, parentSessionFile, attachableSession, onSessionInfo, onSubagentEvent, foregroundHooks) {
|
|
577
|
-
const agent = agents.find((a) => a.name === agentName);
|
|
578
|
-
if (!agent) {
|
|
579
|
-
const available = agents.map((a) => `"${a.name}"`).join(", ") || "none";
|
|
580
|
-
return {
|
|
581
|
-
agent: agentName,
|
|
582
|
-
agentSource: "unknown",
|
|
583
|
-
task,
|
|
584
|
-
exitCode: 1,
|
|
585
|
-
messages: [],
|
|
586
|
-
stderr: `Unknown agent: "${agentName}". Available agents: ${available}.`,
|
|
587
|
-
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
|
|
588
|
-
step,
|
|
589
|
-
};
|
|
590
|
-
}
|
|
591
|
-
let tmpPromptDir = null;
|
|
592
|
-
let tmpPromptPath = null;
|
|
593
|
-
const preferences = loadEffectivePreferences()?.preferences;
|
|
594
|
-
const settingsBudgetModel = readBudgetSubagentModelFromSettings();
|
|
595
|
-
const resolvedModel = resolveConfiguredSubagentModel(agent, preferences, settingsBudgetModel);
|
|
596
|
-
const inferredModel = resolveSubagentModel({ name: agent.name, model: resolvedModel }, { overrideModel: modelOverride, parentModel });
|
|
597
|
-
const currentResult = {
|
|
598
|
-
agent: agentName,
|
|
599
|
-
agentSource: agent.source,
|
|
600
|
-
task,
|
|
601
|
-
exitCode: 0,
|
|
602
|
-
messages: [],
|
|
603
|
-
stderr: "",
|
|
604
|
-
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
|
|
605
|
-
model: inferredModel,
|
|
606
|
-
step,
|
|
607
|
-
parentSessionFile,
|
|
608
|
-
};
|
|
609
|
-
const emitUpdate = () => {
|
|
610
|
-
if (onUpdate) {
|
|
611
|
-
onUpdate({
|
|
612
|
-
content: [{ type: "text", text: getFinalOutput(currentResult.messages) || "(running...)" }],
|
|
613
|
-
details: makeDetails([currentResult]),
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
|
-
};
|
|
617
|
-
let wasAborted = false;
|
|
618
|
-
let deferTempPromptCleanup = false;
|
|
619
|
-
let tempPromptCleanupDone = false;
|
|
620
|
-
const cleanupTempPromptFiles = () => {
|
|
621
|
-
if (tempPromptCleanupDone)
|
|
622
|
-
return;
|
|
623
|
-
tempPromptCleanupDone = true;
|
|
624
|
-
if (tmpPromptPath)
|
|
625
|
-
try {
|
|
626
|
-
fs.unlinkSync(tmpPromptPath);
|
|
627
|
-
}
|
|
628
|
-
catch {
|
|
629
|
-
/* ignore */
|
|
630
|
-
}
|
|
631
|
-
if (tmpPromptDir)
|
|
632
|
-
try {
|
|
633
|
-
fs.rmdirSync(tmpPromptDir);
|
|
634
|
-
}
|
|
635
|
-
catch {
|
|
636
|
-
/* ignore */
|
|
637
|
-
}
|
|
638
|
-
};
|
|
639
|
-
try {
|
|
640
|
-
if (agent.systemPrompt.trim()) {
|
|
641
|
-
const tmp = writePromptToTempFile(agent.name, agent.systemPrompt);
|
|
642
|
-
tmpPromptDir = tmp.dir;
|
|
643
|
-
tmpPromptPath = tmp.filePath;
|
|
644
|
-
}
|
|
645
|
-
const effectiveCwd = cwd ?? defaultCwd;
|
|
646
|
-
const subagentSessionDir = parentSessionFile ? path.dirname(parentSessionFile) : undefined;
|
|
647
|
-
const sessionFilesBefore = attachableSession && subagentSessionDir
|
|
648
|
-
? new Set(listSessionFiles(subagentSessionDir))
|
|
649
|
-
: undefined;
|
|
650
|
-
const launchStartedAt = Date.now();
|
|
651
|
-
const args = buildSubagentProcessArgs(agent, task, tmpPromptPath, inferredModel, {
|
|
652
|
-
noSession: !attachableSession,
|
|
653
|
-
parentSessionFile: parentSessionFile,
|
|
654
|
-
mode: attachableSession ? "rpc" : "json",
|
|
655
|
-
});
|
|
656
|
-
const exitCode = await new Promise((resolve) => {
|
|
657
|
-
const bundledPaths = getBundledExtensionPathsFromEnv();
|
|
658
|
-
const extensionArgs = bundledPaths.flatMap((p) => ["--extension", p]);
|
|
659
|
-
const cliPath = resolveSubagentCliPath(effectiveCwd);
|
|
660
|
-
if (!cliPath) {
|
|
661
|
-
currentResult.stderr += "Unable to resolve LSD/GSD CLI path for subagent launch.";
|
|
662
|
-
resolve(1);
|
|
663
|
-
return;
|
|
664
|
-
}
|
|
665
|
-
const proc = spawn(process.execPath, [cliPath, ...extensionArgs, ...args], { cwd: effectiveCwd, shell: false, stdio: ["pipe", "pipe", "pipe"] });
|
|
666
|
-
// Keep stdin open so approval/classifier responses can be proxied back
|
|
667
|
-
// into the child process. Closing it here can leave the subagent stuck
|
|
668
|
-
// after its first turn when it requests permission for a tool call.
|
|
669
|
-
liveSubagentProcesses.add(proc);
|
|
670
|
-
let buffer = "";
|
|
671
|
-
let completionSeen = false;
|
|
672
|
-
let resolved = false;
|
|
673
|
-
let foregroundReleased = false;
|
|
674
|
-
let isBusy = false;
|
|
675
|
-
let commandSeq = 0;
|
|
676
|
-
const pendingCommandResponses = new Map();
|
|
677
|
-
const procAbortController = new AbortController();
|
|
678
|
-
let resolveBackgroundResult;
|
|
679
|
-
let rejectBackgroundResult;
|
|
680
|
-
const backgroundResultPromise = new Promise((resolveBg, rejectBg) => {
|
|
681
|
-
resolveBackgroundResult = resolveBg;
|
|
682
|
-
rejectBackgroundResult = rejectBg;
|
|
683
|
-
});
|
|
684
|
-
const sendRpcCommand = async (command) => {
|
|
685
|
-
const id = `sa_cmd_${++commandSeq}`;
|
|
686
|
-
if (!proc.stdin)
|
|
687
|
-
throw new Error("Subagent RPC stdin is not available.");
|
|
688
|
-
return new Promise((resolveCmd, rejectCmd) => {
|
|
689
|
-
pendingCommandResponses.set(id, { resolve: resolveCmd, reject: rejectCmd });
|
|
690
|
-
proc.stdin.write(JSON.stringify({ id, ...command }) + "\n");
|
|
691
|
-
});
|
|
692
|
-
};
|
|
693
|
-
const finishForeground = (code) => {
|
|
694
|
-
if (resolved)
|
|
695
|
-
return;
|
|
696
|
-
resolved = true;
|
|
697
|
-
resolve(code);
|
|
698
|
-
};
|
|
699
|
-
const adoptToBackground = (jobId) => {
|
|
700
|
-
if (resolved || foregroundReleased)
|
|
701
|
-
return false;
|
|
702
|
-
foregroundReleased = true;
|
|
703
|
-
deferTempPromptCleanup = true;
|
|
704
|
-
currentResult.backgroundJobId = jobId;
|
|
705
|
-
finishForeground(0);
|
|
706
|
-
return true;
|
|
707
|
-
};
|
|
708
|
-
backgroundResultPromise.finally(() => {
|
|
709
|
-
if (deferTempPromptCleanup)
|
|
710
|
-
cleanupTempPromptFiles();
|
|
711
|
-
});
|
|
712
|
-
foregroundHooks?.onStart?.({
|
|
713
|
-
agentName,
|
|
714
|
-
task,
|
|
715
|
-
cwd: cwd ?? defaultCwd,
|
|
716
|
-
parentSessionFile,
|
|
717
|
-
abortController: procAbortController,
|
|
718
|
-
resultPromise: backgroundResultPromise,
|
|
719
|
-
adoptToBackground,
|
|
720
|
-
sendPrompt: attachableSession
|
|
721
|
-
? async (text, images) => {
|
|
722
|
-
await sendRpcCommand({ type: "prompt", message: text, images });
|
|
723
|
-
}
|
|
724
|
-
: undefined,
|
|
725
|
-
sendSteer: attachableSession
|
|
726
|
-
? async (text, images) => {
|
|
727
|
-
await sendRpcCommand({ type: "steer", message: text, images });
|
|
728
|
-
}
|
|
729
|
-
: undefined,
|
|
730
|
-
sendFollowUp: attachableSession
|
|
731
|
-
? async (text, images) => {
|
|
732
|
-
await sendRpcCommand({ type: "follow_up", message: text, images });
|
|
733
|
-
}
|
|
734
|
-
: undefined,
|
|
735
|
-
isBusy: attachableSession ? () => isBusy : undefined,
|
|
736
|
-
});
|
|
737
|
-
proc.stdout.on("data", (data) => {
|
|
738
|
-
buffer += data.toString();
|
|
739
|
-
const lines = buffer.split("\n");
|
|
740
|
-
buffer = lines.pop() || "";
|
|
741
|
-
for (const line of lines) {
|
|
742
|
-
const trimmed = line.trim();
|
|
743
|
-
if (!trimmed)
|
|
744
|
-
continue;
|
|
745
|
-
if (attachableSession) {
|
|
746
|
-
try {
|
|
747
|
-
const parsed = JSON.parse(trimmed);
|
|
748
|
-
if (parsed?.type === "response" && typeof parsed.id === "string" && pendingCommandResponses.has(parsed.id)) {
|
|
749
|
-
const pending = pendingCommandResponses.get(parsed.id);
|
|
750
|
-
pendingCommandResponses.delete(parsed.id);
|
|
751
|
-
if (parsed.success === false) {
|
|
752
|
-
pending.reject(new Error(typeof parsed.error === "string" ? parsed.error : "Subagent RPC command failed."));
|
|
753
|
-
}
|
|
754
|
-
else {
|
|
755
|
-
pending.resolve(parsed.data);
|
|
756
|
-
}
|
|
757
|
-
continue;
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
catch {
|
|
761
|
-
// Fall through to generic event processing.
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
if (processSubagentEventLine(trimmed, currentResult, emitUpdate, proc, onSessionInfo, (eventType) => {
|
|
765
|
-
if (eventType === "agent_start")
|
|
766
|
-
isBusy = true;
|
|
767
|
-
if (eventType === "agent_end")
|
|
768
|
-
isBusy = false;
|
|
769
|
-
}, (event) => onSubagentEvent?.(event, currentResult))) {
|
|
770
|
-
completionSeen = true;
|
|
771
|
-
try {
|
|
772
|
-
proc.kill("SIGTERM");
|
|
773
|
-
}
|
|
774
|
-
catch {
|
|
775
|
-
/* ignore */
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
});
|
|
780
|
-
proc.stderr.on("data", (data) => {
|
|
781
|
-
currentResult.stderr += data.toString();
|
|
782
|
-
});
|
|
783
|
-
proc.on("close", (code) => {
|
|
784
|
-
liveSubagentProcesses.delete(proc);
|
|
785
|
-
if (buffer.trim()) {
|
|
786
|
-
const completedOnFlush = processSubagentEventLine(buffer, currentResult, emitUpdate, proc, onSessionInfo, (eventType) => {
|
|
787
|
-
if (eventType === "agent_start")
|
|
788
|
-
isBusy = true;
|
|
789
|
-
if (eventType === "agent_end")
|
|
790
|
-
isBusy = false;
|
|
791
|
-
}, (event) => onSubagentEvent?.(event, currentResult));
|
|
792
|
-
completionSeen = completionSeen || completedOnFlush;
|
|
793
|
-
}
|
|
794
|
-
isBusy = false;
|
|
795
|
-
for (const pending of pendingCommandResponses.values()) {
|
|
796
|
-
pending.reject(new Error("Subagent process closed before command response."));
|
|
797
|
-
}
|
|
798
|
-
pendingCommandResponses.clear();
|
|
799
|
-
const finalExitCode = completionSeen && (code === null || code === 143 || code === 15) ? 0 : (code ?? 0);
|
|
800
|
-
currentResult.exitCode = finalExitCode;
|
|
801
|
-
if (attachableSession && sessionFilesBefore && subagentSessionDir && !currentResult.sessionFile) {
|
|
802
|
-
const detected = detectNewSubagentSessionFile(subagentSessionDir, sessionFilesBefore, launchStartedAt);
|
|
803
|
-
if (detected)
|
|
804
|
-
currentResult.sessionFile = detected;
|
|
805
|
-
}
|
|
806
|
-
resolveBackgroundResult?.({
|
|
807
|
-
summary: getFinalOutput(currentResult.messages),
|
|
808
|
-
stderr: currentResult.stderr,
|
|
809
|
-
exitCode: finalExitCode,
|
|
810
|
-
model: currentResult.model,
|
|
811
|
-
sessionFile: currentResult.sessionFile,
|
|
812
|
-
parentSessionFile: currentResult.parentSessionFile,
|
|
813
|
-
});
|
|
814
|
-
foregroundHooks?.onFinish?.();
|
|
815
|
-
finishForeground(finalExitCode);
|
|
816
|
-
});
|
|
817
|
-
proc.on("error", (error) => {
|
|
818
|
-
liveSubagentProcesses.delete(proc);
|
|
819
|
-
isBusy = false;
|
|
820
|
-
for (const pending of pendingCommandResponses.values()) {
|
|
821
|
-
pending.reject(error instanceof Error ? error : new Error(String(error)));
|
|
822
|
-
}
|
|
823
|
-
pendingCommandResponses.clear();
|
|
824
|
-
rejectBackgroundResult?.(error);
|
|
825
|
-
foregroundHooks?.onFinish?.();
|
|
826
|
-
finishForeground(1);
|
|
827
|
-
});
|
|
828
|
-
if (attachableSession) {
|
|
829
|
-
void sendRpcCommand({ type: "prompt", message: task }).catch((error) => {
|
|
830
|
-
currentResult.stderr += error instanceof Error ? error.message : String(error);
|
|
831
|
-
try {
|
|
832
|
-
proc.kill("SIGTERM");
|
|
833
|
-
}
|
|
834
|
-
catch {
|
|
835
|
-
/* ignore */
|
|
836
|
-
}
|
|
837
|
-
});
|
|
838
|
-
}
|
|
839
|
-
const killProc = () => {
|
|
840
|
-
// If the process has been adopted to the background (e.g. via Ctrl+B or
|
|
841
|
-
// /agent attach_live), foregroundReleased is true and the process should
|
|
842
|
-
// survive the parent session's abort — don't kill it.
|
|
843
|
-
if (foregroundReleased)
|
|
844
|
-
return;
|
|
845
|
-
wasAborted = true;
|
|
846
|
-
procAbortController.abort();
|
|
847
|
-
proc.kill("SIGTERM");
|
|
848
|
-
setTimeout(() => {
|
|
849
|
-
if (!proc.killed)
|
|
850
|
-
proc.kill("SIGKILL");
|
|
851
|
-
}, 5000);
|
|
852
|
-
};
|
|
853
|
-
if (signal) {
|
|
854
|
-
if (signal.aborted)
|
|
855
|
-
killProc();
|
|
856
|
-
else
|
|
857
|
-
signal.addEventListener("abort", killProc, { once: true });
|
|
858
|
-
}
|
|
859
|
-
if (procAbortController.signal.aborted) {
|
|
860
|
-
killProc();
|
|
861
|
-
}
|
|
862
|
-
else {
|
|
863
|
-
procAbortController.signal.addEventListener("abort", () => {
|
|
864
|
-
proc.kill("SIGTERM");
|
|
865
|
-
setTimeout(() => {
|
|
866
|
-
if (!proc.killed)
|
|
867
|
-
proc.kill("SIGKILL");
|
|
868
|
-
}, 5000);
|
|
869
|
-
}, { once: true });
|
|
870
|
-
}
|
|
871
|
-
});
|
|
872
|
-
currentResult.exitCode = exitCode;
|
|
873
|
-
if (attachableSession && sessionFilesBefore && subagentSessionDir) {
|
|
874
|
-
const detected = detectNewSubagentSessionFile(subagentSessionDir, sessionFilesBefore, launchStartedAt);
|
|
875
|
-
if (detected) {
|
|
876
|
-
currentResult.sessionFile = detected;
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
if (wasAborted)
|
|
880
|
-
throw new Error("Subagent was aborted");
|
|
881
|
-
return currentResult;
|
|
882
|
-
}
|
|
883
|
-
finally {
|
|
884
|
-
if (!deferTempPromptCleanup)
|
|
885
|
-
cleanupTempPromptFiles();
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
406
|
const TaskItem = Type.Object({
|
|
889
407
|
agent: Type.String({ description: "Name of the agent to invoke" }),
|
|
890
408
|
task: Type.String({ description: "Task to delegate to the agent" }),
|
|
@@ -1033,7 +551,6 @@ export default function (pi) {
|
|
|
1033
551
|
"Do not spawn or delegate to another subagent with the same name as yourself.",
|
|
1034
552
|
`If the user asks you to continue ${subagentName} work, do that work directly in this session.`,
|
|
1035
553
|
taskNote,
|
|
1036
|
-
"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.",
|
|
1037
554
|
].join("\n");
|
|
1038
555
|
return {
|
|
1039
556
|
systemPrompt: `${event.systemPrompt}\n\n${antiRecursion}\n\n${metadata.subagentSystemPrompt}`,
|
|
@@ -1041,9 +558,19 @@ export default function (pi) {
|
|
|
1041
558
|
});
|
|
1042
559
|
pi.on("input", async (event, ctx) => {
|
|
1043
560
|
const sessionFile = ctx.sessionManager.getSessionFile();
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
561
|
+
const runtimeFromSession = sessionFile ? liveRuntimeBySessionFile.get(sessionFile) : undefined;
|
|
562
|
+
const runtimeFromForeground = (!runtimeFromSession && activeForegroundSubagent && !activeForegroundSubagent.claimed &&
|
|
563
|
+
activeForegroundSubagent.sendPrompt && activeForegroundSubagent.sendSteer && activeForegroundSubagent.sendFollowUp && activeForegroundSubagent.isBusy &&
|
|
564
|
+
sessionFile && activeForegroundSubagent.parentSessionFile === sessionFile)
|
|
565
|
+
? {
|
|
566
|
+
agentName: activeForegroundSubagent.agentName,
|
|
567
|
+
isBusy: activeForegroundSubagent.isBusy,
|
|
568
|
+
sendPrompt: activeForegroundSubagent.sendPrompt,
|
|
569
|
+
sendSteer: activeForegroundSubagent.sendSteer,
|
|
570
|
+
sendFollowUp: activeForegroundSubagent.sendFollowUp,
|
|
571
|
+
}
|
|
572
|
+
: undefined;
|
|
573
|
+
const runtime = runtimeFromSession ?? runtimeFromForeground;
|
|
1047
574
|
if (!runtime)
|
|
1048
575
|
return;
|
|
1049
576
|
const text = event.text?.trim();
|
|
@@ -1087,6 +614,8 @@ export default function (pi) {
|
|
|
1087
614
|
agentSessionIdsByParent.clear();
|
|
1088
615
|
parentSessionByChild.clear();
|
|
1089
616
|
liveRuntimeBySessionFile.clear();
|
|
617
|
+
inProcessSubagentDepthBySessionId.clear();
|
|
618
|
+
inProcessSubagentAncestryBySessionId.clear();
|
|
1090
619
|
liveStreamBufferBySession.clear();
|
|
1091
620
|
});
|
|
1092
621
|
// /subagents command
|
|
@@ -1226,31 +755,6 @@ export default function (pi) {
|
|
|
1226
755
|
ctx.ui.notify("Live runtime is no longer available for this subagent. It may have completed.", "warning");
|
|
1227
756
|
return;
|
|
1228
757
|
}
|
|
1229
|
-
// Adopt the foreground subagent to background before switching sessions.
|
|
1230
|
-
// switchSession calls abort() which would fire the tool signal and SIGTERM
|
|
1231
|
-
// the running subagent process. Adopting to background detaches the process
|
|
1232
|
-
// from the foreground abort chain so it survives the session switch.
|
|
1233
|
-
const foreground = activeForegroundSubagent;
|
|
1234
|
-
if (foreground && !foreground.claimed && bgManager) {
|
|
1235
|
-
foreground.claimed = true;
|
|
1236
|
-
try {
|
|
1237
|
-
const jobId = bgManager.adoptRunning(foreground.agentName, foreground.task, foreground.cwd, foreground.abortController, foreground.resultPromise, {
|
|
1238
|
-
parentSessionFile: foreground.parentSessionFile ?? ctx.sessionManager.getSessionFile(),
|
|
1239
|
-
});
|
|
1240
|
-
const released = foreground.adoptToBackground(jobId);
|
|
1241
|
-
if (!released) {
|
|
1242
|
-
foreground.claimed = false;
|
|
1243
|
-
bgManager.cancel(jobId);
|
|
1244
|
-
}
|
|
1245
|
-
else {
|
|
1246
|
-
activeForegroundSubagent = null;
|
|
1247
|
-
ctx.ui.setStatus(foregroundSubagentStatusKey, undefined);
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
catch {
|
|
1251
|
-
foreground.claimed = false;
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
758
|
const switched = await ctx.switchSession(target.sessionFile);
|
|
1255
759
|
if (switched.cancelled) {
|
|
1256
760
|
ctx.ui.notify("Session switch was cancelled.", "warning");
|
|
@@ -1390,9 +894,19 @@ export default function (pi) {
|
|
|
1390
894
|
running.claimed = true;
|
|
1391
895
|
let jobId;
|
|
1392
896
|
try {
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
897
|
+
if (running.handle) {
|
|
898
|
+
jobId = manager.adoptHandle(running.agentName, running.task, running.cwd, running.handle, running.resultPromise, {
|
|
899
|
+
parentSessionFile: running.parentSessionFile ?? ctx.sessionManager.getSessionFile(),
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
else if (running.abortController) {
|
|
903
|
+
jobId = manager.adoptRunning(running.agentName, running.task, running.cwd, running.abortController, running.resultPromise, {
|
|
904
|
+
parentSessionFile: running.parentSessionFile ?? ctx.sessionManager.getSessionFile(),
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
else {
|
|
908
|
+
throw new Error("Foreground subagent cannot be moved to background (missing runtime handle).");
|
|
909
|
+
}
|
|
1396
910
|
}
|
|
1397
911
|
catch (error) {
|
|
1398
912
|
running.claimed = false;
|
|
@@ -1456,7 +970,7 @@ export default function (pi) {
|
|
|
1456
970
|
"For broad review or audit requests, use scout only as a prep step; the parent model or a reviewer should make the final judgments.",
|
|
1457
971
|
"Skip scout when the user already named the exact file/function to inspect or the task is obviously narrow.",
|
|
1458
972
|
"Use parallel mode when tasks are independent and don't need each other's output.",
|
|
1459
|
-
"
|
|
973
|
+
"Use background: true when the user wants to keep chatting while a long-running agent works in parallel.",
|
|
1460
974
|
"If the user wants to wait for a background subagent result, use await_subagent.",
|
|
1461
975
|
],
|
|
1462
976
|
parameters: SubagentParams,
|
|
@@ -1468,6 +982,14 @@ export default function (pi) {
|
|
|
1468
982
|
const cmuxClient = CmuxClient.fromPreferences(loadEffectivePreferences()?.preferences);
|
|
1469
983
|
const cmuxSplitsEnabled = cmuxClient.getConfig().splits;
|
|
1470
984
|
const invokingSessionFile = ctx.sessionManager.getSessionFile();
|
|
985
|
+
const invokingSessionId = ctx.sessionManager.getSessionId();
|
|
986
|
+
const invokingMetadata = getCurrentSessionSubagentMetadata(invokingSessionFile);
|
|
987
|
+
const currentSubagentName = invokingMetadata?.subagentName;
|
|
988
|
+
const trackedAncestry = inProcessSubagentAncestryBySessionId.get(invokingSessionId);
|
|
989
|
+
const currentAncestry = trackedAncestry ?? (currentSubagentName ? [currentSubagentName] : []);
|
|
990
|
+
const inferredCurrentDepth = currentSubagentName ? Math.max(currentAncestry.length, 1) : 0;
|
|
991
|
+
const currentSubagentDepth = Math.max(inProcessSubagentDepthBySessionId.get(invokingSessionId) ?? 0, inferredCurrentDepth);
|
|
992
|
+
const nextSubagentDepth = currentSubagentDepth + 1;
|
|
1471
993
|
// Resolve isolation mode
|
|
1472
994
|
const isolationMode = readIsolationMode();
|
|
1473
995
|
const useIsolation = Boolean(params.isolated) && isolationMode !== "none";
|
|
@@ -1481,6 +1003,30 @@ export default function (pi) {
|
|
|
1481
1003
|
projectAgentsDir: discovery.projectAgentsDir,
|
|
1482
1004
|
results,
|
|
1483
1005
|
});
|
|
1006
|
+
const trackInProcessDepth = (started, depth, ancestry) => {
|
|
1007
|
+
const sessionId = started.handle.sessionId;
|
|
1008
|
+
if (!sessionId)
|
|
1009
|
+
return;
|
|
1010
|
+
inProcessSubagentDepthBySessionId.set(sessionId, depth);
|
|
1011
|
+
inProcessSubagentAncestryBySessionId.set(sessionId, ancestry);
|
|
1012
|
+
void started.resultPromise.finally(() => {
|
|
1013
|
+
inProcessSubagentDepthBySessionId.delete(sessionId);
|
|
1014
|
+
inProcessSubagentAncestryBySessionId.delete(sessionId);
|
|
1015
|
+
});
|
|
1016
|
+
};
|
|
1017
|
+
const buildChildAncestry = (childAgentName) => [...currentAncestry, childAgentName];
|
|
1018
|
+
const requestedAgentNames = hasSingle
|
|
1019
|
+
? [params.agent]
|
|
1020
|
+
: hasChain
|
|
1021
|
+
? (params.chain ?? []).map((step) => step.agent)
|
|
1022
|
+
: (params.tasks ?? []).map((task) => task.agent);
|
|
1023
|
+
if (currentSubagentName && requestedAgentNames.some((name) => name === currentSubagentName)) {
|
|
1024
|
+
return {
|
|
1025
|
+
content: [{ type: "text", text: `Subagent "${currentSubagentName}" cannot spawn another subagent with the same name as itself.` }],
|
|
1026
|
+
details: makeDetails(hasChain ? "chain" : hasTasks ? "parallel" : "single")([]),
|
|
1027
|
+
isError: true,
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1484
1030
|
if (modeCount !== 1) {
|
|
1485
1031
|
const available = agents.map((a) => `${a.name} (${a.source})`).join(", ") || "none";
|
|
1486
1032
|
return {
|
|
@@ -1500,6 +1046,20 @@ export default function (pi) {
|
|
|
1500
1046
|
isError: true,
|
|
1501
1047
|
};
|
|
1502
1048
|
}
|
|
1049
|
+
if (params.background && currentSubagentDepth > 0) {
|
|
1050
|
+
return {
|
|
1051
|
+
content: [{ type: "text", text: "Nested background subagent launches are not supported yet. Run the nested subagent in foreground mode." }],
|
|
1052
|
+
details: makeDetails("single")([]),
|
|
1053
|
+
isError: true,
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
if (USE_IN_PROCESS_SUBAGENT && nextSubagentDepth > MAX_IN_PROCESS_SUBAGENT_DEPTH) {
|
|
1057
|
+
return {
|
|
1058
|
+
content: [{ type: "text", text: `Max subagent depth (${MAX_IN_PROCESS_SUBAGENT_DEPTH}) exceeded.` }],
|
|
1059
|
+
details: makeDetails(hasChain ? "chain" : hasTasks ? "parallel" : "single")([]),
|
|
1060
|
+
isError: true,
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1503
1063
|
if ((agentScope === "project" || agentScope === "both") && confirmProjectAgents && ctx.hasUI) {
|
|
1504
1064
|
const requestedAgentNames = new Set();
|
|
1505
1065
|
if (params.chain)
|
|
@@ -1544,7 +1104,13 @@ export default function (pi) {
|
|
|
1544
1104
|
}
|
|
1545
1105
|
}
|
|
1546
1106
|
: undefined;
|
|
1547
|
-
const result =
|
|
1107
|
+
const result = USE_IN_PROCESS_SUBAGENT
|
|
1108
|
+
? await (async () => {
|
|
1109
|
+
const started = await startInProcessSingleAgent(ctx.cwd, agents, step.agent, taskWithContext, step.cwd, i + 1, step.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, signal, chainUpdate, makeDetails("chain"), invokingSessionFile, nextSubagentDepth, invokingSessionId, currentAncestry);
|
|
1110
|
+
trackInProcessDepth(started, nextSubagentDepth, buildChildAncestry(step.agent));
|
|
1111
|
+
return started.resultPromise;
|
|
1112
|
+
})()
|
|
1113
|
+
: await runLegacySingleAgent(ctx.cwd, agents, step.agent, taskWithContext, step.cwd, i + 1, step.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, signal, chainUpdate, makeDetails("chain"), invokingSessionFile, false, undefined, undefined);
|
|
1548
1114
|
results.push(result);
|
|
1549
1115
|
const isError = result.exitCode !== 0 || result.stopReason === "error" || result.stopReason === "aborted";
|
|
1550
1116
|
if (isError) {
|
|
@@ -1606,12 +1172,23 @@ export default function (pi) {
|
|
|
1606
1172
|
const gridSurfaces = [];
|
|
1607
1173
|
const results = await mapWithConcurrencyLimit(params.tasks, MAX_CONCURRENCY, async (t, index) => {
|
|
1608
1174
|
const workerId = registerWorker(t.agent, t.task, index, batchSize, batchId);
|
|
1609
|
-
const runTask = () =>
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1175
|
+
const runTask = () => USE_IN_PROCESS_SUBAGENT
|
|
1176
|
+
? (async () => {
|
|
1177
|
+
const started = await startInProcessSingleAgent(ctx.cwd, agents, t.agent, t.task, t.cwd, undefined, t.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, signal, (partial) => {
|
|
1178
|
+
if (partial.details?.results[0]) {
|
|
1179
|
+
allResults[index] = partial.details.results[0];
|
|
1180
|
+
emitParallelUpdate();
|
|
1181
|
+
}
|
|
1182
|
+
}, makeDetails("parallel"), invokingSessionFile, nextSubagentDepth, invokingSessionId, currentAncestry);
|
|
1183
|
+
trackInProcessDepth(started, nextSubagentDepth, buildChildAncestry(t.agent));
|
|
1184
|
+
return started.resultPromise;
|
|
1185
|
+
})()
|
|
1186
|
+
: runLegacySingleAgent(ctx.cwd, agents, t.agent, t.task, t.cwd, undefined, t.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, signal, (partial) => {
|
|
1187
|
+
if (partial.details?.results[0]) {
|
|
1188
|
+
allResults[index] = partial.details.results[0];
|
|
1189
|
+
emitParallelUpdate();
|
|
1190
|
+
}
|
|
1191
|
+
}, makeDetails("parallel"), invokingSessionFile, false, undefined);
|
|
1615
1192
|
let result = await runTask();
|
|
1616
1193
|
// Auto-retry failed tasks (likely API rate limit or transient error)
|
|
1617
1194
|
const isFailed = result.exitCode !== 0 || (result.messages.length === 0 && !signal?.aborted);
|
|
@@ -1668,11 +1245,164 @@ export default function (pi) {
|
|
|
1668
1245
|
const bgInferredModel = resolveSubagentModel({ name: agentForBg.name, model: bgResolvedModelCfg }, { overrideModel: params.model, parentModel: ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined });
|
|
1669
1246
|
let jobId;
|
|
1670
1247
|
try {
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
const
|
|
1675
|
-
|
|
1248
|
+
if (USE_IN_PROCESS_SUBAGENT) {
|
|
1249
|
+
const started = await startInProcessSingleAgent(ctx.cwd, agents, params.agent, params.task, params.cwd, undefined, params.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, undefined, undefined, makeDetails("single"), invokingSessionFile, nextSubagentDepth, invokingSessionId, currentAncestry);
|
|
1250
|
+
trackInProcessDepth(started, nextSubagentDepth, buildChildAncestry(params.agent));
|
|
1251
|
+
const effectiveCwd = params.cwd ?? ctx.cwd;
|
|
1252
|
+
jobId = manager.adoptHandle(params.agent, params.task, effectiveCwd, started.handle, started.resultPromise.then((result) => ({
|
|
1253
|
+
summary: (getFinalOutput(result.messages) || "(no output)").slice(0, 300),
|
|
1254
|
+
stderr: result.stderr,
|
|
1255
|
+
exitCode: result.exitCode,
|
|
1256
|
+
model: result.model,
|
|
1257
|
+
sessionFile: result.sessionFile,
|
|
1258
|
+
parentSessionFile: result.parentSessionFile,
|
|
1259
|
+
})), {
|
|
1260
|
+
parentSessionFile: invokingSessionFile,
|
|
1261
|
+
model: bgInferredModel,
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
else {
|
|
1265
|
+
jobId = runSubagentInBackground(manager, agents, params.agent, params.task, params.cwd, params.model, { defaultCwd: ctx.cwd, model: ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, parentSessionFile: invokingSessionFile }, async (bgSignal) => {
|
|
1266
|
+
let liveSessionFile;
|
|
1267
|
+
let liveRuntime;
|
|
1268
|
+
const result = await runLegacySingleAgent(ctx.cwd, agents, params.agent, params.task, params.cwd, undefined, params.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, bgSignal, undefined, // no streaming updates for background jobs
|
|
1269
|
+
makeDetails("single"), invokingSessionFile, true, (info) => {
|
|
1270
|
+
if (!invokingSessionFile || !info.sessionFile)
|
|
1271
|
+
return;
|
|
1272
|
+
upsertAgentSessionLink(params.agent, params.task, invokingSessionFile, info.sessionFile, "running");
|
|
1273
|
+
liveSessionFile = info.sessionFile;
|
|
1274
|
+
if (liveRuntime) {
|
|
1275
|
+
liveRuntime.sessionFile = info.sessionFile;
|
|
1276
|
+
liveRuntime.parentSessionFile = info.parentSessionFile ?? invokingSessionFile;
|
|
1277
|
+
liveRuntimeBySessionFile.set(info.sessionFile, liveRuntime);
|
|
1278
|
+
}
|
|
1279
|
+
}, (event, partial) => {
|
|
1280
|
+
const sessionFile = partial.sessionFile;
|
|
1281
|
+
if (!sessionFile || activeSessionFileForUi !== sessionFile)
|
|
1282
|
+
return;
|
|
1283
|
+
if (event?.type === "message_update" && event.assistantMessageEvent?.type === "text_delta") {
|
|
1284
|
+
const delta = String(event.assistantMessageEvent.delta ?? "");
|
|
1285
|
+
if (delta)
|
|
1286
|
+
pushLiveStreamDelta(sessionFile, delta);
|
|
1287
|
+
}
|
|
1288
|
+
if (event?.type === "message_end") {
|
|
1289
|
+
flushLiveStream(sessionFile);
|
|
1290
|
+
}
|
|
1291
|
+
}, {
|
|
1292
|
+
onStart: (control) => {
|
|
1293
|
+
if (!control.sendPrompt || !control.sendSteer || !control.sendFollowUp || !control.isBusy)
|
|
1294
|
+
return;
|
|
1295
|
+
liveRuntime = {
|
|
1296
|
+
sessionFile: liveSessionFile,
|
|
1297
|
+
parentSessionFile: invokingSessionFile,
|
|
1298
|
+
agentName: params.agent,
|
|
1299
|
+
isBusy: control.isBusy,
|
|
1300
|
+
sendPrompt: control.sendPrompt,
|
|
1301
|
+
sendSteer: control.sendSteer,
|
|
1302
|
+
sendFollowUp: control.sendFollowUp,
|
|
1303
|
+
};
|
|
1304
|
+
if (liveSessionFile) {
|
|
1305
|
+
liveRuntimeBySessionFile.set(liveSessionFile, liveRuntime);
|
|
1306
|
+
}
|
|
1307
|
+
},
|
|
1308
|
+
onFinish: () => {
|
|
1309
|
+
if (liveSessionFile)
|
|
1310
|
+
liveRuntimeBySessionFile.delete(liveSessionFile);
|
|
1311
|
+
},
|
|
1312
|
+
});
|
|
1313
|
+
return {
|
|
1314
|
+
exitCode: result.exitCode,
|
|
1315
|
+
finalOutput: getFinalOutput(result.messages),
|
|
1316
|
+
stderr: result.stderr,
|
|
1317
|
+
model: result.model,
|
|
1318
|
+
sessionFile: result.sessionFile,
|
|
1319
|
+
parentSessionFile: result.parentSessionFile,
|
|
1320
|
+
};
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
catch (err) {
|
|
1325
|
+
return {
|
|
1326
|
+
content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }],
|
|
1327
|
+
details: makeDetails("single")([]),
|
|
1328
|
+
isError: true,
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
const bgModelLine = bgInferredModel ? `\nModel: ${bgInferredModel}` : "";
|
|
1332
|
+
return {
|
|
1333
|
+
content: [{ type: "text", text: `Background subagent started. Job ID: **${jobId}**\nAgent: ${params.agent}${bgModelLine}\nUse \`await_subagent\` to wait, \`/subagents wait ${jobId}\` to block in the TUI, or \`/subagents cancel ${jobId}\` to stop it.` }],
|
|
1334
|
+
details: makeDetails("single")([]),
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
// ── Foreground (blocking) mode ───────────────────────────────
|
|
1338
|
+
let isolation = null;
|
|
1339
|
+
let mergeResult;
|
|
1340
|
+
try {
|
|
1341
|
+
const effectiveCwd = params.cwd ?? ctx.cwd;
|
|
1342
|
+
if (useIsolation) {
|
|
1343
|
+
const taskId = crypto.randomUUID();
|
|
1344
|
+
isolation = await createIsolation(effectiveCwd, taskId, isolationMode);
|
|
1345
|
+
}
|
|
1346
|
+
let result;
|
|
1347
|
+
if (USE_IN_PROCESS_SUBAGENT && !isolation) {
|
|
1348
|
+
const started = await startInProcessSingleAgent(ctx.cwd, agents, params.agent, params.task, params.cwd, undefined, params.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, signal, onUpdate, makeDetails("single"), invokingSessionFile, nextSubagentDepth, invokingSessionId, currentAncestry);
|
|
1349
|
+
trackInProcessDepth(started, nextSubagentDepth, buildChildAncestry(params.agent));
|
|
1350
|
+
const effectiveRunCwd = params.cwd ?? ctx.cwd;
|
|
1351
|
+
let releaseToBackground;
|
|
1352
|
+
const movedToBackground = new Promise((resolve) => {
|
|
1353
|
+
releaseToBackground = resolve;
|
|
1354
|
+
});
|
|
1355
|
+
activeForegroundSubagent = {
|
|
1356
|
+
claimed: false,
|
|
1357
|
+
agentName: params.agent,
|
|
1358
|
+
task: params.task,
|
|
1359
|
+
cwd: effectiveRunCwd,
|
|
1360
|
+
parentSessionFile: invokingSessionFile,
|
|
1361
|
+
handle: started.handle,
|
|
1362
|
+
resultPromise: started.resultPromise.then((done) => ({
|
|
1363
|
+
summary: getFinalOutput(done.messages) || "(no output)",
|
|
1364
|
+
stderr: done.stderr,
|
|
1365
|
+
exitCode: done.exitCode,
|
|
1366
|
+
model: done.model,
|
|
1367
|
+
sessionFile: done.sessionFile,
|
|
1368
|
+
parentSessionFile: done.parentSessionFile,
|
|
1369
|
+
})),
|
|
1370
|
+
adoptToBackground: (jobId) => {
|
|
1371
|
+
if (!releaseToBackground)
|
|
1372
|
+
return false;
|
|
1373
|
+
releaseToBackground(jobId);
|
|
1374
|
+
releaseToBackground = undefined;
|
|
1375
|
+
return true;
|
|
1376
|
+
},
|
|
1377
|
+
sendPrompt: started.handle.prompt,
|
|
1378
|
+
sendSteer: started.handle.steer,
|
|
1379
|
+
sendFollowUp: started.handle.followUp,
|
|
1380
|
+
isBusy: started.handle.isBusy,
|
|
1381
|
+
};
|
|
1382
|
+
ctx.ui.setStatus(foregroundSubagentStatusKey, foregroundSubagentHint);
|
|
1383
|
+
const winner = await Promise.race([
|
|
1384
|
+
started.resultPromise.then((done) => ({ type: "done", done })),
|
|
1385
|
+
movedToBackground.then((jobId) => ({ type: "background", jobId })),
|
|
1386
|
+
]);
|
|
1387
|
+
if (winner.type === "background") {
|
|
1388
|
+
activeForegroundSubagent = null;
|
|
1389
|
+
ctx.ui.setStatus(foregroundSubagentStatusKey, undefined);
|
|
1390
|
+
result = {
|
|
1391
|
+
...started.currentResult,
|
|
1392
|
+
backgroundJobId: winner.jobId,
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
else {
|
|
1396
|
+
activeForegroundSubagent = null;
|
|
1397
|
+
ctx.ui.setStatus(foregroundSubagentStatusKey, undefined);
|
|
1398
|
+
result = winner.done;
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
else {
|
|
1402
|
+
let liveSessionFile;
|
|
1403
|
+
let liveRuntime;
|
|
1404
|
+
result = await runLegacySingleAgent(ctx.cwd, agents, params.agent, params.task, isolation ? isolation.workDir : params.cwd, undefined, params.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, signal, onUpdate, makeDetails("single"), invokingSessionFile, !isolation, !isolation
|
|
1405
|
+
? (info) => {
|
|
1676
1406
|
if (!invokingSessionFile || !info.sessionFile)
|
|
1677
1407
|
return;
|
|
1678
1408
|
upsertAgentSessionLink(params.agent, params.task, invokingSessionFile, info.sessionFile, "running");
|
|
@@ -1682,7 +1412,9 @@ export default function (pi) {
|
|
|
1682
1412
|
liveRuntime.parentSessionFile = info.parentSessionFile ?? invokingSessionFile;
|
|
1683
1413
|
liveRuntimeBySessionFile.set(info.sessionFile, liveRuntime);
|
|
1684
1414
|
}
|
|
1685
|
-
}
|
|
1415
|
+
}
|
|
1416
|
+
: undefined, !isolation
|
|
1417
|
+
? (event, partial) => {
|
|
1686
1418
|
const sessionFile = partial.sessionFile;
|
|
1687
1419
|
if (!sessionFile || activeSessionFileForUi !== sessionFile)
|
|
1688
1420
|
return;
|
|
@@ -1694,8 +1426,12 @@ export default function (pi) {
|
|
|
1694
1426
|
if (event?.type === "message_end") {
|
|
1695
1427
|
flushLiveStream(sessionFile);
|
|
1696
1428
|
}
|
|
1697
|
-
}
|
|
1429
|
+
}
|
|
1430
|
+
: undefined, !isolation
|
|
1431
|
+
? {
|
|
1698
1432
|
onStart: (control) => {
|
|
1433
|
+
activeForegroundSubagent = { ...control, claimed: false };
|
|
1434
|
+
ctx.ui.setStatus(foregroundSubagentStatusKey, foregroundSubagentHint);
|
|
1699
1435
|
if (!control.sendPrompt || !control.sendSteer || !control.sendFollowUp || !control.isBusy)
|
|
1700
1436
|
return;
|
|
1701
1437
|
liveRuntime = {
|
|
@@ -1707,103 +1443,19 @@ export default function (pi) {
|
|
|
1707
1443
|
sendSteer: control.sendSteer,
|
|
1708
1444
|
sendFollowUp: control.sendFollowUp,
|
|
1709
1445
|
};
|
|
1710
|
-
if (liveSessionFile) {
|
|
1446
|
+
if (liveSessionFile && liveRuntime) {
|
|
1711
1447
|
liveRuntimeBySessionFile.set(liveSessionFile, liveRuntime);
|
|
1712
1448
|
}
|
|
1713
1449
|
},
|
|
1714
1450
|
onFinish: () => {
|
|
1451
|
+
activeForegroundSubagent = null;
|
|
1452
|
+
ctx.ui.setStatus(foregroundSubagentStatusKey, undefined);
|
|
1715
1453
|
if (liveSessionFile)
|
|
1716
1454
|
liveRuntimeBySessionFile.delete(liveSessionFile);
|
|
1717
1455
|
},
|
|
1718
|
-
});
|
|
1719
|
-
return {
|
|
1720
|
-
exitCode: result.exitCode,
|
|
1721
|
-
finalOutput: getFinalOutput(result.messages),
|
|
1722
|
-
stderr: result.stderr,
|
|
1723
|
-
model: result.model,
|
|
1724
|
-
sessionFile: result.sessionFile,
|
|
1725
|
-
parentSessionFile: result.parentSessionFile,
|
|
1726
|
-
};
|
|
1727
|
-
});
|
|
1728
|
-
}
|
|
1729
|
-
catch (err) {
|
|
1730
|
-
return {
|
|
1731
|
-
content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }],
|
|
1732
|
-
details: makeDetails("single")([]),
|
|
1733
|
-
isError: true,
|
|
1734
|
-
};
|
|
1735
|
-
}
|
|
1736
|
-
const bgModelLine = bgInferredModel ? `\nModel: ${bgInferredModel}` : "";
|
|
1737
|
-
return {
|
|
1738
|
-
content: [{ type: "text", text: `Background subagent started. Job ID: **${jobId}**\nAgent: ${params.agent}${bgModelLine}\nUse \`await_subagent\` to wait, \`/subagents wait ${jobId}\` to block in the TUI, or \`/subagents cancel ${jobId}\` to stop it.` }],
|
|
1739
|
-
details: makeDetails("single")([]),
|
|
1740
|
-
};
|
|
1741
|
-
}
|
|
1742
|
-
// ── Foreground (blocking) mode ───────────────────────────────
|
|
1743
|
-
let isolation = null;
|
|
1744
|
-
let mergeResult;
|
|
1745
|
-
try {
|
|
1746
|
-
const effectiveCwd = params.cwd ?? ctx.cwd;
|
|
1747
|
-
if (useIsolation) {
|
|
1748
|
-
const taskId = crypto.randomUUID();
|
|
1749
|
-
isolation = await createIsolation(effectiveCwd, taskId, isolationMode);
|
|
1750
|
-
}
|
|
1751
|
-
let liveSessionFile;
|
|
1752
|
-
let liveRuntime;
|
|
1753
|
-
const result = await runSingleAgent(ctx.cwd, agents, params.agent, params.task, isolation ? isolation.workDir : params.cwd, undefined, params.model, ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined, signal, onUpdate, makeDetails("single"), invokingSessionFile, !isolation, !isolation
|
|
1754
|
-
? (info) => {
|
|
1755
|
-
if (!invokingSessionFile || !info.sessionFile)
|
|
1756
|
-
return;
|
|
1757
|
-
upsertAgentSessionLink(params.agent, params.task, invokingSessionFile, info.sessionFile, "running");
|
|
1758
|
-
liveSessionFile = info.sessionFile;
|
|
1759
|
-
if (liveRuntime) {
|
|
1760
|
-
liveRuntime.sessionFile = info.sessionFile;
|
|
1761
|
-
liveRuntime.parentSessionFile = info.parentSessionFile ?? invokingSessionFile;
|
|
1762
|
-
liveRuntimeBySessionFile.set(info.sessionFile, liveRuntime);
|
|
1763
1456
|
}
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
? (event, partial) => {
|
|
1767
|
-
const sessionFile = partial.sessionFile;
|
|
1768
|
-
if (!sessionFile || activeSessionFileForUi !== sessionFile)
|
|
1769
|
-
return;
|
|
1770
|
-
if (event?.type === "message_update" && event.assistantMessageEvent?.type === "text_delta") {
|
|
1771
|
-
const delta = String(event.assistantMessageEvent.delta ?? "");
|
|
1772
|
-
if (delta)
|
|
1773
|
-
pushLiveStreamDelta(sessionFile, delta);
|
|
1774
|
-
}
|
|
1775
|
-
if (event?.type === "message_end") {
|
|
1776
|
-
flushLiveStream(sessionFile);
|
|
1777
|
-
}
|
|
1778
|
-
}
|
|
1779
|
-
: undefined, !isolation
|
|
1780
|
-
? {
|
|
1781
|
-
onStart: (control) => {
|
|
1782
|
-
activeForegroundSubagent = { ...control, claimed: false };
|
|
1783
|
-
ctx.ui.setStatus(foregroundSubagentStatusKey, foregroundSubagentHint);
|
|
1784
|
-
if (!control.sendPrompt || !control.sendSteer || !control.sendFollowUp || !control.isBusy)
|
|
1785
|
-
return;
|
|
1786
|
-
liveRuntime = {
|
|
1787
|
-
sessionFile: liveSessionFile,
|
|
1788
|
-
parentSessionFile: invokingSessionFile,
|
|
1789
|
-
agentName: params.agent,
|
|
1790
|
-
isBusy: control.isBusy,
|
|
1791
|
-
sendPrompt: control.sendPrompt,
|
|
1792
|
-
sendSteer: control.sendSteer,
|
|
1793
|
-
sendFollowUp: control.sendFollowUp,
|
|
1794
|
-
};
|
|
1795
|
-
if (liveSessionFile && liveRuntime) {
|
|
1796
|
-
liveRuntimeBySessionFile.set(liveSessionFile, liveRuntime);
|
|
1797
|
-
}
|
|
1798
|
-
},
|
|
1799
|
-
onFinish: () => {
|
|
1800
|
-
activeForegroundSubagent = null;
|
|
1801
|
-
ctx.ui.setStatus(foregroundSubagentStatusKey, undefined);
|
|
1802
|
-
if (liveSessionFile)
|
|
1803
|
-
liveRuntimeBySessionFile.delete(liveSessionFile);
|
|
1804
|
-
},
|
|
1805
|
-
}
|
|
1806
|
-
: undefined);
|
|
1457
|
+
: undefined);
|
|
1458
|
+
}
|
|
1807
1459
|
if (result.sessionFile && invokingSessionFile) {
|
|
1808
1460
|
const existingParent = parentSessionByChild.get(result.sessionFile);
|
|
1809
1461
|
if (!existingParent) {
|