lsd-pi 1.3.2 → 1.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/dist/resources/extensions/browser-tools/tools/codegen.js +5 -5
  2. package/dist/resources/extensions/browser-tools/tools/navigation.js +107 -178
  3. package/dist/resources/extensions/browser-tools/tools/network-mock.js +112 -167
  4. package/dist/resources/extensions/browser-tools/tools/pages.js +182 -234
  5. package/dist/resources/extensions/browser-tools/tools/refs.js +202 -461
  6. package/dist/resources/extensions/browser-tools/tools/session.js +176 -323
  7. package/dist/resources/extensions/browser-tools/tools/state-persistence.js +91 -154
  8. package/dist/resources/extensions/browser-tools/utils.js +1 -1
  9. package/dist/resources/extensions/slash-commands/extension-manifest.json +2 -2
  10. package/dist/resources/extensions/slash-commands/fast.js +73 -0
  11. package/dist/resources/extensions/slash-commands/index.js +2 -0
  12. package/dist/resources/extensions/slash-commands/plan.js +37 -12
  13. package/dist/resources/extensions/subagent/background-job-manager.js +13 -0
  14. package/dist/resources/extensions/subagent/in-process-runner.js +387 -0
  15. package/dist/resources/extensions/subagent/index.js +278 -626
  16. package/dist/resources/extensions/subagent/legacy-runner.js +503 -0
  17. package/dist/resources/extensions/voice/index.js +96 -36
  18. package/dist/resources/extensions/voice/push-to-talk.js +26 -0
  19. package/package.json +1 -1
  20. package/packages/pi-agent-core/dist/agent.d.ts +19 -0
  21. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  22. package/packages/pi-agent-core/dist/agent.js +16 -0
  23. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  24. package/packages/pi-agent-core/src/agent.ts +32 -2
  25. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts +34 -1
  26. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
  27. package/packages/pi-ai/dist/providers/openai-codex-responses.js +32 -4
  28. package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  29. package/packages/pi-ai/dist/providers/openai-codex-responses.test.js +127 -16
  30. package/packages/pi-ai/dist/providers/openai-codex-responses.test.js.map +1 -1
  31. package/packages/pi-ai/dist/providers/openai-responses.d.ts +8 -1
  32. package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  33. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.d.ts +2 -0
  34. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.d.ts.map +1 -0
  35. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.js +67 -0
  36. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.js.map +1 -0
  37. package/packages/pi-ai/dist/providers/openai-responses.js +21 -3
  38. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  39. package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  40. package/packages/pi-ai/dist/providers/simple-options.js +2 -0
  41. package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
  42. package/packages/pi-ai/dist/types.d.ts +5 -0
  43. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  44. package/packages/pi-ai/dist/types.js.map +1 -1
  45. package/packages/pi-ai/src/providers/openai-codex-responses.test.ts +143 -20
  46. package/packages/pi-ai/src/providers/openai-codex-responses.ts +47 -4
  47. package/packages/pi-ai/src/providers/openai-responses.fast-mode.test.ts +73 -0
  48. package/packages/pi-ai/src/providers/openai-responses.ts +26 -3
  49. package/packages/pi-ai/src/providers/simple-options.ts +2 -0
  50. package/packages/pi-ai/src/types.ts +5 -0
  51. package/packages/pi-coding-agent/dist/core/keybindings.d.ts +1 -1
  52. package/packages/pi-coding-agent/dist/core/keybindings.d.ts.map +1 -1
  53. package/packages/pi-coding-agent/dist/core/keybindings.js +2 -0
  54. package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
  55. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  56. package/packages/pi-coding-agent/dist/core/sdk.js +4 -2
  57. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  58. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -0
  59. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  60. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.d.ts +2 -0
  61. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.d.ts.map +1 -0
  62. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.js +35 -0
  63. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.js.map +1 -0
  64. package/packages/pi-coding-agent/dist/core/settings-manager.js +12 -0
  65. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  66. package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
  67. package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
  68. package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
  69. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  70. package/packages/pi-coding-agent/dist/core/system-prompt.js +6 -1
  71. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  72. package/packages/pi-coding-agent/dist/core/tool-priority.d.ts +4 -0
  73. package/packages/pi-coding-agent/dist/core/tool-priority.d.ts.map +1 -0
  74. package/packages/pi-coding-agent/dist/core/tool-priority.js +18 -0
  75. package/packages/pi-coding-agent/dist/core/tool-priority.js.map +1 -0
  76. package/packages/pi-coding-agent/dist/core/tool-priority.test.d.ts +2 -0
  77. package/packages/pi-coding-agent/dist/core/tool-priority.test.d.ts.map +1 -0
  78. package/packages/pi-coding-agent/dist/core/tool-priority.test.js +27 -0
  79. package/packages/pi-coding-agent/dist/core/tool-priority.test.js.map +1 -0
  80. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.d.ts +2 -0
  81. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.d.ts.map +1 -0
  82. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js +26 -0
  83. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js.map +1 -0
  84. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.d.ts +45 -0
  85. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.d.ts.map +1 -0
  86. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.js +314 -0
  87. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.js.map +1 -0
  88. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.d.ts +2 -0
  89. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.d.ts.map +1 -0
  90. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.js +122 -0
  91. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.js.map +1 -0
  92. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts +2 -0
  93. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  94. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +7 -0
  95. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  96. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +4 -0
  97. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  98. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +26 -2
  99. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  100. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +6 -0
  101. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  102. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +18 -4
  103. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  104. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts +13 -0
  105. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts.map +1 -0
  106. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js +49 -0
  107. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js.map +1 -0
  108. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.d.ts +2 -0
  109. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.d.ts.map +1 -0
  110. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js +197 -0
  111. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js.map +1 -0
  112. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  113. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +97 -0
  114. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  115. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  116. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +7 -0
  117. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +3 -0
  119. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  120. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  121. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
  122. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  123. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +35 -0
  124. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  125. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
  126. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +41 -0
  127. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  128. package/packages/pi-coding-agent/package.json +1 -1
  129. package/packages/pi-coding-agent/src/core/keybindings.ts +4 -1
  130. package/packages/pi-coding-agent/src/core/sdk.ts +4 -2
  131. package/packages/pi-coding-agent/src/core/settings-manager.fast-mode.test.ts +46 -0
  132. package/packages/pi-coding-agent/src/core/settings-manager.ts +18 -0
  133. package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
  134. package/packages/pi-coding-agent/src/core/system-prompt.ts +6 -1
  135. package/packages/pi-coding-agent/src/core/tool-priority.test.ts +30 -0
  136. package/packages/pi-coding-agent/src/core/tool-priority.ts +17 -0
  137. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-summary-line.test.ts +31 -0
  138. package/packages/pi-coding-agent/src/modes/interactive/components/btw-overlay.test.ts +172 -0
  139. package/packages/pi-coding-agent/src/modes/interactive/components/btw-overlay.ts +402 -0
  140. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +8 -0
  141. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +32 -2
  142. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +1154 -1136
  143. package/packages/pi-coding-agent/src/modes/interactive/components/tool-summary-line.ts +64 -0
  144. package/packages/pi-coding-agent/src/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.ts +228 -0
  145. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +494 -398
  146. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +7 -0
  147. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +3 -0
  148. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +38 -0
  149. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +60 -1
  150. package/pkg/package.json +1 -1
  151. package/src/resources/extensions/browser-tools/tools/codegen.ts +5 -5
  152. package/src/resources/extensions/browser-tools/tools/navigation.ts +118 -196
  153. package/src/resources/extensions/browser-tools/tools/network-mock.ts +114 -205
  154. package/src/resources/extensions/browser-tools/tools/pages.ts +183 -237
  155. package/src/resources/extensions/browser-tools/tools/refs.ts +193 -507
  156. package/src/resources/extensions/browser-tools/tools/session.ts +182 -321
  157. package/src/resources/extensions/browser-tools/tools/state-persistence.ts +94 -172
  158. package/src/resources/extensions/browser-tools/utils.ts +1 -1
  159. package/src/resources/extensions/slash-commands/extension-manifest.json +2 -2
  160. package/src/resources/extensions/slash-commands/fast.ts +89 -0
  161. package/src/resources/extensions/slash-commands/index.ts +2 -0
  162. package/src/resources/extensions/slash-commands/plan.ts +42 -12
  163. package/src/resources/extensions/subagent/background-job-manager.ts +28 -0
  164. package/src/resources/extensions/subagent/in-process-runner.ts +534 -0
  165. package/src/resources/extensions/subagent/index.ts +489 -799
  166. package/src/resources/extensions/subagent/legacy-runner.ts +607 -0
  167. package/src/resources/extensions/voice/index.ts +308 -238
  168. package/src/resources/extensions/voice/push-to-talk.ts +42 -0
  169. package/src/resources/extensions/voice/tests/push-to-talk.test.ts +109 -0
