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
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
import { spawn, execFileSync } 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 { getAgentDir } from "@gsd/pi-coding-agent";
|
|
6
|
+
import { buildSubagentProcessArgs, getBundledExtensionPathsFromEnv } from "./launch-helpers.js";
|
|
7
|
+
import { handleSubagentPermissionRequest, isSubagentPermissionRequest } from "./approval-proxy.js";
|
|
8
|
+
import { resolveConfiguredSubagentModel } from "./configured-model.js";
|
|
9
|
+
import { normalizeSubagentModel, resolveSubagentModel } from "./model-resolution.js";
|
|
10
|
+
import { loadEffectivePreferences } from "../shared/preferences.js";
|
|
11
|
+
const liveSubagentProcesses = new Set();
|
|
12
|
+
export function readBudgetSubagentModelFromSettings() {
|
|
13
|
+
try {
|
|
14
|
+
const settingsPath = path.join(getAgentDir(), "settings.json");
|
|
15
|
+
if (!fs.existsSync(settingsPath))
|
|
16
|
+
return undefined;
|
|
17
|
+
const raw = fs.readFileSync(settingsPath, "utf-8");
|
|
18
|
+
const parsed = JSON.parse(raw);
|
|
19
|
+
return typeof parsed.budgetSubagentModel === "string"
|
|
20
|
+
? normalizeSubagentModel(parsed.budgetSubagentModel)
|
|
21
|
+
: undefined;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export async function stopLegacySubagents() {
|
|
28
|
+
const active = Array.from(liveSubagentProcesses);
|
|
29
|
+
if (active.length === 0)
|
|
30
|
+
return;
|
|
31
|
+
for (const proc of active) {
|
|
32
|
+
try {
|
|
33
|
+
proc.kill("SIGTERM");
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
/* ignore */
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
await Promise.all(active.map((proc) => new Promise((resolve) => {
|
|
40
|
+
const done = () => resolve();
|
|
41
|
+
const timer = setTimeout(done, 500);
|
|
42
|
+
proc.once("exit", () => {
|
|
43
|
+
clearTimeout(timer);
|
|
44
|
+
resolve();
|
|
45
|
+
});
|
|
46
|
+
})));
|
|
47
|
+
for (const proc of active) {
|
|
48
|
+
if (proc.exitCode === null) {
|
|
49
|
+
try {
|
|
50
|
+
proc.kill("SIGKILL");
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
/* ignore */
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function listSessionFiles(sessionDir) {
|
|
59
|
+
if (!fs.existsSync(sessionDir))
|
|
60
|
+
return [];
|
|
61
|
+
try {
|
|
62
|
+
return fs
|
|
63
|
+
.readdirSync(sessionDir)
|
|
64
|
+
.filter((name) => name.endsWith(".jsonl"))
|
|
65
|
+
.map((name) => path.join(sessionDir, name));
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function detectNewSubagentSessionFile(sessionDir, before, startedAt) {
|
|
72
|
+
const after = listSessionFiles(sessionDir);
|
|
73
|
+
const created = after.filter((file) => !before.has(file));
|
|
74
|
+
const candidates = created.length > 0 ? created : after;
|
|
75
|
+
const ranked = candidates
|
|
76
|
+
.map((file) => {
|
|
77
|
+
let mtime = 0;
|
|
78
|
+
try {
|
|
79
|
+
mtime = fs.statSync(file).mtimeMs;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
mtime = 0;
|
|
83
|
+
}
|
|
84
|
+
return { file, mtime };
|
|
85
|
+
})
|
|
86
|
+
.filter((entry) => entry.mtime >= startedAt - 5000)
|
|
87
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
88
|
+
return ranked[0]?.file;
|
|
89
|
+
}
|
|
90
|
+
function resolveSubagentCliPath(defaultCwd) {
|
|
91
|
+
const candidates = [process.env.GSD_BIN_PATH, process.env.LSD_BIN_PATH, process.argv[1]]
|
|
92
|
+
.map((value) => value?.trim())
|
|
93
|
+
.filter((value) => Boolean(value && value !== "undefined"));
|
|
94
|
+
for (const candidate of candidates) {
|
|
95
|
+
if (path.isAbsolute(candidate) && fs.existsSync(candidate))
|
|
96
|
+
return candidate;
|
|
97
|
+
}
|
|
98
|
+
const cwdCandidates = [path.join(defaultCwd, "dist", "loader.js"), path.join(defaultCwd, "scripts", "dev-cli.js")];
|
|
99
|
+
for (const candidate of cwdCandidates) {
|
|
100
|
+
if (fs.existsSync(candidate))
|
|
101
|
+
return candidate;
|
|
102
|
+
}
|
|
103
|
+
for (const binName of ["lsd", "gsd"]) {
|
|
104
|
+
try {
|
|
105
|
+
const resolved = execFileSync("which", [binName], { encoding: "utf-8" }).trim();
|
|
106
|
+
if (resolved)
|
|
107
|
+
return resolved;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
/* ignore */
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
function processSubagentEventLine(line, currentResult, emitUpdate, proc, onSessionInfo, onEventType, onParsedEvent) {
|
|
116
|
+
if (!line.trim())
|
|
117
|
+
return false;
|
|
118
|
+
let event;
|
|
119
|
+
try {
|
|
120
|
+
event = JSON.parse(line);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
const eventType = typeof event.type === "string" ? event.type : "unknown";
|
|
126
|
+
onEventType?.(eventType);
|
|
127
|
+
onParsedEvent?.(event);
|
|
128
|
+
if (event.type === "subagent_session_info") {
|
|
129
|
+
let changed = false;
|
|
130
|
+
if (typeof event.sessionFile === "string" && event.sessionFile) {
|
|
131
|
+
if (currentResult.sessionFile !== event.sessionFile)
|
|
132
|
+
changed = true;
|
|
133
|
+
currentResult.sessionFile = event.sessionFile;
|
|
134
|
+
}
|
|
135
|
+
if (typeof event.parentSessionFile === "string" && event.parentSessionFile) {
|
|
136
|
+
if (currentResult.parentSessionFile !== event.parentSessionFile)
|
|
137
|
+
changed = true;
|
|
138
|
+
currentResult.parentSessionFile = event.parentSessionFile;
|
|
139
|
+
}
|
|
140
|
+
if (changed) {
|
|
141
|
+
onSessionInfo?.({
|
|
142
|
+
sessionFile: currentResult.sessionFile,
|
|
143
|
+
parentSessionFile: currentResult.parentSessionFile,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
if (proc && isSubagentPermissionRequest(event)) {
|
|
149
|
+
void handleSubagentPermissionRequest(event, proc);
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
if ((event.type === "message_end" || event.type === "turn_end") && event.message) {
|
|
153
|
+
const msg = event.message;
|
|
154
|
+
currentResult.messages.push(msg);
|
|
155
|
+
if (msg.role === "assistant") {
|
|
156
|
+
currentResult.usage.turns++;
|
|
157
|
+
const usage = msg.usage;
|
|
158
|
+
if (usage) {
|
|
159
|
+
currentResult.usage.input += usage.input || 0;
|
|
160
|
+
currentResult.usage.output += usage.output || 0;
|
|
161
|
+
currentResult.usage.cacheRead += usage.cacheRead || 0;
|
|
162
|
+
currentResult.usage.cacheWrite += usage.cacheWrite || 0;
|
|
163
|
+
currentResult.usage.cost += usage.cost?.total || 0;
|
|
164
|
+
currentResult.usage.contextTokens = usage.totalTokens || 0;
|
|
165
|
+
}
|
|
166
|
+
if (msg.model && (!currentResult.model || msg.model.includes("/")))
|
|
167
|
+
currentResult.model = msg.model;
|
|
168
|
+
if (msg.stopReason)
|
|
169
|
+
currentResult.stopReason = msg.stopReason;
|
|
170
|
+
if (msg.errorMessage)
|
|
171
|
+
currentResult.errorMessage = msg.errorMessage;
|
|
172
|
+
}
|
|
173
|
+
emitUpdate();
|
|
174
|
+
}
|
|
175
|
+
if (event.type === "tool_result_end" && event.message) {
|
|
176
|
+
currentResult.messages.push(event.message);
|
|
177
|
+
emitUpdate();
|
|
178
|
+
}
|
|
179
|
+
return event.type === "agent_end";
|
|
180
|
+
}
|
|
181
|
+
function writePromptToTempFile(agentName, prompt) {
|
|
182
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
|
|
183
|
+
const safeName = agentName.replace(/[^\w.-]+/g, "_");
|
|
184
|
+
const filePath = path.join(tmpDir, `prompt-${safeName}.md`);
|
|
185
|
+
fs.writeFileSync(filePath, prompt, { encoding: "utf-8", mode: 0o600 });
|
|
186
|
+
return { dir: tmpDir, filePath };
|
|
187
|
+
}
|
|
188
|
+
function getFinalOutput(messages) {
|
|
189
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
190
|
+
const msg = messages[i];
|
|
191
|
+
if (msg.role === "assistant") {
|
|
192
|
+
for (const part of msg.content) {
|
|
193
|
+
if (part.type === "text")
|
|
194
|
+
return part.text;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return "";
|
|
199
|
+
}
|
|
200
|
+
export async function runLegacySingleAgent(defaultCwd, agents, agentName, task, cwd, step, modelOverride, parentModel, signal, onUpdate, makeDetails, parentSessionFile, attachableSession, onSessionInfo, onSubagentEvent, foregroundHooks) {
|
|
201
|
+
const agent = agents.find((a) => a.name === agentName);
|
|
202
|
+
if (!agent) {
|
|
203
|
+
const available = agents.map((a) => `"${a.name}"`).join(", ") || "none";
|
|
204
|
+
return {
|
|
205
|
+
agent: agentName,
|
|
206
|
+
agentSource: "unknown",
|
|
207
|
+
task,
|
|
208
|
+
exitCode: 1,
|
|
209
|
+
messages: [],
|
|
210
|
+
stderr: `Unknown agent: "${agentName}". Available agents: ${available}.`,
|
|
211
|
+
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
|
|
212
|
+
step,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
let tmpPromptDir = null;
|
|
216
|
+
let tmpPromptPath = null;
|
|
217
|
+
const preferences = loadEffectivePreferences()?.preferences;
|
|
218
|
+
const settingsBudgetModel = readBudgetSubagentModelFromSettings();
|
|
219
|
+
const resolvedModel = resolveConfiguredSubagentModel(agent, preferences, settingsBudgetModel);
|
|
220
|
+
const inferredModel = resolveSubagentModel({ name: agent.name, model: resolvedModel }, { overrideModel: modelOverride, parentModel });
|
|
221
|
+
const currentResult = {
|
|
222
|
+
agent: agentName,
|
|
223
|
+
agentSource: agent.source,
|
|
224
|
+
task,
|
|
225
|
+
exitCode: 0,
|
|
226
|
+
messages: [],
|
|
227
|
+
stderr: "",
|
|
228
|
+
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
|
|
229
|
+
model: inferredModel,
|
|
230
|
+
step,
|
|
231
|
+
parentSessionFile,
|
|
232
|
+
};
|
|
233
|
+
const emitUpdate = () => {
|
|
234
|
+
if (onUpdate) {
|
|
235
|
+
onUpdate({
|
|
236
|
+
content: [{ type: "text", text: getFinalOutput(currentResult.messages) || "(running...)" }],
|
|
237
|
+
details: makeDetails([currentResult]),
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
let wasAborted = false;
|
|
242
|
+
let deferTempPromptCleanup = false;
|
|
243
|
+
let tempPromptCleanupDone = false;
|
|
244
|
+
const cleanupTempPromptFiles = () => {
|
|
245
|
+
if (tempPromptCleanupDone)
|
|
246
|
+
return;
|
|
247
|
+
tempPromptCleanupDone = true;
|
|
248
|
+
if (tmpPromptPath)
|
|
249
|
+
try {
|
|
250
|
+
fs.unlinkSync(tmpPromptPath);
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
/* ignore */
|
|
254
|
+
}
|
|
255
|
+
if (tmpPromptDir)
|
|
256
|
+
try {
|
|
257
|
+
fs.rmdirSync(tmpPromptDir);
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
/* ignore */
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
try {
|
|
264
|
+
if (agent.systemPrompt.trim()) {
|
|
265
|
+
const tmp = writePromptToTempFile(agent.name, agent.systemPrompt);
|
|
266
|
+
tmpPromptDir = tmp.dir;
|
|
267
|
+
tmpPromptPath = tmp.filePath;
|
|
268
|
+
}
|
|
269
|
+
const effectiveCwd = cwd ?? defaultCwd;
|
|
270
|
+
const subagentSessionDir = parentSessionFile ? path.dirname(parentSessionFile) : undefined;
|
|
271
|
+
const sessionFilesBefore = attachableSession && subagentSessionDir
|
|
272
|
+
? new Set(listSessionFiles(subagentSessionDir))
|
|
273
|
+
: undefined;
|
|
274
|
+
const launchStartedAt = Date.now();
|
|
275
|
+
const args = buildSubagentProcessArgs(agent, task, tmpPromptPath, inferredModel, {
|
|
276
|
+
noSession: !attachableSession,
|
|
277
|
+
parentSessionFile: parentSessionFile,
|
|
278
|
+
mode: attachableSession ? "rpc" : "json",
|
|
279
|
+
});
|
|
280
|
+
const exitCode = await new Promise((resolve) => {
|
|
281
|
+
const bundledPaths = getBundledExtensionPathsFromEnv();
|
|
282
|
+
const extensionArgs = bundledPaths.flatMap((p) => ["--extension", p]);
|
|
283
|
+
const cliPath = resolveSubagentCliPath(effectiveCwd);
|
|
284
|
+
if (!cliPath) {
|
|
285
|
+
currentResult.stderr += "Unable to resolve LSD/GSD CLI path for subagent launch.";
|
|
286
|
+
resolve(1);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const proc = spawn(process.execPath, [cliPath, ...extensionArgs, ...args], { cwd: effectiveCwd, shell: false, stdio: ["pipe", "pipe", "pipe"] });
|
|
290
|
+
liveSubagentProcesses.add(proc);
|
|
291
|
+
let buffer = "";
|
|
292
|
+
let completionSeen = false;
|
|
293
|
+
let resolved = false;
|
|
294
|
+
let foregroundReleased = false;
|
|
295
|
+
let isBusy = false;
|
|
296
|
+
let commandSeq = 0;
|
|
297
|
+
const pendingCommandResponses = new Map();
|
|
298
|
+
const procAbortController = new AbortController();
|
|
299
|
+
let resolveBackgroundResult;
|
|
300
|
+
let rejectBackgroundResult;
|
|
301
|
+
const backgroundResultPromise = new Promise((resolveBg, rejectBg) => {
|
|
302
|
+
resolveBackgroundResult = resolveBg;
|
|
303
|
+
rejectBackgroundResult = rejectBg;
|
|
304
|
+
});
|
|
305
|
+
const sendRpcCommand = async (command) => {
|
|
306
|
+
const id = `sa_cmd_${++commandSeq}`;
|
|
307
|
+
if (!proc.stdin)
|
|
308
|
+
throw new Error("Subagent RPC stdin is not available.");
|
|
309
|
+
return new Promise((resolveCmd, rejectCmd) => {
|
|
310
|
+
pendingCommandResponses.set(id, { resolve: resolveCmd, reject: rejectCmd });
|
|
311
|
+
proc.stdin.write(JSON.stringify({ id, ...command }) + "\n");
|
|
312
|
+
});
|
|
313
|
+
};
|
|
314
|
+
const finishForeground = (code) => {
|
|
315
|
+
if (resolved)
|
|
316
|
+
return;
|
|
317
|
+
resolved = true;
|
|
318
|
+
resolve(code);
|
|
319
|
+
};
|
|
320
|
+
const adoptToBackground = (jobId) => {
|
|
321
|
+
if (resolved || foregroundReleased)
|
|
322
|
+
return false;
|
|
323
|
+
foregroundReleased = true;
|
|
324
|
+
deferTempPromptCleanup = true;
|
|
325
|
+
currentResult.backgroundJobId = jobId;
|
|
326
|
+
finishForeground(0);
|
|
327
|
+
return true;
|
|
328
|
+
};
|
|
329
|
+
backgroundResultPromise.finally(() => {
|
|
330
|
+
if (deferTempPromptCleanup)
|
|
331
|
+
cleanupTempPromptFiles();
|
|
332
|
+
});
|
|
333
|
+
foregroundHooks?.onStart?.({
|
|
334
|
+
agentName,
|
|
335
|
+
task,
|
|
336
|
+
cwd: cwd ?? defaultCwd,
|
|
337
|
+
parentSessionFile,
|
|
338
|
+
abortController: procAbortController,
|
|
339
|
+
resultPromise: backgroundResultPromise,
|
|
340
|
+
adoptToBackground,
|
|
341
|
+
sendPrompt: attachableSession
|
|
342
|
+
? async (text, images) => {
|
|
343
|
+
await sendRpcCommand({ type: "prompt", message: text, images });
|
|
344
|
+
}
|
|
345
|
+
: undefined,
|
|
346
|
+
sendSteer: attachableSession
|
|
347
|
+
? async (text, images) => {
|
|
348
|
+
await sendRpcCommand({ type: "steer", message: text, images });
|
|
349
|
+
}
|
|
350
|
+
: undefined,
|
|
351
|
+
sendFollowUp: attachableSession
|
|
352
|
+
? async (text, images) => {
|
|
353
|
+
await sendRpcCommand({ type: "follow_up", message: text, images });
|
|
354
|
+
}
|
|
355
|
+
: undefined,
|
|
356
|
+
isBusy: attachableSession ? () => isBusy : undefined,
|
|
357
|
+
});
|
|
358
|
+
proc.stdout.on("data", (data) => {
|
|
359
|
+
buffer += data.toString();
|
|
360
|
+
const lines = buffer.split("\n");
|
|
361
|
+
buffer = lines.pop() || "";
|
|
362
|
+
for (const line of lines) {
|
|
363
|
+
const trimmed = line.trim();
|
|
364
|
+
if (!trimmed)
|
|
365
|
+
continue;
|
|
366
|
+
if (attachableSession) {
|
|
367
|
+
try {
|
|
368
|
+
const parsed = JSON.parse(trimmed);
|
|
369
|
+
if (parsed?.type === "response" && typeof parsed.id === "string" && pendingCommandResponses.has(parsed.id)) {
|
|
370
|
+
const pending = pendingCommandResponses.get(parsed.id);
|
|
371
|
+
pendingCommandResponses.delete(parsed.id);
|
|
372
|
+
if (parsed.success === false) {
|
|
373
|
+
pending.reject(new Error(typeof parsed.error === "string" ? parsed.error : "Subagent RPC command failed."));
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
pending.resolve(parsed.data);
|
|
377
|
+
}
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
// Fall through to generic event processing.
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (processSubagentEventLine(trimmed, currentResult, emitUpdate, proc, onSessionInfo, (eventType) => {
|
|
386
|
+
if (eventType === "agent_start")
|
|
387
|
+
isBusy = true;
|
|
388
|
+
if (eventType === "agent_end")
|
|
389
|
+
isBusy = false;
|
|
390
|
+
}, (event) => onSubagentEvent?.(event, currentResult))) {
|
|
391
|
+
completionSeen = true;
|
|
392
|
+
try {
|
|
393
|
+
proc.kill("SIGTERM");
|
|
394
|
+
}
|
|
395
|
+
catch {
|
|
396
|
+
/* ignore */
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
proc.stderr.on("data", (data) => {
|
|
402
|
+
currentResult.stderr += data.toString();
|
|
403
|
+
});
|
|
404
|
+
proc.on("close", (code) => {
|
|
405
|
+
liveSubagentProcesses.delete(proc);
|
|
406
|
+
if (buffer.trim()) {
|
|
407
|
+
const completedOnFlush = processSubagentEventLine(buffer, currentResult, emitUpdate, proc, onSessionInfo, (eventType) => {
|
|
408
|
+
if (eventType === "agent_start")
|
|
409
|
+
isBusy = true;
|
|
410
|
+
if (eventType === "agent_end")
|
|
411
|
+
isBusy = false;
|
|
412
|
+
}, (event) => onSubagentEvent?.(event, currentResult));
|
|
413
|
+
completionSeen = completionSeen || completedOnFlush;
|
|
414
|
+
}
|
|
415
|
+
isBusy = false;
|
|
416
|
+
for (const pending of pendingCommandResponses.values()) {
|
|
417
|
+
pending.reject(new Error("Subagent process closed before command response."));
|
|
418
|
+
}
|
|
419
|
+
pendingCommandResponses.clear();
|
|
420
|
+
const finalExitCode = completionSeen && (code === null || code === 143 || code === 15) ? 0 : (code ?? 0);
|
|
421
|
+
currentResult.exitCode = finalExitCode;
|
|
422
|
+
if (attachableSession && sessionFilesBefore && subagentSessionDir && !currentResult.sessionFile) {
|
|
423
|
+
const detected = detectNewSubagentSessionFile(subagentSessionDir, sessionFilesBefore, launchStartedAt);
|
|
424
|
+
if (detected)
|
|
425
|
+
currentResult.sessionFile = detected;
|
|
426
|
+
}
|
|
427
|
+
resolveBackgroundResult?.({
|
|
428
|
+
summary: getFinalOutput(currentResult.messages),
|
|
429
|
+
stderr: currentResult.stderr,
|
|
430
|
+
exitCode: finalExitCode,
|
|
431
|
+
model: currentResult.model,
|
|
432
|
+
sessionFile: currentResult.sessionFile,
|
|
433
|
+
parentSessionFile: currentResult.parentSessionFile,
|
|
434
|
+
});
|
|
435
|
+
foregroundHooks?.onFinish?.();
|
|
436
|
+
finishForeground(finalExitCode);
|
|
437
|
+
});
|
|
438
|
+
proc.on("error", (error) => {
|
|
439
|
+
liveSubagentProcesses.delete(proc);
|
|
440
|
+
isBusy = false;
|
|
441
|
+
for (const pending of pendingCommandResponses.values()) {
|
|
442
|
+
pending.reject(error instanceof Error ? error : new Error(String(error)));
|
|
443
|
+
}
|
|
444
|
+
pendingCommandResponses.clear();
|
|
445
|
+
rejectBackgroundResult?.(error);
|
|
446
|
+
foregroundHooks?.onFinish?.();
|
|
447
|
+
finishForeground(1);
|
|
448
|
+
});
|
|
449
|
+
if (attachableSession) {
|
|
450
|
+
void sendRpcCommand({ type: "prompt", message: task }).catch((error) => {
|
|
451
|
+
currentResult.stderr += error instanceof Error ? error.message : String(error);
|
|
452
|
+
try {
|
|
453
|
+
proc.kill("SIGTERM");
|
|
454
|
+
}
|
|
455
|
+
catch {
|
|
456
|
+
/* ignore */
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
const killProc = () => {
|
|
461
|
+
wasAborted = true;
|
|
462
|
+
procAbortController.abort();
|
|
463
|
+
proc.kill("SIGTERM");
|
|
464
|
+
setTimeout(() => {
|
|
465
|
+
if (!proc.killed)
|
|
466
|
+
proc.kill("SIGKILL");
|
|
467
|
+
}, 5000);
|
|
468
|
+
};
|
|
469
|
+
if (signal) {
|
|
470
|
+
if (signal.aborted)
|
|
471
|
+
killProc();
|
|
472
|
+
else
|
|
473
|
+
signal.addEventListener("abort", killProc, { once: true });
|
|
474
|
+
}
|
|
475
|
+
if (procAbortController.signal.aborted) {
|
|
476
|
+
killProc();
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
procAbortController.signal.addEventListener("abort", () => {
|
|
480
|
+
proc.kill("SIGTERM");
|
|
481
|
+
setTimeout(() => {
|
|
482
|
+
if (!proc.killed)
|
|
483
|
+
proc.kill("SIGKILL");
|
|
484
|
+
}, 5000);
|
|
485
|
+
}, { once: true });
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
currentResult.exitCode = exitCode;
|
|
489
|
+
if (attachableSession && sessionFilesBefore && subagentSessionDir) {
|
|
490
|
+
const detected = detectNewSubagentSessionFile(subagentSessionDir, sessionFilesBefore, launchStartedAt);
|
|
491
|
+
if (detected) {
|
|
492
|
+
currentResult.sessionFile = detected;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
if (wasAborted)
|
|
496
|
+
throw new Error("Subagent was aborted");
|
|
497
|
+
return currentResult;
|
|
498
|
+
}
|
|
499
|
+
finally {
|
|
500
|
+
if (!deferTempPromptCleanup)
|
|
501
|
+
cleanupTempPromptFiles();
|
|
502
|
+
}
|
|
503
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { shortcutDesc } from "../shared/mod.js";
|
|
2
|
-
import { isKeyRelease, Key, matchesKey, truncateToWidth, visibleWidth } from "@gsd/pi-tui";
|
|
2
|
+
import { isKeyRelease, isKittyProtocolActive, Key, matchesKey, truncateToWidth, visibleWidth } from "@gsd/pi-tui";
|
|
3
3
|
import { spawn, execFileSync } from "node:child_process";
|
|
4
4
|
import * as fs from "node:fs";
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import * as readline from "node:readline";
|
|
7
7
|
import { linuxPython, diagnoseSounddeviceError, ensureVoiceVenv } from "./linux-ready.js";
|
|
8
|
+
import { handlePushToTalkInput } from "./push-to-talk.js";
|
|
8
9
|
const __extensionDir = import.meta.dirname;
|
|
9
10
|
const SWIFT_SRC = path.join(__extensionDir, "speech-recognizer.swift");
|
|
10
11
|
const RECOGNIZER_BIN = path.join(__extensionDir, "speech-recognizer");
|
|
@@ -76,10 +77,14 @@ export default function (pi) {
|
|
|
76
77
|
if (!IS_DARWIN && !IS_LINUX)
|
|
77
78
|
return;
|
|
78
79
|
let active = false;
|
|
80
|
+
let activationMode = null;
|
|
79
81
|
let recognizerProcess = null;
|
|
80
82
|
let flashOn = true;
|
|
81
83
|
let flashTimer = null;
|
|
82
84
|
let footerTui = null;
|
|
85
|
+
let closeVoiceOverlay = null;
|
|
86
|
+
let voiceSessionPromise = null;
|
|
87
|
+
let holdToTalkUnsupportedNotified = false;
|
|
83
88
|
function setVoiceFooter(ctx, on) {
|
|
84
89
|
if (!on) {
|
|
85
90
|
stopFlash();
|
|
@@ -153,41 +158,59 @@ export default function (pi) {
|
|
|
153
158
|
}
|
|
154
159
|
footerTui = null;
|
|
155
160
|
}
|
|
156
|
-
|
|
157
|
-
if (
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
setVoiceFooter(ctx, false);
|
|
161
|
-
return;
|
|
161
|
+
function killRecognizer() {
|
|
162
|
+
if (recognizerProcess) {
|
|
163
|
+
recognizerProcess.kill("SIGTERM");
|
|
164
|
+
recognizerProcess = null;
|
|
162
165
|
}
|
|
166
|
+
}
|
|
167
|
+
function prepareVoice(ctx) {
|
|
163
168
|
if (IS_DARWIN) {
|
|
164
169
|
if (!ensureBinary()) {
|
|
165
170
|
ctx.ui.notify("Voice: failed to compile speech recognizer (need Xcode CLI tools)", "error");
|
|
166
|
-
return;
|
|
171
|
+
return false;
|
|
167
172
|
}
|
|
168
173
|
}
|
|
169
174
|
else if (IS_LINUX) {
|
|
170
175
|
if (!ensureLinuxReady(ctx)) {
|
|
171
|
-
return;
|
|
176
|
+
return false;
|
|
172
177
|
}
|
|
173
178
|
}
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
async function startVoice(ctx, mode) {
|
|
182
|
+
if (active)
|
|
183
|
+
return false;
|
|
184
|
+
if (!prepareVoice(ctx))
|
|
185
|
+
return false;
|
|
174
186
|
active = true;
|
|
187
|
+
activationMode = mode;
|
|
175
188
|
setVoiceFooter(ctx, true);
|
|
176
|
-
|
|
189
|
+
voiceSessionPromise = runVoiceSession(ctx).finally(() => {
|
|
190
|
+
if (voiceSessionPromise) {
|
|
191
|
+
voiceSessionPromise = null;
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
return true;
|
|
177
195
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
196
|
+
async function stopVoice(ctx) {
|
|
197
|
+
if (!active && !closeVoiceOverlay && !recognizerProcess)
|
|
198
|
+
return;
|
|
199
|
+
killRecognizer();
|
|
200
|
+
active = false;
|
|
201
|
+
activationMode = null;
|
|
202
|
+
setVoiceFooter(ctx, false);
|
|
203
|
+
const close = closeVoiceOverlay;
|
|
204
|
+
closeVoiceOverlay = null;
|
|
205
|
+
close?.();
|
|
206
|
+
await voiceSessionPromise;
|
|
207
|
+
}
|
|
208
|
+
async function toggleVoice(ctx) {
|
|
209
|
+
if (active) {
|
|
210
|
+
await stopVoice(ctx);
|
|
211
|
+
return;
|
|
190
212
|
}
|
|
213
|
+
await startVoice(ctx, "toggle");
|
|
191
214
|
}
|
|
192
215
|
function startRecognizer(onPartial, onFinal, onError, onReady) {
|
|
193
216
|
if (IS_LINUX) {
|
|
@@ -224,20 +247,57 @@ export default function (pi) {
|
|
|
224
247
|
}, (text) => {
|
|
225
248
|
ctx.ui.setEditorText(text);
|
|
226
249
|
}, (msg) => ctx.ui.notify(`Voice: ${msg}`, "error"), () => { });
|
|
227
|
-
ctx.ui.custom((_tui, _theme, _kb, done) =>
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
250
|
+
ctx.ui.custom((_tui, _theme, _kb, done) => {
|
|
251
|
+
const close = () => done();
|
|
252
|
+
closeVoiceOverlay = close;
|
|
253
|
+
return {
|
|
254
|
+
render() { return []; },
|
|
255
|
+
handleInput(data) {
|
|
256
|
+
if (isKeyRelease(data))
|
|
257
|
+
return;
|
|
258
|
+
if (matchesKey(data, Key.escape) || matchesKey(data, Key.enter)) {
|
|
259
|
+
void stopVoice(ctx);
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
invalidate() { },
|
|
263
|
+
dispose() {
|
|
264
|
+
if (closeVoiceOverlay === close) {
|
|
265
|
+
closeVoiceOverlay = null;
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
}, { overlay: true, overlayOptions: { anchor: "bottom-center", width: "100%" } }).then(() => resolve());
|
|
241
270
|
});
|
|
242
271
|
}
|
|
272
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
273
|
+
ctx.ui.onTerminalInput((data) => handlePushToTalkInput(data, {
|
|
274
|
+
active,
|
|
275
|
+
activationMode,
|
|
276
|
+
editorText: ctx.ui.getEditorText(),
|
|
277
|
+
holdToTalkSupported: isKittyProtocolActive(),
|
|
278
|
+
onUnsupported: () => {
|
|
279
|
+
if (holdToTalkUnsupportedNotified)
|
|
280
|
+
return;
|
|
281
|
+
holdToTalkUnsupportedNotified = true;
|
|
282
|
+
ctx.ui.notify("Voice: hold Space requires Kitty key-release support in this terminal — use /voice or Ctrl+Alt+V", "warning");
|
|
283
|
+
},
|
|
284
|
+
startPushToTalk: async () => {
|
|
285
|
+
await startVoice(ctx, "push-to-talk");
|
|
286
|
+
},
|
|
287
|
+
stopVoice: async () => {
|
|
288
|
+
await stopVoice(ctx);
|
|
289
|
+
},
|
|
290
|
+
}));
|
|
291
|
+
});
|
|
292
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
293
|
+
await stopVoice(ctx);
|
|
294
|
+
});
|
|
295
|
+
pi.registerCommand("voice", {
|
|
296
|
+
description: "Toggle voice mode",
|
|
297
|
+
handler: async (_args, ctx) => toggleVoice(ctx),
|
|
298
|
+
});
|
|
299
|
+
pi.registerShortcut("ctrl+alt+v", {
|
|
300
|
+
description: shortcutDesc("Toggle voice mode", "/voice"),
|
|
301
|
+
handler: async (ctx) => toggleVoice(ctx),
|
|
302
|
+
});
|
|
243
303
|
}
|