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.
- 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/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/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.d.ts +6 -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 +12 -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/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 +26 -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/footer.d.ts +2 -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 +7 -0
- 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 +4 -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 +26 -2
- 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 +6 -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 +18 -4
- 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 +13 -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 +49 -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 +197 -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 +97 -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/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 +3 -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 +4 -0
- 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 +35 -0
- 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/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.fast-mode.test.ts +46 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +18 -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/modes/interactive/components/__tests__/tool-summary-line.test.ts +31 -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/footer.ts +8 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +32 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +1154 -1136
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-summary-line.ts +64 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.ts +228 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +494 -398
- 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 +3 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +38 -0
- package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +60 -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/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
|
@@ -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
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
1283
|
-
const
|
|
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
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
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
|
-
"
|
|
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 =
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
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
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
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
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
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
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
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
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
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
|
-
|
|
2181
|
-
|
|
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
|
-
|
|
2185
|
-
|
|
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);
|