@@ -0,0 +1,607 @@
1
+ import { spawn, execFileSync, type ChildProcess } from "node:child_process";
2
+ import * as fs from "node:fs";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import type { AgentToolResult } from "@gsd/pi-agent-core";
6
+ import type { ImageContent, Message } from "@gsd/pi-ai";
7
+ import { getAgentDir } from "@gsd/pi-coding-agent";
8
+ import type { AgentConfig } from "./agents.js";
9
+ import { buildSubagentProcessArgs, getBundledExtensionPathsFromEnv } from "./launch-helpers.js";
10
+ import { handleSubagentPermissionRequest, isSubagentPermissionRequest } from "./approval-proxy.js";
11
+ import { resolveConfiguredSubagentModel } from "./configured-model.js";
12
+ import { normalizeSubagentModel, resolveSubagentModel } from "./model-resolution.js";
13
+ import { loadEffectivePreferences } from "../shared/preferences.js";
14
+
15
+ const liveSubagentProcesses = new Set<ChildProcess>();
16
+
17
+ export interface LegacyUsageStats {
18
+ input: number;
19
+ output: number;
20
+ cacheRead: number;
21
+ cacheWrite: number;
22
+ cost: number;
23
+ contextTokens: number;
24
+ turns: number;
25
+ }
26
+
27
+ export interface LegacySingleResult {
28
+ agent: string;
29
+ agentSource: "bundled" | "user" | "project" | "unknown";
30
+ task: string;
31
+ exitCode: number;
32
+ messages: Message[];
33
+ stderr: string;
34
+ usage: LegacyUsageStats;
35
+ model?: string;
36
+ stopReason?: string;
37
+ errorMessage?: string;
38
+ step?: number;
39
+ backgroundJobId?: string;
40
+ sessionFile?: string;
41
+ parentSessionFile?: string;
42
+ }
43
+
44
+ type BackgroundResultPayload = {
45
+ summary: string;
46
+ stderr: string;
47
+ exitCode: number;
48
+ model?: string;
49
+ sessionFile?: string;
50
+ parentSessionFile?: string;
51
+ };
52
+
53
+ export interface ForegroundSingleRunControl {
54
+ agentName: string;
55
+ task: string;
56
+ cwd: string;
57
+ parentSessionFile?: string;
58
+ abortController: AbortController;
59
+ resultPromise: Promise<BackgroundResultPayload>;
60
+ adoptToBackground: (jobId: string) => boolean;
61
+ sendPrompt?: (text: string, images?: ImageContent[]) => Promise<void>;
62
+ sendSteer?: (text: string, images?: ImageContent[]) => Promise<void>;
63
+ sendFollowUp?: (text: string, images?: ImageContent[]) => Promise<void>;
64
+ isBusy?: () => boolean;
65
+ }
66
+
67
+ export interface ForegroundSingleRunHooks {
68
+ onStart?: (control: ForegroundSingleRunControl) => void;
69
+ onFinish?: () => void;
70
+ }
71
+
72
+ export type LegacyOnUpdateCallback<TDetails> = (partial: AgentToolResult<TDetails>) => void;
73
+
74
+ export function readBudgetSubagentModelFromSettings(): string | undefined {
75
+ try {
76
+ const settingsPath = path.join(getAgentDir(), "settings.json");
77
+ if (!fs.existsSync(settingsPath)) return undefined;
78
+ const raw = fs.readFileSync(settingsPath, "utf-8");
79
+ const parsed = JSON.parse(raw) as { budgetSubagentModel?: unknown };
80
+ return typeof parsed.budgetSubagentModel === "string"
81
+ ? normalizeSubagentModel(parsed.budgetSubagentModel)
82
+ : undefined;
83
+ } catch {
84
+ return undefined;
85
+ }
86
+ }
87
+
88
+ export async function stopLegacySubagents(): Promise<void> {
89
+ const active = Array.from(liveSubagentProcesses);
90
+ if (active.length === 0) return;
91
+
92
+ for (const proc of active) {
93
+ try {
94
+ proc.kill("SIGTERM");
95
+ } catch {
96
+ /* ignore */
97
+ }
98
+ }
99
+
100
+ await Promise.all(
101
+ active.map(
102
+ (proc) =>
103
+ new Promise<void>((resolve) => {
104
+ const done = () => resolve();
105
+ const timer = setTimeout(done, 500);
106
+ proc.once("exit", () => {
107
+ clearTimeout(timer);
108
+ resolve();
109
+ });
110
+ }),
111
+ ),
112
+ );
113
+
114
+ for (const proc of active) {
115
+ if (proc.exitCode === null) {
116
+ try {
117
+ proc.kill("SIGKILL");
118
+ } catch {
119
+ /* ignore */
120
+ }
121
+ }
122
+ }
123
+ }
124
+
125
+ function listSessionFiles(sessionDir: string): string[] {
126
+ if (!fs.existsSync(sessionDir)) return [];
127
+ try {
128
+ return fs
129
+ .readdirSync(sessionDir)
130
+ .filter((name) => name.endsWith(".jsonl"))
131
+ .map((name) => path.join(sessionDir, name));
132
+ } catch {
133
+ return [];
134
+ }
135
+ }
136
+
137
+ function detectNewSubagentSessionFile(sessionDir: string, before: Set<string>, startedAt: number): string | undefined {
138
+ const after = listSessionFiles(sessionDir);
139
+ const created = after.filter((file) => !before.has(file));
140
+ const candidates = created.length > 0 ? created : after;
141
+ const ranked = candidates
142
+ .map((file) => {
143
+ let mtime = 0;
144
+ try {
145
+ mtime = fs.statSync(file).mtimeMs;
146
+ } catch {
147
+ mtime = 0;
148
+ }
149
+ return { file, mtime };
150
+ })
151
+ .filter((entry) => entry.mtime >= startedAt - 5000)
152
+ .sort((a, b) => b.mtime - a.mtime);
153
+ return ranked[0]?.file;
154
+ }
155
+
156
+ function resolveSubagentCliPath(defaultCwd: string): string | null {
157
+ const candidates = [process.env.GSD_BIN_PATH, process.env.LSD_BIN_PATH, process.argv[1]]
158
+ .map((value) => value?.trim())
159
+ .filter((value): value is string => Boolean(value && value !== "undefined"));
160
+
161
+ for (const candidate of candidates) {
162
+ if (path.isAbsolute(candidate) && fs.existsSync(candidate)) return candidate;
163
+ }
164
+
165
+ const cwdCandidates = [path.join(defaultCwd, "dist", "loader.js"), path.join(defaultCwd, "scripts", "dev-cli.js")];
166
+ for (const candidate of cwdCandidates) {
167
+ if (fs.existsSync(candidate)) return candidate;
168
+ }
169
+
170
+ for (const binName of ["lsd", "gsd"]) {
171
+ try {
172
+ const resolved = execFileSync("which", [binName], { encoding: "utf-8" }).trim();
173
+ if (resolved) return resolved;
174
+ } catch {
175
+ /* ignore */
176
+ }
177
+ }
178
+
179
+ return null;
180
+ }
181
+
182
+ function processSubagentEventLine(
183
+ line: string,
184
+ currentResult: LegacySingleResult,
185
+ emitUpdate: () => void,
186
+ proc: ChildProcess | undefined,
187
+ onSessionInfo?: (info: { sessionFile?: string; parentSessionFile?: string }) => void,
188
+ onEventType?: (eventType: string) => void,
189
+ onParsedEvent?: (event: any) => void,
190
+ ): boolean {
191
+ if (!line.trim()) return false;
192
+ let event: any;
193
+ try {
194
+ event = JSON.parse(line);
195
+ } catch {
196
+ return false;
197
+ }
198
+
199
+ const eventType = typeof event.type === "string" ? event.type : "unknown";
200
+ onEventType?.(eventType);
201
+ onParsedEvent?.(event);
202
+
203
+ if (event.type === "subagent_session_info") {
204
+ let changed = false;
205
+ if (typeof event.sessionFile === "string" && event.sessionFile) {
206
+ if (currentResult.sessionFile !== event.sessionFile) changed = true;
207
+ currentResult.sessionFile = event.sessionFile;
208
+ }
209
+ if (typeof event.parentSessionFile === "string" && event.parentSessionFile) {
210
+ if (currentResult.parentSessionFile !== event.parentSessionFile) changed = true;
211
+ currentResult.parentSessionFile = event.parentSessionFile;
212
+ }
213
+ if (changed) {
214
+ onSessionInfo?.({
215
+ sessionFile: currentResult.sessionFile,
216
+ parentSessionFile: currentResult.parentSessionFile,
217
+ });
218
+ }
219
+ return false;
220
+ }
221
+
222
+ if (proc && isSubagentPermissionRequest(event)) {
223
+ void handleSubagentPermissionRequest(event, proc);
224
+ return false;
225
+ }
226
+
227
+ if ((event.type === "message_end" || event.type === "turn_end") && event.message) {
228
+ const msg = event.message as Message;
229
+ currentResult.messages.push(msg);
230
+
231
+ if (msg.role === "assistant") {
232
+ currentResult.usage.turns++;
233
+ const usage = msg.usage;
234
+ if (usage) {
235
+ currentResult.usage.input += usage.input || 0;
236
+ currentResult.usage.output += usage.output || 0;
237
+ currentResult.usage.cacheRead += usage.cacheRead || 0;
238
+ currentResult.usage.cacheWrite += usage.cacheWrite || 0;
239
+ currentResult.usage.cost += usage.cost?.total || 0;
240
+ currentResult.usage.contextTokens = usage.totalTokens || 0;
241
+ }
242
+ if (msg.model && (!currentResult.model || msg.model.includes("/"))) currentResult.model = msg.model;
243
+ if (msg.stopReason) currentResult.stopReason = msg.stopReason;
244
+ if (msg.errorMessage) currentResult.errorMessage = msg.errorMessage;
245
+ }
246
+ emitUpdate();
247
+ }
248
+
249
+ if (event.type === "tool_result_end" && event.message) {
250
+ currentResult.messages.push(event.message as Message);
251
+ emitUpdate();
252
+ }
253
+
254
+ return event.type === "agent_end";
255
+ }
256
+
257
+ function writePromptToTempFile(agentName: string, prompt: string): { dir: string; filePath: string } {
258
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
259
+ const safeName = agentName.replace(/[^\w.-]+/g, "_");
260
+ const filePath = path.join(tmpDir, `prompt-${safeName}.md`);
261
+ fs.writeFileSync(filePath, prompt, { encoding: "utf-8", mode: 0o600 });
262
+ return { dir: tmpDir, filePath };
263
+ }
264
+
265
+ function getFinalOutput(messages: Message[]): string {
266
+ for (let i = messages.length - 1; i >= 0; i--) {
267
+ const msg = messages[i];
268
+ if (msg.role === "assistant") {
269
+ for (const part of msg.content) {
270
+ if (part.type === "text") return part.text;
271
+ }
272
+ }
273
+ }
274
+ return "";
275
+ }
276
+
277
+ export async function runLegacySingleAgent<TDetails>(
278
+ defaultCwd: string,
279
+ agents: AgentConfig[],
280
+ agentName: string,
281
+ task: string,
282
+ cwd: string | undefined,
283
+ step: number | undefined,
284
+ modelOverride: string | undefined,
285
+ parentModel: { provider: string; id: string } | undefined,
286
+ signal: AbortSignal | undefined,
287
+ onUpdate: LegacyOnUpdateCallback<TDetails> | undefined,
288
+ makeDetails: (results: LegacySingleResult[]) => TDetails,
289
+ parentSessionFile: string | undefined,
290
+ attachableSession: boolean,
291
+ onSessionInfo?: (info: { sessionFile?: string; parentSessionFile?: string }) => void,
292
+ onSubagentEvent?: (event: any, currentResult: LegacySingleResult) => void,
293
+ foregroundHooks?: ForegroundSingleRunHooks,
294
+ ): Promise<LegacySingleResult> {
295
+ const agent = agents.find((a) => a.name === agentName);
296
+
297
+ if (!agent) {
298
+ const available = agents.map((a) => `"${a.name}"`).join(", ") || "none";
299
+ return {
300
+ agent: agentName,
301
+ agentSource: "unknown",
302
+ task,
303
+ exitCode: 1,
304
+ messages: [],
305
+ stderr: `Unknown agent: "${agentName}". Available agents: ${available}.`,
306
+ usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
307
+ step,
308
+ };
309
+ }
310
+
311
+ let tmpPromptDir: string | null = null;
312
+ let tmpPromptPath: string | null = null;
313
+
314
+ const preferences = loadEffectivePreferences()?.preferences;
315
+ const settingsBudgetModel = readBudgetSubagentModelFromSettings();
316
+ const resolvedModel = resolveConfiguredSubagentModel(agent, preferences, settingsBudgetModel);
317
+ const inferredModel = resolveSubagentModel(
318
+ { name: agent.name, model: resolvedModel },
319
+ { overrideModel: modelOverride, parentModel },
320
+ );
321
+
322
+ const currentResult: LegacySingleResult = {
323
+ agent: agentName,
324
+ agentSource: agent.source,
325
+ task,
326
+ exitCode: 0,
327
+ messages: [],
328
+ stderr: "",
329
+ usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
330
+ model: inferredModel,
331
+ step,
332
+ parentSessionFile,
333
+ };
334
+
335
+ const emitUpdate = () => {
336
+ if (onUpdate) {
337
+ onUpdate({
338
+ content: [{ type: "text", text: getFinalOutput(currentResult.messages) || "(running...)" }],
339
+ details: makeDetails([currentResult]),
340
+ });
341
+ }
342
+ };
343
+
344
+ let wasAborted = false;
345
+ let deferTempPromptCleanup = false;
346
+ let tempPromptCleanupDone = false;
347
+
348
+ const cleanupTempPromptFiles = () => {
349
+ if (tempPromptCleanupDone) return;
350
+ tempPromptCleanupDone = true;
351
+ if (tmpPromptPath)
352
+ try {
353
+ fs.unlinkSync(tmpPromptPath);
354
+ } catch {
355
+ /* ignore */
356
+ }
357
+ if (tmpPromptDir)
358
+ try {
359
+ fs.rmdirSync(tmpPromptDir);
360
+ } catch {
361
+ /* ignore */
362
+ }
363
+ };
364
+
365
+ try {
366
+ if (agent.systemPrompt.trim()) {
367
+ const tmp = writePromptToTempFile(agent.name, agent.systemPrompt);
368
+ tmpPromptDir = tmp.dir;
369
+ tmpPromptPath = tmp.filePath;
370
+ }
371
+ const effectiveCwd = cwd ?? defaultCwd;
372
+ const subagentSessionDir = parentSessionFile ? path.dirname(parentSessionFile) : undefined;
373
+ const sessionFilesBefore = attachableSession && subagentSessionDir
374
+ ? new Set(listSessionFiles(subagentSessionDir))
375
+ : undefined;
376
+ const launchStartedAt = Date.now();
377
+
378
+ const args = buildSubagentProcessArgs(agent, task, tmpPromptPath, inferredModel, {
379
+ noSession: !attachableSession,
380
+ parentSessionFile: parentSessionFile,
381
+ mode: attachableSession ? "rpc" : "json",
382
+ });
383
+
384
+ const exitCode = await new Promise<number>((resolve) => {
385
+ const bundledPaths = getBundledExtensionPathsFromEnv();
386
+ const extensionArgs = bundledPaths.flatMap((p) => ["--extension", p]);
387
+ const cliPath = resolveSubagentCliPath(effectiveCwd);
388
+ if (!cliPath) {
389
+ currentResult.stderr += "Unable to resolve LSD/GSD CLI path for subagent launch.";
390
+ resolve(1);
391
+ return;
392
+ }
393
+ const proc = spawn(
394
+ process.execPath,
395
+ [cliPath, ...extensionArgs, ...args],
396
+ { cwd: effectiveCwd, shell: false, stdio: ["pipe", "pipe", "pipe"] },
397
+ );
398
+ liveSubagentProcesses.add(proc);
399
+ let buffer = "";
400
+ let completionSeen = false;
401
+ let resolved = false;
402
+ let foregroundReleased = false;
403
+ let isBusy = false;
404
+ let commandSeq = 0;
405
+ const pendingCommandResponses = new Map<string, { resolve: (data: any) => void; reject: (error: Error) => void }>();
406
+ const procAbortController = new AbortController();
407
+ let resolveBackgroundResult: ((value: BackgroundResultPayload) => void) | undefined;
408
+ let rejectBackgroundResult: ((reason?: unknown) => void) | undefined;
409
+ const backgroundResultPromise = new Promise<BackgroundResultPayload>((resolveBg, rejectBg) => {
410
+ resolveBackgroundResult = resolveBg;
411
+ rejectBackgroundResult = rejectBg;
412
+ });
413
+
414
+ const sendRpcCommand = async (command: Record<string, unknown>): Promise<any> => {
415
+ const id = `sa_cmd_${++commandSeq}`;
416
+ if (!proc.stdin) throw new Error("Subagent RPC stdin is not available.");
417
+ return new Promise((resolveCmd, rejectCmd) => {
418
+ pendingCommandResponses.set(id, { resolve: resolveCmd, reject: rejectCmd });
419
+ proc.stdin!.write(JSON.stringify({ id, ...command }) + "\n");
420
+ });
421
+ };
422
+
423
+ const finishForeground = (code: number) => {
424
+ if (resolved) return;
425
+ resolved = true;
426
+ resolve(code);
427
+ };
428
+
429
+ const adoptToBackground = (jobId: string): boolean => {
430
+ if (resolved || foregroundReleased) return false;
431
+ foregroundReleased = true;
432
+ deferTempPromptCleanup = true;
433
+ currentResult.backgroundJobId = jobId;
434
+ finishForeground(0);
435
+ return true;
436
+ };
437
+
438
+ backgroundResultPromise.finally(() => {
439
+ if (deferTempPromptCleanup) cleanupTempPromptFiles();
440
+ });
441
+
442
+ foregroundHooks?.onStart?.({
443
+ agentName,
444
+ task,
445
+ cwd: cwd ?? defaultCwd,
446
+ parentSessionFile,
447
+ abortController: procAbortController,
448
+ resultPromise: backgroundResultPromise,
449
+ adoptToBackground,
450
+ sendPrompt: attachableSession
451
+ ? async (text: string, images?: ImageContent[]) => {
452
+ await sendRpcCommand({ type: "prompt", message: text, images });
453
+ }
454
+ : undefined,
455
+ sendSteer: attachableSession
456
+ ? async (text: string, images?: ImageContent[]) => {
457
+ await sendRpcCommand({ type: "steer", message: text, images });
458
+ }
459
+ : undefined,
460
+ sendFollowUp: attachableSession
461
+ ? async (text: string, images?: ImageContent[]) => {
462
+ await sendRpcCommand({ type: "follow_up", message: text, images });
463
+ }
464
+ : undefined,
465
+ isBusy: attachableSession ? () => isBusy : undefined,
466
+ });
467
+
468
+ proc.stdout.on("data", (data) => {
469
+ buffer += data.toString();
470
+ const lines = buffer.split("\n");
471
+ buffer = lines.pop() || "";
472
+ for (const line of lines) {
473
+ const trimmed = line.trim();
474
+ if (!trimmed) continue;
475
+ if (attachableSession) {
476
+ try {
477
+ const parsed = JSON.parse(trimmed);
478
+ if (parsed?.type === "response" && typeof parsed.id === "string" && pendingCommandResponses.has(parsed.id)) {
479
+ const pending = pendingCommandResponses.get(parsed.id)!;
480
+ pendingCommandResponses.delete(parsed.id);
481
+ if (parsed.success === false) {
482
+ pending.reject(new Error(typeof parsed.error === "string" ? parsed.error : "Subagent RPC command failed."));
483
+ } else {
484
+ pending.resolve(parsed.data);
485
+ }
486
+ continue;
487
+ }
488
+ } catch {
489
+ // Fall through to generic event processing.
490
+ }
491
+ }
492
+
493
+ if (processSubagentEventLine(trimmed, currentResult, emitUpdate, proc, onSessionInfo, (eventType) => {
494
+ if (eventType === "agent_start") isBusy = true;
495
+ if (eventType === "agent_end") isBusy = false;
496
+ }, (event) => onSubagentEvent?.(event, currentResult))) {
497
+ completionSeen = true;
498
+ try {
499
+ proc.kill("SIGTERM");
500
+ } catch {
501
+ /* ignore */
502
+ }
503
+ }
504
+ }
505
+ });
506
+
507
+ proc.stderr.on("data", (data) => {
508
+ currentResult.stderr += data.toString();
509
+ });
510
+
511
+ proc.on("close", (code) => {
512
+ liveSubagentProcesses.delete(proc);
513
+ if (buffer.trim()) {
514
+ const completedOnFlush = processSubagentEventLine(buffer, currentResult, emitUpdate, proc, onSessionInfo, (eventType) => {
515
+ if (eventType === "agent_start") isBusy = true;
516
+ if (eventType === "agent_end") isBusy = false;
517
+ }, (event) => onSubagentEvent?.(event, currentResult));
518
+ completionSeen = completionSeen || completedOnFlush;
519
+ }
520
+ isBusy = false;
521
+ for (const pending of pendingCommandResponses.values()) {
522
+ pending.reject(new Error("Subagent process closed before command response."));
523
+ }
524
+ pendingCommandResponses.clear();
525
+
526
+ const finalExitCode = completionSeen && (code === null || code === 143 || code === 15) ? 0 : (code ?? 0);
527
+ currentResult.exitCode = finalExitCode;
528
+
529
+ if (attachableSession && sessionFilesBefore && subagentSessionDir && !currentResult.sessionFile) {
530
+ const detected = detectNewSubagentSessionFile(subagentSessionDir, sessionFilesBefore, launchStartedAt);
531
+ if (detected) currentResult.sessionFile = detected;
532
+ }
533
+
534
+ resolveBackgroundResult?.({
535
+ summary: getFinalOutput(currentResult.messages),
536
+ stderr: currentResult.stderr,
537
+ exitCode: finalExitCode,
538
+ model: currentResult.model,
539
+ sessionFile: currentResult.sessionFile,
540
+ parentSessionFile: currentResult.parentSessionFile,
541
+ });
542
+ foregroundHooks?.onFinish?.();
543
+ finishForeground(finalExitCode);
544
+ });
545
+
546
+ proc.on("error", (error) => {
547
+ liveSubagentProcesses.delete(proc);
548
+ isBusy = false;
549
+ for (const pending of pendingCommandResponses.values()) {
550
+ pending.reject(error instanceof Error ? error : new Error(String(error)));
551
+ }
552
+ pendingCommandResponses.clear();
553
+ rejectBackgroundResult?.(error);
554
+ foregroundHooks?.onFinish?.();
555
+ finishForeground(1);
556
+ });
557
+
558
+ if (attachableSession) {
559
+ void sendRpcCommand({ type: "prompt", message: task }).catch((error) => {
560
+ currentResult.stderr += error instanceof Error ? error.message : String(error);
561
+ try {
562
+ proc.kill("SIGTERM");
563
+ } catch {
564
+ /* ignore */
565
+ }
566
+ });
567
+ }
568
+
569
+ const killProc = () => {
570
+ wasAborted = true;
571
+ procAbortController.abort();
572
+ proc.kill("SIGTERM");
573
+ setTimeout(() => {
574
+ if (!proc.killed) proc.kill("SIGKILL");
575
+ }, 5000);
576
+ };
577
+
578
+ if (signal) {
579
+ if (signal.aborted) killProc();
580
+ else signal.addEventListener("abort", killProc, { once: true });
581
+ }
582
+
583
+ if (procAbortController.signal.aborted) {
584
+ killProc();
585
+ } else {
586
+ procAbortController.signal.addEventListener("abort", () => {
587
+ proc.kill("SIGTERM");
588
+ setTimeout(() => {
589
+ if (!proc.killed) proc.kill("SIGKILL");
590
+ }, 5000);
591
+ }, { once: true });
592
+ }
593
+ });
594
+
595
+ currentResult.exitCode = exitCode;
596
+ if (attachableSession && sessionFilesBefore && subagentSessionDir) {
597
+ const detected = detectNewSubagentSessionFile(subagentSessionDir, sessionFilesBefore, launchStartedAt);
598
+ if (detected) {
599
+ currentResult.sessionFile = detected;
600
+ }
601
+ }
602
+ if (wasAborted) throw new Error("Subagent was aborted");
603
+ return currentResult;
604
+ } finally {
605
+ if (!deferTempPromptCleanup) cleanupTempPromptFiles();
606
+ }
607
+ }