@oh-my-pi/pi-coding-agent 17.0.9 → 17.1.1

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 (349) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +1 -1
  3. package/dist/cli.js +4685 -4614
  4. package/dist/types/async/job-manager.d.ts +37 -1
  5. package/dist/types/cli/args.d.ts +2 -0
  6. package/dist/types/cli/bench-cli.d.ts +41 -0
  7. package/dist/types/commands/bench.d.ts +15 -3
  8. package/dist/types/commands/launch.d.ts +4 -0
  9. package/dist/types/config/model-registry.d.ts +3 -1
  10. package/dist/types/config/models-config-schema.d.ts +18 -6
  11. package/dist/types/config/models-config.d.ts +14 -5
  12. package/dist/types/config/provider-globals.d.ts +2 -2
  13. package/dist/types/config/service-tier.d.ts +5 -1
  14. package/dist/types/config/settings-schema.d.ts +374 -47
  15. package/dist/types/config/settings.d.ts +7 -0
  16. package/dist/types/dap/client.d.ts +20 -0
  17. package/dist/types/exec/bash-executor.d.ts +38 -0
  18. package/dist/types/exec/direnv.d.ts +33 -0
  19. package/dist/types/export/html/args.d.ts +15 -0
  20. package/dist/types/export/html/index.d.ts +11 -26
  21. package/dist/types/export/html/web-palette.d.ts +55 -116
  22. package/dist/types/extensibility/extensions/loader.d.ts +3 -0
  23. package/dist/types/extensibility/extensions/types.d.ts +17 -2
  24. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +3 -0
  25. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +13 -0
  26. package/dist/types/live/attestation.d.ts +2 -0
  27. package/dist/types/live/controller.d.ts +48 -0
  28. package/dist/types/live/protocol.d.ts +85 -0
  29. package/dist/types/live/protocol.test.d.ts +1 -0
  30. package/dist/types/live/transport.d.ts +35 -0
  31. package/dist/types/live/visualizer.d.ts +30 -0
  32. package/dist/types/lsp/client.d.ts +16 -1
  33. package/dist/types/mcp/config-writer.test.d.ts +1 -0
  34. package/dist/types/mcp/oauth-discovery.d.ts +2 -0
  35. package/dist/types/mcp/smithery-auth.d.ts +1 -1
  36. package/dist/types/mcp/smithery-auth.test.d.ts +1 -0
  37. package/dist/types/memory-backend/tool-names.d.ts +2 -0
  38. package/dist/types/modes/components/assistant-message.d.ts +1 -0
  39. package/dist/types/modes/components/custom-message.d.ts +1 -1
  40. package/dist/types/modes/components/login-dialog.d.ts +2 -2
  41. package/dist/types/modes/components/message-frame.d.ts +8 -4
  42. package/dist/types/modes/components/plan-review-overlay.d.ts +26 -0
  43. package/dist/types/modes/components/session-account-selector.d.ts +11 -0
  44. package/dist/types/modes/components/settings-defs.d.ts +7 -1
  45. package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +1 -0
  46. package/dist/types/modes/components/tool-execution.d.ts +9 -1
  47. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  48. package/dist/types/modes/controllers/event-controller.d.ts +6 -1
  49. package/dist/types/modes/controllers/live-command-controller.d.ts +14 -0
  50. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  51. package/dist/types/modes/interactive-mode.d.ts +6 -0
  52. package/dist/types/modes/theme/theme.d.ts +1 -1
  53. package/dist/types/modes/types.d.ts +4 -1
  54. package/dist/types/modes/utils/context-usage.d.ts +12 -2
  55. package/dist/types/plan-mode/plan-files.d.ts +10 -0
  56. package/dist/types/registry/agent-lifecycle.d.ts +21 -9
  57. package/dist/types/registry/agent-registry.d.ts +11 -4
  58. package/dist/types/sdk.d.ts +15 -1
  59. package/dist/types/secrets/index.d.ts +15 -2
  60. package/dist/types/secrets/obfuscator.d.ts +113 -15
  61. package/dist/types/session/acp-permission-gate.d.ts +19 -0
  62. package/dist/types/session/agent-session-events.d.ts +84 -0
  63. package/dist/types/session/agent-session-types.d.ts +321 -0
  64. package/dist/types/session/agent-session.d.ts +135 -753
  65. package/dist/types/session/async-job-delivery.d.ts +28 -0
  66. package/dist/types/session/auth-storage.d.ts +1 -1
  67. package/dist/types/session/bash-runner.d.ts +75 -0
  68. package/dist/types/session/blob-store.d.ts +11 -1
  69. package/dist/types/session/blob-store.test.d.ts +1 -0
  70. package/dist/types/session/checkpoint-entries.d.ts +28 -0
  71. package/dist/types/session/eval-runner.d.ts +51 -0
  72. package/dist/types/session/irc-bridge.d.ts +46 -0
  73. package/dist/types/session/messages.d.ts +24 -0
  74. package/dist/types/session/model-controls.d.ts +180 -0
  75. package/dist/types/session/prewalk.d.ts +46 -0
  76. package/dist/types/session/queued-messages.d.ts +22 -0
  77. package/dist/types/session/redis-session-storage.d.ts +1 -0
  78. package/dist/types/session/retry-fallback-chains.d.ts +74 -0
  79. package/dist/types/session/role-models.d.ts +15 -0
  80. package/dist/types/session/session-advisors.d.ts +228 -0
  81. package/dist/types/session/session-entries.d.ts +8 -0
  82. package/dist/types/session/session-handoff.d.ts +70 -0
  83. package/dist/types/session/session-history-format.d.ts +6 -1
  84. package/dist/types/session/session-maintenance.d.ts +246 -0
  85. package/dist/types/session/session-manager.d.ts +41 -0
  86. package/dist/types/session/session-memory.d.ts +56 -0
  87. package/dist/types/session/session-provider-boundary.d.ts +62 -0
  88. package/dist/types/session/session-stats.d.ts +48 -0
  89. package/dist/types/session/session-tools.d.ts +158 -0
  90. package/dist/types/session/session-workspace.d.ts +32 -0
  91. package/dist/types/session/stream-guards.d.ts +44 -0
  92. package/dist/types/session/streaming-output.d.ts +8 -0
  93. package/dist/types/session/todo-tracker.d.ts +56 -0
  94. package/dist/types/session/ttsr-coordinator.d.ts +51 -0
  95. package/dist/types/session/turn-recovery.d.ts +225 -0
  96. package/dist/types/slash-commands/helpers/session-pin.d.ts +9 -0
  97. package/dist/types/stt/index.d.ts +0 -2
  98. package/dist/types/stt/stt-controller.d.ts +7 -0
  99. package/dist/types/system-prompt.d.ts +2 -0
  100. package/dist/types/task/executor.d.ts +4 -1
  101. package/dist/types/task/output-manager.d.ts +3 -1
  102. package/dist/types/tiny/title-client.d.ts +10 -0
  103. package/dist/types/tools/approval.d.ts +1 -0
  104. package/dist/types/tools/browser/launch.d.ts +5 -0
  105. package/dist/types/tools/builtin-names.d.ts +1 -1
  106. package/dist/types/tools/computer/protocol.d.ts +43 -0
  107. package/dist/types/tools/computer/supervisor.d.ts +32 -0
  108. package/dist/types/tools/computer/worker-entry.d.ts +1 -0
  109. package/dist/types/tools/computer/worker.d.ts +15 -0
  110. package/dist/types/tools/computer-renderer.d.ts +22 -0
  111. package/dist/types/tools/computer.d.ts +71 -0
  112. package/dist/types/tools/context.d.ts +2 -0
  113. package/dist/types/tools/default-renderer.d.ts +21 -0
  114. package/dist/types/tools/image-gen.d.ts +4 -4
  115. package/dist/types/tools/image-providers.d.ts +38 -0
  116. package/dist/types/tools/index.d.ts +7 -0
  117. package/dist/types/tools/path-utils.d.ts +8 -0
  118. package/dist/types/tools/todo.d.ts +10 -4
  119. package/dist/types/tools/xdev.d.ts +5 -1
  120. package/dist/types/tts/streaming-player.d.ts +10 -29
  121. package/dist/types/tts/vocalizer.d.ts +5 -0
  122. package/dist/types/utils/jj.d.ts +29 -0
  123. package/dist/types/utils/lang-from-path.d.ts +1 -0
  124. package/dist/types/utils/title-generator.d.ts +30 -0
  125. package/dist/types/utils/tools-manager.d.ts +1 -2
  126. package/dist/types/vibe/runtime.d.ts +36 -8
  127. package/dist/types/web/search/index.d.ts +1 -1
  128. package/dist/types/web/search/provider.d.ts +15 -5
  129. package/dist/types/web/search/types.d.ts +94 -0
  130. package/package.json +12 -12
  131. package/scripts/generate-share-viewer.ts +4 -6
  132. package/src/advisor/__tests__/advisor.test.ts +643 -0
  133. package/src/advisor/runtime.ts +144 -43
  134. package/src/async/job-manager.ts +90 -2
  135. package/src/cli/args.ts +3 -0
  136. package/src/cli/auth-gateway-cli.ts +18 -3
  137. package/src/cli/bench-cli.ts +395 -29
  138. package/src/cli/flag-tables.ts +3 -0
  139. package/src/cli/gc-cli.ts +2 -2
  140. package/src/cli/setup-cli.ts +2 -14
  141. package/src/cli/usage-cli.ts +10 -1
  142. package/src/cli.ts +8 -0
  143. package/src/collab/host.ts +3 -2
  144. package/src/commands/bench.ts +18 -4
  145. package/src/commands/launch.ts +4 -0
  146. package/src/config/model-registry.ts +16 -2
  147. package/src/config/models-config-schema.ts +15 -4
  148. package/src/config/provider-globals.ts +9 -9
  149. package/src/config/service-tier.ts +26 -1
  150. package/src/config/settings-schema.ts +270 -51
  151. package/src/config/settings.ts +81 -2
  152. package/src/dap/client.ts +37 -4
  153. package/src/edit/index.ts +49 -12
  154. package/src/eval/executor-base.ts +1 -0
  155. package/src/eval/js/executor.ts +2 -0
  156. package/src/eval/py/executor.ts +103 -33
  157. package/src/exec/bash-executor.ts +90 -6
  158. package/src/exec/direnv.ts +145 -0
  159. package/src/export/html/args.ts +20 -0
  160. package/src/export/html/index.ts +45 -47
  161. package/src/export/html/template.css +19 -1
  162. package/src/export/html/template.html +6 -0
  163. package/src/export/html/template.js +21 -0
  164. package/src/export/html/tool-views.generated.js +31 -31
  165. package/src/export/html/web-palette.ts +116 -132
  166. package/src/export/share.ts +249 -49
  167. package/src/extensibility/extensions/loader.ts +29 -1
  168. package/src/extensibility/extensions/runner.ts +6 -0
  169. package/src/extensibility/extensions/types.ts +33 -2
  170. package/src/extensibility/extensions/wrapper.ts +68 -12
  171. package/src/extensibility/legacy-pi-coding-agent-shim.ts +7 -1
  172. package/src/extensibility/plugins/legacy-pi-compat.ts +19 -1
  173. package/src/hindsight/state.ts +64 -6
  174. package/src/internal-urls/local-protocol.ts +2 -1
  175. package/src/internal-urls/memory-protocol.ts +2 -2
  176. package/src/internal-urls/skill-protocol.ts +2 -2
  177. package/src/internal-urls/ssh-protocol.ts +2 -1
  178. package/src/internal-urls/vault-protocol.ts +2 -1
  179. package/src/live/attestation.ts +91 -0
  180. package/src/live/controller.ts +517 -0
  181. package/src/live/prompts/agent-final-message.md +3 -0
  182. package/src/live/prompts/live-instructions.md +23 -0
  183. package/src/live/protocol.test.ts +140 -0
  184. package/src/live/protocol.ts +233 -0
  185. package/src/live/transport.ts +422 -0
  186. package/src/live/visualizer.ts +214 -0
  187. package/src/lsp/client.ts +29 -9
  188. package/src/lsp/index.ts +19 -4
  189. package/src/main.ts +8 -1
  190. package/src/mcp/config-writer.test.ts +43 -0
  191. package/src/mcp/config-writer.ts +109 -82
  192. package/src/mcp/oauth-discovery.ts +10 -2
  193. package/src/mcp/smithery-auth.test.ts +29 -0
  194. package/src/mcp/smithery-auth.ts +3 -2
  195. package/src/memories/index.ts +40 -20
  196. package/src/memory-backend/tool-names.ts +2 -0
  197. package/src/mnemopi/backend.ts +24 -7
  198. package/src/modes/acp/acp-agent.ts +56 -10
  199. package/src/modes/acp/acp-event-mapper.ts +8 -1
  200. package/src/modes/components/agent-hub.ts +1 -1
  201. package/src/modes/components/assistant-message.ts +7 -2
  202. package/src/modes/components/custom-message.ts +4 -1
  203. package/src/modes/components/login-dialog.ts +14 -4
  204. package/src/modes/components/message-frame.ts +14 -8
  205. package/src/modes/components/plan-review-overlay.ts +350 -35
  206. package/src/modes/components/session-account-selector.ts +62 -0
  207. package/src/modes/components/settings-defs.ts +23 -1
  208. package/src/modes/components/settings-selector.ts +169 -1
  209. package/src/modes/components/status-line/component.jj-cache.test.ts +229 -0
  210. package/src/modes/components/status-line/component.ts +123 -2
  211. package/src/modes/components/tool-execution.ts +28 -112
  212. package/src/modes/controllers/command-controller.ts +56 -15
  213. package/src/modes/controllers/event-controller.ts +111 -16
  214. package/src/modes/controllers/extension-ui-controller.test.ts +1 -0
  215. package/src/modes/controllers/extension-ui-controller.ts +20 -4
  216. package/src/modes/controllers/input-controller.ts +47 -39
  217. package/src/modes/controllers/live-command-controller.ts +255 -0
  218. package/src/modes/controllers/mcp-command-controller.ts +10 -1
  219. package/src/modes/controllers/omfg-controller.ts +44 -40
  220. package/src/modes/controllers/selector-controller.ts +80 -10
  221. package/src/modes/controllers/session-focus-controller.ts +6 -1
  222. package/src/modes/interactive-mode.ts +176 -38
  223. package/src/modes/runtime-init.ts +2 -0
  224. package/src/modes/setup-wizard/scenes/web-search.ts +17 -9
  225. package/src/modes/theme/theme.ts +1 -1
  226. package/src/modes/types.ts +4 -1
  227. package/src/modes/utils/context-usage.ts +23 -7
  228. package/src/modes/utils/ui-helpers.ts +10 -5
  229. package/src/plan-mode/plan-files.ts +40 -0
  230. package/src/prompts/bench/cache-prefix-chunk.md +1 -0
  231. package/src/prompts/bench/cache-prefix.md +3 -0
  232. package/src/prompts/bench/cache-suffix.md +1 -0
  233. package/src/prompts/system/computer-safety.md +14 -0
  234. package/src/prompts/system/custom-system-prompt.md +1 -1
  235. package/src/prompts/system/project-prompt.md +8 -1
  236. package/src/prompts/system/subagent-async-pending.md +6 -0
  237. package/src/prompts/system/system-prompt.md +1 -1
  238. package/src/prompts/system/workflow-notice.md +48 -40
  239. package/src/prompts/system/xdev-mount-notice.md +4 -0
  240. package/src/prompts/tools/computer.md +26 -0
  241. package/src/prompts/tools/todo.md +3 -1
  242. package/src/registry/agent-lifecycle.ts +116 -46
  243. package/src/registry/agent-registry.ts +35 -8
  244. package/src/registry/persisted-agents.ts +26 -2
  245. package/src/sdk.ts +215 -136
  246. package/src/secrets/index.ts +127 -4
  247. package/src/secrets/obfuscator.ts +2328 -105
  248. package/src/session/acp-permission-gate.ts +165 -0
  249. package/src/session/agent-session-error-log.test.ts +1 -1
  250. package/src/session/agent-session-events.ts +66 -0
  251. package/src/session/agent-session-types.ts +343 -0
  252. package/src/session/agent-session.ts +1940 -12218
  253. package/src/session/artifacts.ts +6 -5
  254. package/src/session/async-job-delivery.ts +74 -0
  255. package/src/session/auth-storage.ts +1 -0
  256. package/src/session/bash-runner.ts +326 -0
  257. package/src/session/blob-store.test.ts +56 -0
  258. package/src/session/blob-store.ts +18 -2
  259. package/src/session/checkpoint-entries.ts +81 -0
  260. package/src/session/eval-runner.ts +217 -0
  261. package/src/session/indexed-session-storage.ts +9 -2
  262. package/src/session/irc-bridge.ts +203 -0
  263. package/src/session/messages.ts +266 -1
  264. package/src/session/model-controls.ts +714 -0
  265. package/src/session/prewalk.ts +252 -0
  266. package/src/session/queued-messages.ts +93 -0
  267. package/src/session/redis-session-storage.ts +51 -11
  268. package/src/session/retry-fallback-chains.ts +455 -0
  269. package/src/session/role-models.ts +85 -0
  270. package/src/session/session-advisors.ts +1679 -0
  271. package/src/session/session-context.ts +6 -3
  272. package/src/session/session-entries.ts +8 -0
  273. package/src/session/session-handoff.ts +308 -0
  274. package/src/session/session-history-format.ts +20 -9
  275. package/src/session/session-loader.ts +0 -46
  276. package/src/session/session-maintenance.ts +2983 -0
  277. package/src/session/session-manager.ts +390 -30
  278. package/src/session/session-memory.ts +222 -0
  279. package/src/session/session-provider-boundary.ts +307 -0
  280. package/src/session/session-stats.ts +293 -0
  281. package/src/session/session-tools.ts +980 -0
  282. package/src/session/session-workspace.ts +53 -0
  283. package/src/session/stream-guards.ts +417 -0
  284. package/src/session/streaming-output.ts +52 -5
  285. package/src/session/todo-tracker.ts +380 -0
  286. package/src/session/ttsr-coordinator.ts +496 -0
  287. package/src/session/turn-recovery.ts +1629 -0
  288. package/src/slash-commands/builtin-registry.ts +257 -20
  289. package/src/slash-commands/helpers/session-pin.ts +44 -0
  290. package/src/slash-commands/helpers/usage-report.ts +22 -3
  291. package/src/stt/downloader.ts +0 -2
  292. package/src/stt/index.ts +0 -2
  293. package/src/stt/stt-controller.ts +57 -146
  294. package/src/system-prompt.ts +45 -17
  295. package/src/task/executor.ts +285 -70
  296. package/src/task/index.ts +98 -105
  297. package/src/task/output-manager.ts +25 -3
  298. package/src/task/persisted-revive.ts +4 -3
  299. package/src/task/structured-subagent.ts +1 -0
  300. package/src/tiny/title-client.ts +22 -0
  301. package/src/tools/approval.ts +55 -10
  302. package/src/tools/bash-interactive.ts +116 -86
  303. package/src/tools/bash-skill-urls.ts +28 -2
  304. package/src/tools/bash.ts +339 -123
  305. package/src/tools/browser/launch.ts +9 -1
  306. package/src/tools/browser/tab-supervisor.ts +8 -5
  307. package/src/tools/browser.ts +94 -47
  308. package/src/tools/builtin-names.ts +1 -0
  309. package/src/tools/computer/protocol.ts +28 -0
  310. package/src/tools/computer/supervisor.ts +258 -0
  311. package/src/tools/computer/worker-entry.ts +25 -0
  312. package/src/tools/computer/worker.ts +135 -0
  313. package/src/tools/computer-renderer.ts +108 -0
  314. package/src/tools/computer.ts +433 -0
  315. package/src/tools/context.ts +2 -0
  316. package/src/tools/default-renderer.ts +139 -0
  317. package/src/tools/essential-tools.ts +1 -0
  318. package/src/tools/image-gen.ts +20 -28
  319. package/src/tools/image-providers.ts +50 -0
  320. package/src/tools/index.ts +14 -0
  321. package/src/tools/path-utils.ts +49 -2
  322. package/src/tools/read.ts +156 -88
  323. package/src/tools/renderers.ts +9 -1
  324. package/src/tools/todo.ts +101 -11
  325. package/src/tools/vibe.ts +1 -2
  326. package/src/tools/write.ts +51 -1
  327. package/src/tools/xdev.ts +121 -29
  328. package/src/tts/streaming-player.ts +82 -283
  329. package/src/tts/vocalizer.ts +18 -1
  330. package/src/utils/clipboard.ts +1 -30
  331. package/src/utils/jj.ts +125 -4
  332. package/src/utils/lang-from-path.ts +7 -0
  333. package/src/utils/mac-file-urls.applescript +37 -0
  334. package/src/utils/title-generator.ts +137 -1
  335. package/src/utils/tool-choice.ts +14 -0
  336. package/src/utils/tools-manager.ts +1 -19
  337. package/src/vibe/runtime.ts +852 -72
  338. package/src/web/search/index.ts +3 -6
  339. package/src/web/search/provider.ts +31 -16
  340. package/src/web/search/providers/xai.ts +7 -1
  341. package/src/web/search/types.ts +3 -0
  342. package/dist/types/stt/recorder.d.ts +0 -30
  343. package/dist/types/stt/transcriber.d.ts +0 -14
  344. package/dist/types/stt/wav.d.ts +0 -29
  345. package/dist/types/tts/player.d.ts +0 -32
  346. package/src/stt/recorder.ts +0 -551
  347. package/src/stt/transcriber.ts +0 -60
  348. package/src/stt/wav.ts +0 -173
  349. package/src/tts/player.ts +0 -137
