lsd-pi 1.3.2 → 1.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/dist/cli.js +2 -1
  2. package/dist/lsd-settings-manager.d.ts +2 -0
  3. package/dist/lsd-settings-manager.js +5 -0
  4. package/dist/resource-loader.js +33 -3
  5. package/dist/resources/extensions/browser-tools/tools/codegen.js +5 -5
  6. package/dist/resources/extensions/browser-tools/tools/navigation.js +107 -178
  7. package/dist/resources/extensions/browser-tools/tools/network-mock.js +112 -167
  8. package/dist/resources/extensions/browser-tools/tools/pages.js +182 -234
  9. package/dist/resources/extensions/browser-tools/tools/refs.js +202 -461
  10. package/dist/resources/extensions/browser-tools/tools/session.js +176 -323
  11. package/dist/resources/extensions/browser-tools/tools/state-persistence.js +91 -154
  12. package/dist/resources/extensions/browser-tools/utils.js +1 -1
  13. package/dist/resources/extensions/cache-timer/index.js +3 -2
  14. package/dist/resources/extensions/slash-commands/extension-manifest.json +2 -2
  15. package/dist/resources/extensions/slash-commands/fast.js +73 -0
  16. package/dist/resources/extensions/slash-commands/index.js +2 -0
  17. package/dist/resources/extensions/slash-commands/plan.js +37 -12
  18. package/dist/resources/extensions/subagent/background-job-manager.js +13 -0
  19. package/dist/resources/extensions/subagent/in-process-runner.js +387 -0
  20. package/dist/resources/extensions/subagent/index.js +278 -626
  21. package/dist/resources/extensions/subagent/legacy-runner.js +503 -0
  22. package/dist/resources/extensions/voice/index.js +96 -36
  23. package/dist/resources/extensions/voice/push-to-talk.js +26 -0
  24. package/dist/welcome-screen.js +2 -2
  25. package/package.json +1 -1
  26. package/packages/pi-agent-core/dist/agent.d.ts +19 -0
  27. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  28. package/packages/pi-agent-core/dist/agent.js +16 -0
  29. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  30. package/packages/pi-agent-core/src/agent.ts +32 -2
  31. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts +34 -1
  32. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
  33. package/packages/pi-ai/dist/providers/openai-codex-responses.js +32 -4
  34. package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  35. package/packages/pi-ai/dist/providers/openai-codex-responses.test.js +127 -16
  36. package/packages/pi-ai/dist/providers/openai-codex-responses.test.js.map +1 -1
  37. package/packages/pi-ai/dist/providers/openai-responses.d.ts +8 -1
  38. package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  39. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.d.ts +2 -0
  40. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.d.ts.map +1 -0
  41. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.js +67 -0
  42. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.js.map +1 -0
  43. package/packages/pi-ai/dist/providers/openai-responses.js +21 -3
  44. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  45. package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  46. package/packages/pi-ai/dist/providers/simple-options.js +2 -0
  47. package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
  48. package/packages/pi-ai/dist/types.d.ts +5 -0
  49. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  50. package/packages/pi-ai/dist/types.js.map +1 -1
  51. package/packages/pi-ai/src/providers/openai-codex-responses.test.ts +143 -20
  52. package/packages/pi-ai/src/providers/openai-codex-responses.ts +47 -4
  53. package/packages/pi-ai/src/providers/openai-responses.fast-mode.test.ts +73 -0
  54. package/packages/pi-ai/src/providers/openai-responses.ts +26 -3
  55. package/packages/pi-ai/src/providers/simple-options.ts +2 -0
  56. package/packages/pi-ai/src/types.ts +5 -0
  57. package/packages/pi-coding-agent/dist/core/keybindings.d.ts +1 -1
  58. package/packages/pi-coding-agent/dist/core/keybindings.d.ts.map +1 -1
  59. package/packages/pi-coding-agent/dist/core/keybindings.js +2 -0
  60. package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
  61. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  62. package/packages/pi-coding-agent/dist/core/sdk.js +4 -2
  63. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  64. package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.d.ts +2 -0
  65. package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.d.ts.map +1 -0
  66. package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.js +35 -0
  67. package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.js.map +1 -0
  68. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +12 -0
  69. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  70. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.d.ts +2 -0
  71. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.d.ts.map +1 -0
  72. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.js +35 -0
  73. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.js.map +1 -0
  74. package/packages/pi-coding-agent/dist/core/settings-manager.js +24 -0
  75. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  76. package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
  77. package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
  78. package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
  79. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  80. package/packages/pi-coding-agent/dist/core/system-prompt.js +6 -1
  81. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  82. package/packages/pi-coding-agent/dist/core/tool-priority.d.ts +4 -0
  83. package/packages/pi-coding-agent/dist/core/tool-priority.d.ts.map +1 -0
  84. package/packages/pi-coding-agent/dist/core/tool-priority.js +18 -0
  85. package/packages/pi-coding-agent/dist/core/tool-priority.js.map +1 -0
  86. package/packages/pi-coding-agent/dist/core/tool-priority.test.d.ts +2 -0
  87. package/packages/pi-coding-agent/dist/core/tool-priority.test.d.ts.map +1 -0
  88. package/packages/pi-coding-agent/dist/core/tool-priority.test.js +27 -0
  89. package/packages/pi-coding-agent/dist/core/tool-priority.test.js.map +1 -0
  90. package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts +5 -0
  91. package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts.map +1 -1
  92. package/packages/pi-coding-agent/dist/core/tools/edit-diff.js +21 -0
  93. package/packages/pi-coding-agent/dist/core/tools/edit-diff.js.map +1 -1
  94. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +16 -1
  95. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -1
  96. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.d.ts +2 -0
  97. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.d.ts.map +1 -0
  98. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js +34 -0
  99. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js.map +1 -0
  100. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.d.ts +45 -0
  101. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.d.ts.map +1 -0
  102. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.js +314 -0
  103. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.js.map +1 -0
  104. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.d.ts +2 -0
  105. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.d.ts.map +1 -0
  106. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.js +122 -0
  107. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.js.map +1 -0
  108. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts +7 -5
  109. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts.map +1 -1
  110. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js +86 -28
  111. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js.map +1 -1
  112. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts +4 -0
  113. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  114. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +23 -10
  115. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  116. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +8 -0
  117. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  118. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +52 -6
  119. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  120. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +19 -0
  121. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  122. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +127 -14
  123. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  124. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts +14 -0
  125. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts.map +1 -0
  126. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js +93 -0
  127. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js.map +1 -0
  128. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.d.ts +2 -0
  129. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.d.ts.map +1 -0
  130. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js +328 -0
  131. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js.map +1 -0
  132. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  133. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +123 -0
  134. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  135. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +1 -1
  136. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -1
  137. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  138. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +7 -0
  139. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  140. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +4 -0
  141. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  142. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  143. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +9 -1
  144. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  145. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +103 -23
  146. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  147. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
  148. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +41 -0
  149. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  150. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +4 -4
  151. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  152. package/packages/pi-coding-agent/package.json +1 -1
  153. package/packages/pi-coding-agent/src/core/keybindings.ts +4 -1
  154. package/packages/pi-coding-agent/src/core/sdk.ts +4 -2
  155. package/packages/pi-coding-agent/src/core/settings-manager.collapse-tool-calls.test.ts +46 -0
  156. package/packages/pi-coding-agent/src/core/settings-manager.fast-mode.test.ts +46 -0
  157. package/packages/pi-coding-agent/src/core/settings-manager.ts +36 -0
  158. package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
  159. package/packages/pi-coding-agent/src/core/system-prompt.ts +6 -1
  160. package/packages/pi-coding-agent/src/core/tool-priority.test.ts +30 -0
  161. package/packages/pi-coding-agent/src/core/tool-priority.ts +17 -0
  162. package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +20 -0
  163. package/packages/pi-coding-agent/src/core/tools/edit-diff.ts +26 -0
  164. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-summary-line.test.ts +41 -0
  165. package/packages/pi-coding-agent/src/modes/interactive/components/btw-overlay.test.ts +172 -0
  166. package/packages/pi-coding-agent/src/modes/interactive/components/btw-overlay.ts +402 -0
  167. package/packages/pi-coding-agent/src/modes/interactive/components/diff.ts +105 -28
  168. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +21 -6
  169. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +63 -6
  170. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +1262 -1138
  171. package/packages/pi-coding-agent/src/modes/interactive/components/tool-summary-line.ts +120 -0
  172. package/packages/pi-coding-agent/src/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.ts +396 -0
  173. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +530 -398
  174. package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +1 -1
  175. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +7 -0
  176. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +4 -0
  177. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +109 -23
  178. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +60 -1
  179. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +4 -4
  180. package/packages/pi-tui/dist/components/editor.js +3 -3
  181. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  182. package/packages/pi-tui/src/components/editor.ts +3 -3
  183. package/pkg/dist/modes/interactive/theme/themes.js +4 -4
  184. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  185. package/pkg/package.json +1 -1
  186. package/src/resources/extensions/browser-tools/tools/codegen.ts +5 -5
  187. package/src/resources/extensions/browser-tools/tools/navigation.ts +118 -196
  188. package/src/resources/extensions/browser-tools/tools/network-mock.ts +114 -205
  189. package/src/resources/extensions/browser-tools/tools/pages.ts +183 -237
  190. package/src/resources/extensions/browser-tools/tools/refs.ts +193 -507
  191. package/src/resources/extensions/browser-tools/tools/session.ts +182 -321
  192. package/src/resources/extensions/browser-tools/tools/state-persistence.ts +94 -172
  193. package/src/resources/extensions/browser-tools/utils.ts +1 -1
  194. package/src/resources/extensions/cache-timer/index.ts +3 -2
  195. package/src/resources/extensions/slash-commands/extension-manifest.json +2 -2
  196. package/src/resources/extensions/slash-commands/fast.ts +89 -0
  197. package/src/resources/extensions/slash-commands/index.ts +2 -0
  198. package/src/resources/extensions/slash-commands/plan.ts +42 -12
  199. package/src/resources/extensions/subagent/background-job-manager.ts +28 -0
  200. package/src/resources/extensions/subagent/in-process-runner.ts +534 -0
  201. package/src/resources/extensions/subagent/index.ts +489 -799
  202. package/src/resources/extensions/subagent/legacy-runner.ts +607 -0
  203. package/src/resources/extensions/voice/index.ts +308 -238
  204. package/src/resources/extensions/voice/push-to-talk.ts +42 -0
  205. 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
+ }