@oh-my-pi/pi-coding-agent 12.4.0 → 12.5.0
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/CHANGELOG.md +51 -0
- package/docs/custom-tools.md +21 -6
- package/docs/extensions.md +20 -0
- package/package.json +12 -12
- package/src/cli/setup-cli.ts +62 -2
- package/src/commands/setup.ts +1 -1
- package/src/config/keybindings.ts +4 -1
- package/src/config/settings-schema.ts +58 -4
- package/src/config/settings.ts +23 -9
- package/src/debug/index.ts +26 -19
- package/src/debug/log-formatting.ts +60 -0
- package/src/debug/log-viewer.ts +903 -0
- package/src/debug/report-bundle.ts +87 -8
- package/src/discovery/helpers.ts +131 -137
- package/src/extensibility/custom-tools/types.ts +44 -6
- package/src/extensibility/extensions/types.ts +60 -0
- package/src/extensibility/hooks/types.ts +60 -0
- package/src/extensibility/skills.ts +4 -2
- package/src/main.ts +7 -1
- package/src/modes/components/custom-editor.ts +8 -0
- package/src/modes/components/settings-selector.ts +29 -14
- package/src/modes/controllers/command-controller.ts +2 -0
- package/src/modes/controllers/event-controller.ts +7 -0
- package/src/modes/controllers/input-controller.ts +23 -2
- package/src/modes/controllers/selector-controller.ts +9 -7
- package/src/modes/interactive-mode.ts +84 -1
- package/src/modes/rpc/rpc-client.ts +7 -0
- package/src/modes/rpc/rpc-mode.ts +8 -0
- package/src/modes/rpc/rpc-types.ts +2 -0
- package/src/modes/theme/theme.ts +163 -7
- package/src/modes/types.ts +1 -0
- package/src/patch/hashline.ts +2 -1
- package/src/patch/shared.ts +44 -13
- package/src/prompts/system/plan-mode-approved.md +5 -0
- package/src/prompts/system/subagent-system-prompt.md +1 -0
- package/src/prompts/system/system-prompt.md +10 -0
- package/src/prompts/tools/todo-write.md +3 -1
- package/src/sdk.ts +82 -9
- package/src/session/agent-session.ts +137 -29
- package/src/stt/downloader.ts +71 -0
- package/src/stt/index.ts +3 -0
- package/src/stt/recorder.ts +351 -0
- package/src/stt/setup.ts +52 -0
- package/src/stt/stt-controller.ts +160 -0
- package/src/stt/transcribe.py +70 -0
- package/src/stt/transcriber.ts +91 -0
- package/src/task/executor.ts +10 -2
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
2
|
+
import transcribeScript from "./transcribe.py" with { type: "text" };
|
|
3
|
+
|
|
4
|
+
export interface TranscribeOptions {
|
|
5
|
+
modelName?: string;
|
|
6
|
+
language?: string;
|
|
7
|
+
signal?: AbortSignal;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const TRANSCRIBE_TIMEOUT_MS = 120_000;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Find a usable Python command.
|
|
14
|
+
*/
|
|
15
|
+
export function resolvePython(): string | null {
|
|
16
|
+
for (const cmd of ["python", "py", "python3"]) {
|
|
17
|
+
if (Bun.which(cmd)) return cmd;
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Transcribe a WAV file using Python openai-whisper.
|
|
24
|
+
*
|
|
25
|
+
* Reads the WAV via Python's built-in `wave` module (no ffmpeg needed),
|
|
26
|
+
* resamples to 16 kHz mono, and passes the numpy array directly to whisper.
|
|
27
|
+
*/
|
|
28
|
+
export async function transcribe(audioPath: string, options?: TranscribeOptions): Promise<string> {
|
|
29
|
+
const audioFile = Bun.file(audioPath);
|
|
30
|
+
if (audioFile.size < 100) {
|
|
31
|
+
throw new Error(`Audio file is empty or too small (${audioFile.size} bytes). Check microphone.`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const pythonCmd = resolvePython();
|
|
35
|
+
if (!pythonCmd) {
|
|
36
|
+
throw new Error("Python not found. Install Python 3.8+ from https://python.org");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const modelName = options?.modelName ?? "base.en";
|
|
40
|
+
const language = options?.language ?? "en";
|
|
41
|
+
|
|
42
|
+
logger.debug("Transcribing with Python whisper", { pythonCmd, audioPath, modelName, language });
|
|
43
|
+
|
|
44
|
+
const proc = Bun.spawn([pythonCmd, "-c", transcribeScript, audioPath, modelName, language], {
|
|
45
|
+
stdout: "pipe",
|
|
46
|
+
stderr: "pipe",
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (options?.signal?.aborted) {
|
|
50
|
+
proc.kill();
|
|
51
|
+
options.signal.throwIfAborted();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const onAbort = () => proc.kill();
|
|
55
|
+
options?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
56
|
+
|
|
57
|
+
let timedOut = false;
|
|
58
|
+
|
|
59
|
+
const killTimer = setTimeout(() => {
|
|
60
|
+
timedOut = true;
|
|
61
|
+
logger.error("Python whisper transcription timed out, killing process", { timeoutMs: TRANSCRIBE_TIMEOUT_MS });
|
|
62
|
+
proc.kill();
|
|
63
|
+
}, TRANSCRIBE_TIMEOUT_MS);
|
|
64
|
+
|
|
65
|
+
const exitCode = await proc.exited;
|
|
66
|
+
clearTimeout(killTimer);
|
|
67
|
+
options?.signal?.removeEventListener("abort", onAbort);
|
|
68
|
+
|
|
69
|
+
options?.signal?.throwIfAborted();
|
|
70
|
+
|
|
71
|
+
const stdout = await new Response(proc.stdout).text();
|
|
72
|
+
const stderr = await new Response(proc.stderr).text();
|
|
73
|
+
|
|
74
|
+
if (timedOut) {
|
|
75
|
+
throw new Error(`Transcription timed out after ${Math.round(TRANSCRIBE_TIMEOUT_MS / 1000)}s`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (exitCode !== 0) {
|
|
79
|
+
logger.error("Python whisper transcription failed", { exitCode, stderr: stderr.trim() });
|
|
80
|
+
if (stderr.includes("No module named 'whisper'")) {
|
|
81
|
+
throw new Error("openai-whisper not installed. Run: pip install openai-whisper");
|
|
82
|
+
}
|
|
83
|
+
// Show last line of stderr (the actual error, not the full traceback)
|
|
84
|
+
const lastLine = stderr.trim().split("\n").pop() ?? "";
|
|
85
|
+
throw new Error(`Transcription failed: ${lastLine}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const text = stdout.trim();
|
|
89
|
+
logger.debug("Transcription complete", { length: text.length });
|
|
90
|
+
return text;
|
|
91
|
+
}
|
package/src/task/executor.ts
CHANGED
|
@@ -887,10 +887,17 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
887
887
|
|
|
888
888
|
activeSession = session;
|
|
889
889
|
|
|
890
|
+
const subagentToolNames = session.getActiveToolNames();
|
|
891
|
+
const parentOwnedToolNames = new Set(["todo_write"]);
|
|
892
|
+
const filteredSubagentTools = subagentToolNames.filter(name => !parentOwnedToolNames.has(name));
|
|
893
|
+
if (filteredSubagentTools.length !== subagentToolNames.length) {
|
|
894
|
+
await session.setActiveToolsByName(filteredSubagentTools);
|
|
895
|
+
}
|
|
896
|
+
|
|
890
897
|
session.sessionManager.appendSessionInit({
|
|
891
898
|
systemPrompt: session.agent.state.systemPrompt,
|
|
892
899
|
task,
|
|
893
|
-
tools: session.
|
|
900
|
+
tools: session.getActiveToolNames(),
|
|
894
901
|
outputSchema,
|
|
895
902
|
});
|
|
896
903
|
|
|
@@ -928,7 +935,8 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
928
935
|
},
|
|
929
936
|
getActiveTools: () => session.getActiveToolNames(),
|
|
930
937
|
getAllTools: () => session.getAllToolNames(),
|
|
931
|
-
setActiveTools: (toolNames: string[]) =>
|
|
938
|
+
setActiveTools: (toolNames: string[]) =>
|
|
939
|
+
session.setActiveToolsByName(toolNames.filter(name => !parentOwnedToolNames.has(name))),
|
|
932
940
|
getCommands: () => [],
|
|
933
941
|
setModel: async model => {
|
|
934
942
|
const key = await session.modelRegistry.getApiKey(model);
|