@@ -1,22 +1,10 @@
1
- import * as fs from "node:fs/promises";
2
- import * as os from "node:os";
3
- import * as path from "node:path";
4
- import { logger, Snowflake } from "@oh-my-pi/pi-utils";
1
+ import { AudioCapture } from "@oh-my-pi/pi-natives";
2
+ import { logger } from "@oh-my-pi/pi-utils";
5
3
  import { settings } from "../config/settings";
6
4
  import { type SttStreamHandle, sttClient } from "./asr-client";
7
5
  import { downloadSttModel, isSttModelCached } from "./downloader";
8
6
  import { resolveSttModelSpec } from "./models";
9
- import {
10
- detectRecorder,
11
- ensureRecorder,
12
- type RecordingHandle,
13
- type StreamingRecordingHandle,
14
- startRecording,
15
- startStreamingRecording,
16
- verifyRecordingFile,
17
- } from "./recorder";
18
7
  import { evaluateSubmitTrigger } from "./submit-trigger";
19
- import { transcribe } from "./transcriber";
20
8
 
21
9
  export type SttState = "idle" | "recording" | "transcribing";
22
10
 
@@ -38,26 +26,34 @@ interface Editor {
38
26
  deleteBeforeCursor(count: number): void;
39
27
  }
40
28
 
