@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.
Files changed (47) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/docs/custom-tools.md +21 -6
  3. package/docs/extensions.md +20 -0
  4. package/package.json +12 -12
  5. package/src/cli/setup-cli.ts +62 -2
  6. package/src/commands/setup.ts +1 -1
  7. package/src/config/keybindings.ts +4 -1
  8. package/src/config/settings-schema.ts +58 -4
  9. package/src/config/settings.ts +23 -9
  10. package/src/debug/index.ts +26 -19
  11. package/src/debug/log-formatting.ts +60 -0
  12. package/src/debug/log-viewer.ts +903 -0
  13. package/src/debug/report-bundle.ts +87 -8
  14. package/src/discovery/helpers.ts +131 -137
  15. package/src/extensibility/custom-tools/types.ts +44 -6
  16. package/src/extensibility/extensions/types.ts +60 -0
  17. package/src/extensibility/hooks/types.ts +60 -0
  18. package/src/extensibility/skills.ts +4 -2
  19. package/src/main.ts +7 -1
  20. package/src/modes/components/custom-editor.ts +8 -0
  21. package/src/modes/components/settings-selector.ts +29 -14
  22. package/src/modes/controllers/command-controller.ts +2 -0
  23. package/src/modes/controllers/event-controller.ts +7 -0
  24. package/src/modes/controllers/input-controller.ts +23 -2
  25. package/src/modes/controllers/selector-controller.ts +9 -7
  26. package/src/modes/interactive-mode.ts +84 -1
  27. package/src/modes/rpc/rpc-client.ts +7 -0
  28. package/src/modes/rpc/rpc-mode.ts +8 -0
  29. package/src/modes/rpc/rpc-types.ts +2 -0
  30. package/src/modes/theme/theme.ts +163 -7
  31. package/src/modes/types.ts +1 -0
  32. package/src/patch/hashline.ts +2 -1
  33. package/src/patch/shared.ts +44 -13
  34. package/src/prompts/system/plan-mode-approved.md +5 -0
  35. package/src/prompts/system/subagent-system-prompt.md +1 -0
  36. package/src/prompts/system/system-prompt.md +10 -0
  37. package/src/prompts/tools/todo-write.md +3 -1
  38. package/src/sdk.ts +82 -9
  39. package/src/session/agent-session.ts +137 -29
  40. package/src/stt/downloader.ts +71 -0
  41. package/src/stt/index.ts +3 -0
  42. package/src/stt/recorder.ts +351 -0
  43. package/src/stt/setup.ts +52 -0
  44. package/src/stt/stt-controller.ts +160 -0
  45. package/src/stt/transcribe.py +70 -0
  46. package/src/stt/transcriber.ts +91 -0
  47. 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
+ }
@@ -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.getAllToolNames(),
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[]) => session.setActiveToolsByName(toolNames),
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);