29
+ interface CaptureHandle {
30
+ stop(): void;
31
+ }
32
+
33
+ type CaptureFactory = (onAudio: (error: Error | null, samples: Float32Array) => void) => CaptureHandle;
34
+
35
+ /** Coordinates native microphone capture with incremental local transcription. */
41
36
  export class STTController {
42
37
  #state: SttState = "idle";
43
38
  #resolvedModelKey: string | null = null;
44
39
  #toggling = false;
45
40
  #stopAfterStart = false;
46
41
  #disposed = false;
47
-
48
- // Batch (single-shot) capture.
49
- #recordingHandle: RecordingHandle | null = null;
50
- #tempFile: string | null = null;
51
- #transcriptionAbort: AbortController | null = null;
42
+ readonly #createCapture: CaptureFactory;
52
43
 
53
44
  // Live streaming capture.
54
45
  #stream: SttStreamHandle | null = null;
55
- #streamRecorder: StreamingRecordingHandle | null = null;
46
+ #streamRecorder: CaptureHandle | null = null;
56
47
  #streamEditor: Editor | null = null;
57
48
  #streamCommitted = false;
58
49
  #streamAbort: AbortController | null = null;
59
50
  #streamUtterance = "";
60
51
 
52
+ /** Creates a controller; tests may replace the hardware capture boundary. */
53
+ constructor(createCapture: CaptureFactory = onAudio => new AudioCapture(16_000, onAudio)) {
54
+ this.#createCapture = createCapture;
55
+ }
56
+
61
57
  get state(): SttState {
62
58
  return this.#state;
63
59
  }
@@ -79,7 +75,7 @@ export class STTController {
79
75
  await this.#start(editor, options);
80
76
  break;
81
77
  case "recording":
82
- await this.#stop(editor, options);
78
+ await this.#stop(options);
83
79
  break;
84
80
  case "transcribing":
85
81
  options.showStatus("Transcription in progress...");
@@ -87,7 +83,7 @@ export class STTController {
87
83
  }
88
84
  if (this.#stopAfterStart && this.#state === "recording") {
89
85
  this.#stopAfterStart = false;
90
- await this.#stop(editor, options);
86
+ await this.#stop(options);
91
87
  } else if (this.#state !== "recording") {
92
88
  this.#stopAfterStart = false;
93
89
  }
@@ -103,19 +99,13 @@ export class STTController {
103
99
  // (with progress) instead of blocking silently at stop.
104
100
  if (this.#resolvedModelKey === modelKey) return true;
105
101
  try {
106
- // Only clear the status line if we actually wrote to it: the cached
107
- // fast path (recorder on PATH, model present) emits nothing, so an
108
- // unconditional clear would be a stray write.
102
+ // Only clear the status line when preflight emitted progress; the
103
+ // cached-model fast path emits nothing.
109
104
  let wroteStatus = false;
110
105
  const status = (msg: string): void => {
111
106
  wroteStatus = true;
112
107
  options.showStatus(msg);
113
108
  };
114
- // A recorder is required to capture audio; startRecording /
115
- // startStreamingRecording only *detect* a recorder and throw when none
116
- // exists, so provision one here. Instant when sox/ffmpeg/arecord is on
117
- // PATH — only a first-run static-ffmpeg download actually blocks.
118
- await ensureRecorder(p => status(p.stage + (p.percent != null ? ` (${p.percent}%)` : "")));
119
109
  // Loading the multi-hundred-MB speech model into the worker is what made
120
110
  // the old "Checking STT dependencies…" step slow. Don't pay it before
121
111
  // recording: when the weights are already cached, start now and warm the
@@ -157,30 +147,15 @@ export class STTController {
157
147
 
158
148
  async #start(editor: Editor, options: ToggleOptions): Promise<void> {
159
149
  if (!(await this.#ensureDeps(options))) return;
160
- // Live transcription needs a recorder that can pipe PCM; the Windows
161
- // PowerShell mci fallback records to a file, so it stays single-shot.
162
- if (this.#recorderCanStream()) {
163
- await this.#startStreaming(editor, options);
164
- return;
165
- }
166
- await this.#startBatchRecording(options);
150
+ await this.#startStreaming(editor, options);
167
151
  }
168
152
 
169
- async #stop(editor: Editor, options: ToggleOptions): Promise<void> {
170
- if (this.#stream) {
171
- await this.#stopStreaming(options);
172
- return;
173
- }
174
- await this.#stopBatch(editor, options);
153
+ async #stop(options: ToggleOptions): Promise<void> {
154
+ await this.#stopStreaming(options);
175
155
  }
176
156
 
177
157
  // ── Live streaming ──────────────────────────────────────────────
178
158
 
179
- #recorderCanStream(): boolean {
180
- const recorder = detectRecorder();
181
- return recorder !== null && recorder.tool !== "powershell";
182
- }
183
-
184
159
  /** Segment text gets a leading space once a prior segment is committed, so
185
160
  * phrases join naturally; the first phrase is inserted at the cursor as-is. */
186
161
  #prefixed(text: string): string {
@@ -218,18 +193,38 @@ export class STTController {
218
193
  },
219
194
  });
220
195
  this.#stream = stream;
221
- let recorder: StreamingRecordingHandle | null = null;
196
+ let recorder: CaptureHandle;
222
197
  try {
223
- recorder = await startStreamingRecording(samples => stream.pushAudio(samples));
224
- } catch (err) {
225
- logger.warn("STT streaming recorder failed to start; falling back to batch recording", {
226
- error: err instanceof Error ? err.message : String(err),
198
+ recorder = this.#createCapture((error, samples) => {
199
+ if (this.#disposed || this.#stream !== stream || this.#state !== "recording") return;
200
+ if (error) {
201
+ logger.error("Native microphone capture failed", { error: error.message });
202
+ const activeRecorder = this.#streamRecorder;
203
+ this.#streamRecorder = null;
204
+ try {
205
+ activeRecorder?.stop();
206
+ } catch (cause) {
207
+ logger.debug("stt: microphone cleanup failed", {
208
+ error: cause instanceof Error ? cause.message : String(cause),
209
+ });
210
+ }
211
+ this.#streamAbort?.abort(error);
212
+ stream.cancel();
213
+ this.#streamEditor?.clearVolatileText();
214
+ options.requestRender?.();
215
+ this.#cleanupStream();
216
+ this.#setState("idle", options);
217
+ options.showWarning(error.message);
218
+ return;
219
+ }
220
+ stream.pushAudio(samples);
227
221
  });
228
- }
229
- if (!recorder) {
222
+ } catch (err) {
230
223
  stream.cancel();
231
224
  this.#cleanupStream();
232
- await this.#startBatchRecording(options);
225
+ const msg = err instanceof Error ? err.message : "Failed to start microphone capture";
226
+ options.showWarning(msg);
227
+ logger.error("STT recording failed to start", { error: msg });
233
228
  return;
234
229
  }
235
230
  this.#streamRecorder = recorder;
@@ -247,7 +242,7 @@ export class STTController {
247
242
  this.#setState("transcribing", options);
248
243
  // Stop the mic first so no further audio is fed, then flush the worker.
249
244
  try {
250
- await recorder?.stop();
245
+ recorder?.stop();
251
246
  } catch (err) {
252
247
  logger.debug("stt: streaming recorder stop failed", {
253
248
  error: err instanceof Error ? err.message : String(err),
@@ -306,103 +301,19 @@ export class STTController {
306
301
  this.#streamUtterance = "";
307
302
  }
308
303
 
309
- // ── Batch (single-shot) ─────────────────────────────────────────
310
-
311
- async #startBatchRecording(options: ToggleOptions): Promise<void> {
312
- const id = Snowflake.next();
313
- this.#tempFile = path.join(os.tmpdir(), `omp-stt-${id}.wav`);
314
- try {
315
- this.#recordingHandle = await startRecording(this.#tempFile);
316
- this.#setState("recording", options);
317
- logger.debug("STT recording started", { tempFile: this.#tempFile });
318
- } catch (err) {
319
- this.#tempFile = null;
320
- const msg = err instanceof Error ? err.message : "Failed to start recording";
321
- options.showWarning(msg);
322
- logger.error("STT recording failed to start", { error: msg });
323
- }
324
- }
325
-
326
- async #stopBatch(editor: Editor, options: ToggleOptions): Promise<void> {
327
- const handle = this.#recordingHandle;
328
- const tempFile = this.#tempFile;
329
- this.#recordingHandle = null;
330
-
331
- if (!handle || !tempFile) {
332
- this.#setState("idle", options);
333
- return;
334
- }
335
-
336
- try {
337
- await handle.stop();
338
- // Validate the recording produced a usable file
339
- await verifyRecordingFile(tempFile);
340
- this.#setState("transcribing", options);
341
-
342
- const sttSettings = {
343
- modelName: settings.get("stt.modelName") as string | undefined,
344
- language: settings.get("stt.language") as string | undefined,
345
- };
346
- this.#transcriptionAbort = new AbortController();
347
- const text = await transcribe(tempFile, { ...sttSettings, signal: this.#transcriptionAbort.signal });
348
- this.#transcriptionAbort = null;
349
- if (this.#disposed) return;
350
- if (text.length > 0) {
351
- const trigger = settings.get("stt.submitTrigger");
352
- const { submit, trimTrailing } = evaluateSubmitTrigger(text, trigger);
353
- const textToInsert = trimTrailing > 0 ? text.slice(0, -trimTrailing) : text;
354
- if (textToInsert.length > 0) {
355
- editor.insertText(textToInsert);
356
- }
357
- options.showStatus("");
358
- if (submit) {
359
- editor.submit();
360
- }
361
- } else {
362
- options.showStatus("No speech detected.");
363
- }
364
- if (!this.#disposed) this.#setState("idle", options);
365
- } catch (err) {
366
- if (this.#disposed) return;
367
- if (err instanceof DOMException && err.name === "AbortError") {
368
- this.#setState("idle", options);
369
- return;
370
- }
371
- const msg = err instanceof Error ? err.message : "Transcription failed";
372
- options.showWarning(msg);
373
- logger.error("STT transcription failed", { error: msg });
374
- this.#setState("idle", options);
375
- } finally {
376
- try {
377
- await fs.rm(tempFile, { force: true });
378
- } catch {
379
- // best effort cleanup
380
- }
381
- this.#tempFile = null;
382
- }
383
- }
384
-
385
304
  dispose(): void {
386
305
  this.#disposed = true;
387
- if (this.#transcriptionAbort) {
388
- this.#transcriptionAbort.abort();
389
- this.#transcriptionAbort = null;
390
- }
391
306
  if (this.#streamAbort) {
392
307
  this.#streamAbort.abort();
393
308
  this.#streamAbort = null;
394
309
  }
395
310
  this.#stream?.cancel();
396
- this.#streamRecorder?.stop().catch(() => {});
397
- this.#cleanupStream();
398
- if (this.#recordingHandle) {
399
- this.#recordingHandle.stop().catch(() => {});
400
- this.#recordingHandle = null;
401
- }
402
- if (this.#tempFile) {
403
- fs.rm(this.#tempFile, { force: true }).catch(() => {});
404
- this.#tempFile = null;
311
+ try {
312
+ this.#streamRecorder?.stop();
313
+ } catch {
314
+ // best effort cleanup
405
315
  }
316
+ this.#cleanupStream();
406
317
  this.#state = "idle";
407
318
  this.#resolvedModelKey = null;
408
319
  }
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import * as os from "node:os";
6
+ import * as path from "node:path";
6
7
  import type { AgentTool } from "@oh-my-pi/pi-agent-core";
7
8
  import type { ToolExample, TSchema } from "@oh-my-pi/pi-ai";
8
9
  import { renderToolInventory } from "@oh-my-pi/pi-ai/dialect";
@@ -16,6 +17,7 @@ import { expandAtImports } from "./discovery/at-imports";
16
17
  import { loadSkills, type Skill } from "./extensibility/skills";
17
18
  import { hasObsidian } from "./internal-urls/vault-protocol";
18
19
  import activeRepoContextTemplate from "./prompts/system/active-repo-context.md" with { type: "text" };
20
+ import computerSafetyPrompt from "./prompts/system/computer-safety.md" with { type: "text" };
19
21
  import customSystemPromptTemplate from "./prompts/system/custom-system-prompt.md" with { type: "text" };
20
22
  import defaultPersonality from "./prompts/system/personalities/default.md" with { type: "text" };
21
23
  import friendlyPersonality from "./prompts/system/personalities/friendly.md" with { type: "text" };
@@ -466,6 +468,8 @@ export interface BuildSystemPromptOptions {
466
468
  skillsSettings?: SkillsSettings;
467
469
  /** Working directory. Default: getProjectDir() */
468
470
  cwd?: string;
471
+ /** Additional workspace directories beyond cwd (multi-root), absolute. Injected into the project prompt. */
472
+ additionalWorkspaceRoots?: string[];
469
473
  /** Pre-loaded context files (skips discovery if provided). */
470
474
  contextFiles?: Array<{ path: string; content: string; depth?: number }>;
471
475
  /** Skills provided directly to system prompt construction. */
@@ -535,6 +539,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
535
539
  skillsSettings,
536
540
  toolNames: providedToolNames,
537
541
  cwd,
542
+ additionalWorkspaceRoots = [],
538
543
  contextFiles: providedContextFiles,
539
544
  skills: providedSkills,
540
545
  rules,
@@ -619,23 +624,42 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
619
624
  const systemPromptCustomizationPromise: Promise<string | null> = callerControlsCustomPrompt
620
625
  ? Promise.resolve(null)
621
626
  : logger.time("loadSystemPromptFiles", loadSystemPromptFiles, { cwd: resolvedCwd });
622
- const contextFilesPromise = providedContextFiles
623
- ? Promise.resolve(providedContextFiles)
624
- : logger.time("loadProjectContextFiles", loadProjectContextFiles, { cwd: resolvedCwd });
625
- const workspaceTreePromise =
626
- providedWorkspaceTree !== undefined
627
- ? Promise.resolve(providedWorkspaceTree)
628
- : includeWorkspaceTree
629
- ? logger.time("buildWorkspaceTree", () =>
630
- buildWorkspaceTree(resolvedCwd, { timeoutMs: SYSTEM_PROMPT_PREP_TIMEOUT_MS }),
631
- )
632
- : Promise.resolve({
633
- rootPath: resolvedCwd,
634
- rendered: "",
635
- truncated: false,
636
- totalLines: 0,
637
- agentsMdFiles: [],
638
- });
627
+ const contextFilesPromise = (async () => {
628
+ const primary = providedContextFiles
629
+ ? providedContextFiles
630
+ : await logger.time("loadProjectContextFiles", loadProjectContextFiles, { cwd: resolvedCwd });
631
+ // Also discover context files (AGENTS.md, rules, etc.) for each additional workspace root.
632
+ const additionalRoots = additionalWorkspaceRoots.filter(d => path.resolve(d) !== path.resolve(resolvedCwd));
633
+ if (additionalRoots.length === 0) return primary;
634
+ const extra = await Promise.all(
635
+ additionalRoots.map(root => loadProjectContextFiles({ cwd: root }).catch(() => [])),
636
+ );
637
+ return dedupeExactContextFiles([...primary, ...extra.flat()]);
638
+ })();
639
+ const additionalRootsForTree = additionalWorkspaceRoots.filter(d => path.resolve(d) !== path.resolve(resolvedCwd));
640
+ const workspaceTreePromise = (async () => {
641
+ const primary =
642
+ providedWorkspaceTree !== undefined
643
+ ? await Promise.resolve(providedWorkspaceTree)
644
+ : includeWorkspaceTree
645
+ ? await logger.time("buildWorkspaceTree", () =>
646
+ buildWorkspaceTree(resolvedCwd, { timeoutMs: SYSTEM_PROMPT_PREP_TIMEOUT_MS }),
647
+ )
648
+ : { rootPath: resolvedCwd, rendered: "", truncated: false, totalLines: 0, agentsMdFiles: [] };
649
+ if (additionalRootsForTree.length === 0 || !includeWorkspaceTree) return primary;
650
+ const extraTrees = await Promise.all(
651
+ additionalRootsForTree.map(root =>
652
+ buildWorkspaceTree(root, { timeoutMs: SYSTEM_PROMPT_PREP_TIMEOUT_MS }).catch(() => ({
653
+ rootPath: root,
654
+ rendered: "",
655
+ truncated: false,
656
+ totalLines: 0,
657
+ agentsMdFiles: [],
658
+ })),
659
+ ),
660
+ );
661
+ return { ...primary, agentsMdFiles: [...primary.agentsMdFiles, ...extraTrees.flatMap(t => t.agentsMdFiles)] };
662
+ })();
639
663
  const skillsPromise: Promise<readonly Skill[]> =
640
664
  providedSkills !== undefined
641
665
  ? Promise.resolve(providedSkills)
@@ -794,6 +818,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
794
818
  date,
795
819
  dateTime,
796
820
  cwd: promptCwd,
821
+ additionalWorkspaceRoots: additionalWorkspaceRoots.filter(d => path.resolve(d) !== path.resolve(resolvedCwd)),
797
822
  model: includeModelInPrompt ? (model ?? "") : "",
798
823
  useCodexTaskPrompt: usesCodexTaskPrompt(model),
799
824
  personality: personality === "none" ? "" : PERSONALITY_SPECS[personality].trim(),
@@ -815,6 +840,9 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
815
840
  };
816
841
  const rendered = prompt.render(resolvedCustomPrompt ? customSystemPromptTemplate : systemPromptTemplate, data);
817
842
  const systemPrompt = [rendered];
843
+ if (toolNames.includes("computer")) {
844
+ systemPrompt.push(computerSafetyPrompt.trim());
845
+ }
818
846
  // Custom prompt templates already render context files and append text; the
819
847
  // project footer still carries environment, cwd, workspace, and dir-context.
820
848
  const projectPrompt = prompt