lsd-pi 1.2.4 → 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.
Files changed (357) hide show
  1. package/README.md +22 -16
  2. package/dist/app-paths.d.ts +4 -0
  3. package/dist/app-paths.js +4 -0
  4. package/dist/bedrock-auth.d.ts +4 -0
  5. package/dist/bedrock-auth.js +4 -0
  6. package/dist/bundled-extension-paths.d.ts +4 -0
  7. package/dist/bundled-extension-paths.js +4 -0
  8. package/dist/cli-theme.d.ts +2 -2
  9. package/dist/cli-theme.js +13 -14
  10. package/dist/cli.js +43 -3
  11. package/dist/codex-rotate-settings.d.ts +4 -0
  12. package/dist/codex-rotate-settings.js +4 -0
  13. package/dist/help-text.d.ts +4 -0
  14. package/dist/help-text.js +4 -0
  15. package/dist/lsd-brand.d.ts +4 -0
  16. package/dist/lsd-brand.js +4 -0
  17. package/dist/onboarding-llm.d.ts +5 -0
  18. package/dist/onboarding-llm.js +5 -0
  19. package/dist/project-sessions.d.ts +4 -0
  20. package/dist/project-sessions.js +4 -0
  21. package/dist/resources/agents/generic.md +1 -0
  22. package/dist/resources/agents/scout.md +8 -1
  23. package/dist/resources/agents/worker.md +1 -0
  24. package/dist/resources/extensions/ask-user-questions.js +70 -0
  25. package/dist/resources/extensions/bg-shell/bg-shell-tool.js +6 -16
  26. package/dist/resources/extensions/browser-tools/tools/codegen.js +5 -5
  27. package/dist/resources/extensions/browser-tools/tools/navigation.js +107 -178
  28. package/dist/resources/extensions/browser-tools/tools/network-mock.js +112 -167
  29. package/dist/resources/extensions/browser-tools/tools/pages.js +182 -234
  30. package/dist/resources/extensions/browser-tools/tools/refs.js +202 -461
  31. package/dist/resources/extensions/browser-tools/tools/session.js +176 -323
  32. package/dist/resources/extensions/browser-tools/tools/state-persistence.js +91 -154
  33. package/dist/resources/extensions/browser-tools/utils.js +1 -1
  34. package/dist/resources/extensions/mac-tools/index.js +19 -34
  35. package/dist/resources/extensions/memory/index.js +20 -2
  36. package/dist/resources/extensions/shared/interview-ui.js +103 -20
  37. package/dist/resources/extensions/slash-commands/extension-manifest.json +2 -2
  38. package/dist/resources/extensions/slash-commands/fast.js +73 -0
  39. package/dist/resources/extensions/slash-commands/index.js +2 -0
  40. package/dist/resources/extensions/slash-commands/plan.js +54 -28
  41. package/dist/resources/extensions/slash-commands/tools.js +40 -4
  42. package/dist/resources/extensions/subagent/agent-switcher-component.js +208 -0
  43. package/dist/resources/extensions/subagent/agent-switcher-model.js +107 -0
  44. package/dist/resources/extensions/subagent/background-job-manager.js +24 -6
  45. package/dist/resources/extensions/subagent/background-runner.js +4 -0
  46. package/dist/resources/extensions/subagent/in-process-runner.js +387 -0
  47. package/dist/resources/extensions/subagent/index.js +715 -370
  48. package/dist/resources/extensions/subagent/launch-helpers.js +19 -5
  49. package/dist/resources/extensions/subagent/legacy-runner.js +503 -0
  50. package/dist/resources/extensions/voice/index.js +96 -36
  51. package/dist/resources/extensions/voice/push-to-talk.js +26 -0
  52. package/dist/shared-paths.d.ts +4 -0
  53. package/dist/shared-paths.js +4 -0
  54. package/dist/shared-preferences.d.ts +4 -0
  55. package/dist/shared-preferences.js +4 -0
  56. package/dist/startup-model-validation.d.ts +1 -1
  57. package/dist/startup-timings.d.ts +4 -0
  58. package/dist/startup-timings.js +4 -0
  59. package/dist/update-check.d.ts +4 -0
  60. package/dist/update-check.js +4 -0
  61. package/dist/update-cmd.d.ts +4 -0
  62. package/dist/update-cmd.js +4 -0
  63. package/dist/welcome-screen.js +4 -4
  64. package/dist/wizard.d.ts +4 -0
  65. package/dist/wizard.js +4 -0
  66. package/package.json +1 -1
  67. package/packages/pi-agent-core/dist/agent.d.ts +28 -0
  68. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  69. package/packages/pi-agent-core/dist/agent.js +105 -5
  70. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  71. package/packages/pi-agent-core/dist/types.d.ts +13 -2
  72. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  73. package/packages/pi-agent-core/dist/types.js.map +1 -1
  74. package/packages/pi-agent-core/src/agent.ts +142 -6
  75. package/packages/pi-agent-core/src/types.ts +12 -3
  76. package/packages/pi-ai/dist/adaptive/classifier.d.ts +29 -0
  77. package/packages/pi-ai/dist/adaptive/classifier.d.ts.map +1 -0
  78. package/packages/pi-ai/dist/adaptive/classifier.js +72 -0
  79. package/packages/pi-ai/dist/adaptive/classifier.js.map +1 -0
  80. package/packages/pi-ai/dist/adaptive/classifier.test.d.ts +2 -0
  81. package/packages/pi-ai/dist/adaptive/classifier.test.d.ts.map +1 -0
  82. package/packages/pi-ai/dist/adaptive/classifier.test.js +32 -0
  83. package/packages/pi-ai/dist/adaptive/classifier.test.js.map +1 -0
  84. package/packages/pi-ai/dist/index.d.ts +1 -0
  85. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  86. package/packages/pi-ai/dist/index.js +1 -0
  87. package/packages/pi-ai/dist/index.js.map +1 -1
  88. package/packages/pi-ai/dist/providers/amazon-bedrock.js +0 -2
  89. package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
  90. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  91. package/packages/pi-ai/dist/providers/anthropic-shared.js +0 -2
  92. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  93. package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts +1 -1
  94. package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
  95. package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
  96. package/packages/pi-ai/dist/providers/google-gemini-cli.d.ts.map +1 -1
  97. package/packages/pi-ai/dist/providers/google-gemini-cli.js +0 -4
  98. package/packages/pi-ai/dist/providers/google-gemini-cli.js.map +1 -1
  99. package/packages/pi-ai/dist/providers/google-vertex.js +0 -5
  100. package/packages/pi-ai/dist/providers/google-vertex.js.map +1 -1
  101. package/packages/pi-ai/dist/providers/google.js +0 -5
  102. package/packages/pi-ai/dist/providers/google.js.map +1 -1
  103. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts +35 -2
  104. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
  105. package/packages/pi-ai/dist/providers/openai-codex-responses.js +32 -6
  106. package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  107. package/packages/pi-ai/dist/providers/openai-codex-responses.test.js +127 -16
  108. package/packages/pi-ai/dist/providers/openai-codex-responses.test.js.map +1 -1
  109. package/packages/pi-ai/dist/providers/openai-completions.d.ts +1 -1
  110. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  111. package/packages/pi-ai/dist/providers/openai-completions.js +0 -1
  112. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  113. package/packages/pi-ai/dist/providers/openai-responses.d.ts +9 -2
  114. package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  115. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.d.ts +2 -0
  116. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.d.ts.map +1 -0
  117. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.js +67 -0
  118. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.js.map +1 -0
  119. package/packages/pi-ai/dist/providers/openai-responses.js +21 -3
  120. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  121. package/packages/pi-ai/dist/providers/openai-shared.d.ts +0 -1
  122. package/packages/pi-ai/dist/providers/openai-shared.d.ts.map +1 -1
  123. package/packages/pi-ai/dist/providers/openai-shared.js +0 -4
  124. package/packages/pi-ai/dist/providers/openai-shared.js.map +1 -1
  125. package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  126. package/packages/pi-ai/dist/providers/simple-options.js +2 -1
  127. package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
  128. package/packages/pi-ai/dist/types.d.ts +6 -2
  129. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  130. package/packages/pi-ai/dist/types.js.map +1 -1
  131. package/packages/pi-ai/src/adaptive/classifier.test.ts +38 -0
  132. package/packages/pi-ai/src/adaptive/classifier.ts +107 -0
  133. package/packages/pi-ai/src/index.ts +1 -0
  134. package/packages/pi-ai/src/providers/amazon-bedrock.ts +0 -2
  135. package/packages/pi-ai/src/providers/anthropic-shared.ts +0 -2
  136. package/packages/pi-ai/src/providers/azure-openai-responses.ts +1 -1
  137. package/packages/pi-ai/src/providers/google-gemini-cli.ts +0 -4
  138. package/packages/pi-ai/src/providers/google-vertex.ts +0 -5
  139. package/packages/pi-ai/src/providers/google.ts +0 -5
  140. package/packages/pi-ai/src/providers/openai-codex-responses.test.ts +143 -20
  141. package/packages/pi-ai/src/providers/openai-codex-responses.ts +48 -7
  142. package/packages/pi-ai/src/providers/openai-completions.ts +1 -2
  143. package/packages/pi-ai/src/providers/openai-responses.fast-mode.test.ts +73 -0
  144. package/packages/pi-ai/src/providers/openai-responses.ts +27 -4
  145. package/packages/pi-ai/src/providers/openai-shared.ts +0 -3
  146. package/packages/pi-ai/src/providers/simple-options.ts +2 -1
  147. package/packages/pi-ai/src/types.ts +6 -2
  148. package/packages/pi-coding-agent/dist/cli/args.js +2 -2
  149. package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
  150. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +7 -2
  151. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  152. package/packages/pi-coding-agent/dist/core/agent-session.js +53 -20
  153. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  154. package/packages/pi-coding-agent/dist/core/keybindings.d.ts +1 -1
  155. package/packages/pi-coding-agent/dist/core/keybindings.d.ts.map +1 -1
  156. package/packages/pi-coding-agent/dist/core/keybindings.js +2 -0
  157. package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
  158. package/packages/pi-coding-agent/dist/core/lsp/lsp.md +3 -1
  159. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  160. package/packages/pi-coding-agent/dist/core/sdk.js +36 -8
  161. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  162. package/packages/pi-coding-agent/dist/core/sdk.test.js +37 -0
  163. package/packages/pi-coding-agent/dist/core/sdk.test.js.map +1 -1
  164. package/packages/pi-coding-agent/dist/core/session-manager.d.ts +8 -0
  165. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  166. package/packages/pi-coding-agent/dist/core/session-manager.js +4 -0
  167. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  168. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +18 -7
  169. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  170. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.d.ts +2 -0
  171. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.d.ts.map +1 -0
  172. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.js +35 -0
  173. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.js.map +1 -0
  174. package/packages/pi-coding-agent/dist/core/settings-manager.js +32 -2
  175. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  176. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  177. package/packages/pi-coding-agent/dist/core/skills.js +4 -1
  178. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  179. package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
  180. package/packages/pi-coding-agent/dist/core/slash-commands.js +2 -1
  181. package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
  182. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  183. package/packages/pi-coding-agent/dist/core/system-prompt.js +12 -3
  184. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  185. package/packages/pi-coding-agent/dist/core/tool-priority.d.ts +4 -0
  186. package/packages/pi-coding-agent/dist/core/tool-priority.d.ts.map +1 -0
  187. package/packages/pi-coding-agent/dist/core/tool-priority.js +18 -0
  188. package/packages/pi-coding-agent/dist/core/tool-priority.js.map +1 -0
  189. package/packages/pi-coding-agent/dist/core/tool-priority.test.d.ts +2 -0
  190. package/packages/pi-coding-agent/dist/core/tool-priority.test.d.ts.map +1 -0
  191. package/packages/pi-coding-agent/dist/core/tool-priority.test.js +27 -0
  192. package/packages/pi-coding-agent/dist/core/tool-priority.test.js.map +1 -0
  193. package/packages/pi-coding-agent/dist/core/tools/grep.js +1 -1
  194. package/packages/pi-coding-agent/dist/core/tools/grep.js.map +1 -1
  195. package/packages/pi-coding-agent/dist/core/tools/index.d.ts +2 -0
  196. package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
  197. package/packages/pi-coding-agent/dist/core/tools/index.js +2 -0
  198. package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
  199. package/packages/pi-coding-agent/dist/core/tools/pty.d.ts +10 -1
  200. package/packages/pi-coding-agent/dist/core/tools/pty.d.ts.map +1 -1
  201. package/packages/pi-coding-agent/dist/core/tools/pty.js +29 -3
  202. package/packages/pi-coding-agent/dist/core/tools/pty.js.map +1 -1
  203. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.d.ts +2 -0
  204. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.d.ts.map +1 -0
  205. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js +26 -0
  206. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js.map +1 -0
  207. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.d.ts +45 -0
  208. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.d.ts.map +1 -0
  209. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.js +314 -0
  210. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.js.map +1 -0
  211. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.d.ts +2 -0
  212. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.d.ts.map +1 -0
  213. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.js +122 -0
  214. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.js.map +1 -0
  215. package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.js +1 -1
  216. package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.js.map +1 -1
  217. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts +2 -0
  218. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  219. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +19 -2
  220. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  221. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +11 -2
  222. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  223. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +49 -6
  224. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  225. package/packages/pi-coding-agent/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  226. package/packages/pi-coding-agent/dist/modes/interactive/components/thinking-selector.js +1 -2
  227. package/packages/pi-coding-agent/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  228. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +6 -0
  229. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  230. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +18 -4
  231. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  232. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts +13 -0
  233. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts.map +1 -0
  234. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js +49 -0
  235. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js.map +1 -0
  236. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.d.ts +2 -0
  237. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.d.ts.map +1 -0
  238. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js +197 -0
  239. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js.map +1 -0
  240. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  241. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +106 -0
  242. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  243. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  244. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +7 -0
  245. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  246. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +3 -0
  247. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  248. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  249. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
  250. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  251. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +88 -2
  252. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  253. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
  254. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +41 -0
  255. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  256. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts +2 -2
  257. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  258. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +10 -6
  259. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
  260. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +1 -1
  261. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  262. package/packages/pi-coding-agent/dist/modes/print-mode.d.ts.map +1 -1
  263. package/packages/pi-coding-agent/dist/modes/print-mode.js +6 -0
  264. package/packages/pi-coding-agent/dist/modes/print-mode.js.map +1 -1
  265. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  266. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +20 -0
  267. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  268. package/packages/pi-coding-agent/dist/tests/path-display.test.js +15 -0
  269. package/packages/pi-coding-agent/dist/tests/path-display.test.js.map +1 -1
  270. package/packages/pi-coding-agent/package.json +1 -1
  271. package/packages/pi-coding-agent/src/cli/args.ts +2 -2
  272. package/packages/pi-coding-agent/src/core/agent-session.ts +58 -21
  273. package/packages/pi-coding-agent/src/core/keybindings.ts +4 -1
  274. package/packages/pi-coding-agent/src/core/lsp/lsp.md +3 -1
  275. package/packages/pi-coding-agent/src/core/sdk.test.ts +45 -0
  276. package/packages/pi-coding-agent/src/core/sdk.ts +39 -8
  277. package/packages/pi-coding-agent/src/core/session-manager.ts +12 -0
  278. package/packages/pi-coding-agent/src/core/settings-manager.fast-mode.test.ts +46 -0
  279. package/packages/pi-coding-agent/src/core/settings-manager.ts +50 -9
  280. package/packages/pi-coding-agent/src/core/skills.ts +4 -1
  281. package/packages/pi-coding-agent/src/core/slash-commands.ts +2 -1
  282. package/packages/pi-coding-agent/src/core/system-prompt.ts +14 -3
  283. package/packages/pi-coding-agent/src/core/tool-priority.test.ts +30 -0
  284. package/packages/pi-coding-agent/src/core/tool-priority.ts +17 -0
  285. package/packages/pi-coding-agent/src/core/tools/grep.ts +1 -1
  286. package/packages/pi-coding-agent/src/core/tools/index.ts +3 -0
  287. package/packages/pi-coding-agent/src/core/tools/pty.ts +45 -6
  288. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-summary-line.test.ts +31 -0
  289. package/packages/pi-coding-agent/src/modes/interactive/components/btw-overlay.test.ts +172 -0
  290. package/packages/pi-coding-agent/src/modes/interactive/components/btw-overlay.ts +402 -0
  291. package/packages/pi-coding-agent/src/modes/interactive/components/embedded-terminal.ts +1 -1
  292. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +18 -2
  293. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +63 -9
  294. package/packages/pi-coding-agent/src/modes/interactive/components/thinking-selector.ts +1 -2
  295. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +1154 -1136
  296. package/packages/pi-coding-agent/src/modes/interactive/components/tool-summary-line.ts +64 -0
  297. package/packages/pi-coding-agent/src/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.ts +228 -0
  298. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +494 -389
  299. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +7 -0
  300. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +3 -0
  301. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +103 -3
  302. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +60 -1
  303. package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +11 -7
  304. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +1 -1
  305. package/packages/pi-coding-agent/src/modes/print-mode.ts +6 -0
  306. package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +29 -0
  307. package/packages/pi-coding-agent/src/tests/path-display.test.ts +17 -0
  308. package/packages/pi-tui/dist/components/loader.d.ts +5 -2
  309. package/packages/pi-tui/dist/components/loader.d.ts.map +1 -1
  310. package/packages/pi-tui/dist/components/loader.js +33 -3
  311. package/packages/pi-tui/dist/components/loader.js.map +1 -1
  312. package/packages/pi-tui/src/components/loader.ts +31 -3
  313. package/packages/rpc-client/src/index.ts +1 -1
  314. package/packages/rpc-client/src/rpc-client.ts +29 -0
  315. package/packages/rpc-client/src/rpc-types.ts +1 -1
  316. package/pkg/dist/modes/interactive/theme/theme.d.ts +2 -2
  317. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  318. package/pkg/dist/modes/interactive/theme/theme.js +10 -6
  319. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
  320. package/pkg/dist/modes/interactive/theme/themes.js +1 -1
  321. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  322. package/pkg/package.json +1 -1
  323. package/src/resources/agents/generic.md +1 -0
  324. package/src/resources/agents/scout.md +8 -1
  325. package/src/resources/agents/worker.md +1 -0
  326. package/src/resources/extensions/ask-user-questions.ts +88 -0
  327. package/src/resources/extensions/bg-shell/bg-shell-tool.ts +6 -16
  328. package/src/resources/extensions/browser-tools/tools/codegen.ts +5 -5
  329. package/src/resources/extensions/browser-tools/tools/navigation.ts +118 -196
  330. package/src/resources/extensions/browser-tools/tools/network-mock.ts +114 -205
  331. package/src/resources/extensions/browser-tools/tools/pages.ts +183 -237
  332. package/src/resources/extensions/browser-tools/tools/refs.ts +193 -507
  333. package/src/resources/extensions/browser-tools/tools/session.ts +182 -321
  334. package/src/resources/extensions/browser-tools/tools/state-persistence.ts +94 -172
  335. package/src/resources/extensions/browser-tools/utils.ts +1 -1
  336. package/src/resources/extensions/mac-tools/index.ts +19 -34
  337. package/src/resources/extensions/memory/index.ts +22 -2
  338. package/src/resources/extensions/shared/interview-ui.ts +108 -15
  339. package/src/resources/extensions/shared/tests/ask-user-freetext.test.ts +61 -0
  340. package/src/resources/extensions/shared/tests/custom-ui-fallbacks.test.ts +46 -0
  341. package/src/resources/extensions/slash-commands/extension-manifest.json +2 -2
  342. package/src/resources/extensions/slash-commands/fast.ts +89 -0
  343. package/src/resources/extensions/slash-commands/index.ts +2 -0
  344. package/src/resources/extensions/slash-commands/plan.ts +59 -30
  345. package/src/resources/extensions/slash-commands/tools.ts +43 -4
  346. package/src/resources/extensions/subagent/agent-switcher-component.ts +228 -0
  347. package/src/resources/extensions/subagent/agent-switcher-model.ts +160 -0
  348. package/src/resources/extensions/subagent/background-job-manager.ts +57 -6
  349. package/src/resources/extensions/subagent/background-runner.ts +8 -0
  350. package/src/resources/extensions/subagent/background-types.ts +4 -0
  351. package/src/resources/extensions/subagent/in-process-runner.ts +534 -0
  352. package/src/resources/extensions/subagent/index.ts +998 -493
  353. package/src/resources/extensions/subagent/launch-helpers.ts +15 -4
  354. package/src/resources/extensions/subagent/legacy-runner.ts +607 -0
  355. package/src/resources/extensions/voice/index.ts +308 -238
  356. package/src/resources/extensions/voice/push-to-talk.ts +42 -0
  357. package/src/resources/extensions/voice/tests/push-to-talk.test.ts +109 -0
@@ -1,12 +1,13 @@
1
1
  import type { ExtensionAPI, ExtensionContext } from "@gsd/pi-coding-agent";
2
2
  import { shortcutDesc } from "../shared/mod.js";
3
3
  import type { AssistantMessage } from "@gsd/pi-ai";
4
- import { isKeyRelease, Key, matchesKey, truncateToWidth, visibleWidth } from "@gsd/pi-tui";
4
+ import { isKeyRelease, isKittyProtocolActive, Key, matchesKey, truncateToWidth, visibleWidth } from "@gsd/pi-tui";
5
5
  import { spawn, execFileSync, type ChildProcess } from "node:child_process";
6
6
  import * as fs from "node:fs";
7
7
  import * as path from "node:path";
8
8
  import * as readline from "node:readline";
9
- import { linuxPython, diagnoseSounddeviceError, ensureVoiceVenv, VOICE_VENV_PYTHON } from "./linux-ready.js";
9
+ import { linuxPython, diagnoseSounddeviceError, ensureVoiceVenv } from "./linux-ready.js";
10
+ import { handlePushToTalkInput, type VoiceActivationMode } from "./push-to-talk.js";
10
11
 
11
12
  const __extensionDir = import.meta.dirname!;
12
13
  const SWIFT_SRC = path.join(__extensionDir, "speech-recognizer.swift");
@@ -17,247 +18,316 @@ const IS_DARWIN = process.platform === "darwin";
17
18
  const IS_LINUX = process.platform === "linux";
18
19
 
19
20
  function ensureBinary(): boolean {
20
- if (fs.existsSync(RECOGNIZER_BIN)) return true;
21
- try {
22
- execFileSync("swiftc", [SWIFT_SRC, "-o", RECOGNIZER_BIN, "-framework", "Speech", "-framework", "AVFoundation"], {
23
- timeout: 60000,
24
- });
25
- return true;
26
- } catch {
27
- return false;
28
- }
21
+ if (fs.existsSync(RECOGNIZER_BIN)) return true;
22
+ try {
23
+ execFileSync("swiftc", [SWIFT_SRC, "-o", RECOGNIZER_BIN, "-framework", "Speech", "-framework", "AVFoundation"], {
24
+ timeout: 60000,
25
+ });
26
+ return true;
27
+ } catch {
28
+ return false;
29
+ }
29
30
  }
30
31
 
31
32
  let linuxReady = false;
32
33
 
33
34
  function ensureLinuxReady(ctx: ExtensionContext): boolean {
34
- if (linuxReady) return true;
35
-
36
- // Check GROQ_API_KEY is available
37
- if (!process.env.GROQ_API_KEY) {
38
- ctx.ui.notify("Voice: GROQ_API_KEY not set — run 'gsd config' to configure", "error");
39
- return false;
40
- }
41
-
42
- // Check python3 exists
43
- try {
44
- execFileSync("which", ["python3"], { stdio: "pipe" });
45
- } catch {
46
- ctx.ui.notify("Voice: python3 not found — install with: sudo apt install python3", "error");
47
- return false;
48
- }
49
-
50
- // Check that sounddevice is importable
51
- const py = linuxPython();
52
- try {
53
- execFileSync(py, ["-c", "import sounddevice"], {
54
- stdio: "pipe",
55
- timeout: 10000,
56
- });
57
- } catch (err: unknown) {
58
- const stderr = (err as { stderr?: Buffer })?.stderr?.toString() ?? "";
59
- const diagnosis = diagnoseSounddeviceError(stderr);
60
-
61
- if (diagnosis === "missing-module") {
62
- // Module not installed — auto-create venv (handles PEP 668 systems
63
- // where system pip is blocked). See #2403.
64
- if (!ensureVoiceVenv({ notify: (msg, level) => ctx.ui.notify(msg, level) })) {
65
- return false;
66
- }
67
- linuxReady = true;
68
- return true;
69
- } else if (diagnosis === "missing-portaudio") {
70
- ctx.ui.notify("Voice: install libportaudio2 with: sudo apt install libportaudio2", "error");
71
- } else {
72
- ctx.ui.notify(`Voice: dependency check failed — ${stderr.split("\n")[0] || "unknown error"}`, "error");
73
- }
74
- return false;
75
- }
76
-
77
- linuxReady = true;
78
- return true;
35
+ if (linuxReady) return true;
36
+
37
+ // Check GROQ_API_KEY is available
38
+ if (!process.env.GROQ_API_KEY) {
39
+ ctx.ui.notify("Voice: GROQ_API_KEY not set — run 'gsd config' to configure", "error");
40
+ return false;
41
+ }
42
+
43
+ // Check python3 exists
44
+ try {
45
+ execFileSync("which", ["python3"], { stdio: "pipe" });
46
+ } catch {
47
+ ctx.ui.notify("Voice: python3 not found — install with: sudo apt install python3", "error");
48
+ return false;
49
+ }
50
+
51
+ // Check that sounddevice is importable
52
+ const py = linuxPython();
53
+ try {
54
+ execFileSync(py, ["-c", "import sounddevice"], {
55
+ stdio: "pipe",
56
+ timeout: 10000,
57
+ });
58
+ } catch (err: unknown) {
59
+ const stderr = (err as { stderr?: Buffer })?.stderr?.toString() ?? "";
60
+ const diagnosis = diagnoseSounddeviceError(stderr);
61
+
62
+ if (diagnosis === "missing-module") {
63
+ // Module not installed — auto-create venv (handles PEP 668 systems
64
+ // where system pip is blocked). See #2403.
65
+ if (!ensureVoiceVenv({ notify: (msg, level) => ctx.ui.notify(msg, level) })) {
66
+ return false;
67
+ }
68
+ linuxReady = true;
69
+ return true;
70
+ } else if (diagnosis === "missing-portaudio") {
71
+ ctx.ui.notify("Voice: install libportaudio2 with: sudo apt install libportaudio2", "error");
72
+ } else {
73
+ ctx.ui.notify(`Voice: dependency check failed — ${stderr.split("\n")[0] || "unknown error"}`, "error");
74
+ }
75
+ return false;
76
+ }
77
+
78
+ linuxReady = true;
79
+ return true;
79
80
  }
80
81
 
81
- export default function (pi: ExtensionAPI) {
82
- if (!IS_DARWIN && !IS_LINUX) return;
83
-
84
- let active = false;
85
- let recognizerProcess: ChildProcess | null = null;
86
- let flashOn = true;
87
- let flashTimer: ReturnType<typeof setInterval> | null = null;
88
- let footerTui: { requestRender: () => void } | null = null;
89
-
90
- function setVoiceFooter(ctx: ExtensionContext, on: boolean) {
91
- if (!on) {
92
- stopFlash();
93
- ctx.ui.setFooter(undefined);
94
- return;
95
- }
96
-
97
- flashOn = true;
98
- flashTimer = setInterval(() => {
99
- flashOn = !flashOn;
100
- footerTui?.requestRender();
101
- }, 500);
102
-
103
- ctx.ui.setFooter((tui, theme, footerData) => {
104
- footerTui = tui;
105
- const branchUnsub = footerData.onBranchChange(() => tui.requestRender());
106
-
107
- return {
108
- dispose: branchUnsub,
109
- invalidate() {},
110
- render(width: number): string[] {
111
- // Row 1: pwd (branch) ... ● transcribing
112
- let pwd = process.cwd();
113
- const home = process.env.HOME || process.env.USERPROFILE;
114
- if (home && pwd.startsWith(home)) pwd = `~${pwd.slice(home.length)}`;
115
- const branch = footerData.getGitBranch();
116
- if (branch) pwd = `${pwd} (${branch})`;
117
-
118
- const dot = flashOn ? theme.fg("error", "●") : theme.fg("dim", "●");
119
- const voiceTag = `${dot} ${theme.fg("error", "transcribing")}`;
120
- const voiceTagWidth = visibleWidth(voiceTag);
121
-
122
- const maxPwdWidth = width - voiceTagWidth - 2;
123
- const pwdStr = truncateToWidth(theme.fg("dim", pwd), maxPwdWidth, theme.fg("dim", "..."));
124
- const pad1 = " ".repeat(Math.max(1, width - visibleWidth(pwdStr) - voiceTagWidth));
125
- const row1 = truncateToWidth(pwdStr + pad1 + voiceTag, width);
126
-
127
- // Row 2: stats ... model
128
- let totalInput = 0, totalOutput = 0, totalCost = 0;
129
- for (const entry of ctx.sessionManager.getEntries()) {
130
- if (entry.type === "message" && entry.message.role === "assistant") {
131
- const m = entry.message as AssistantMessage;
132
- totalInput += m.usage.input;
133
- totalOutput += m.usage.output;
134
- totalCost += m.usage.cost.total;
135
- }
136
- }
137
-
138
- const fmt = (n: number) => n < 1000 ? `${n}` : n < 10000 ? `${(n / 1000).toFixed(1)}k` : `${Math.round(n / 1000)}k`;
139
- const parts: string[] = [];
140
- if (totalInput) parts.push(`↑${fmt(totalInput)}`);
141
- if (totalOutput) parts.push(`↓${fmt(totalOutput)}`);
142
- if (totalCost) parts.push(`$${totalCost.toFixed(3)}`);
143
-
144
- const usage = ctx.getContextUsage();
145
- const ctxPct = usage?.percent !== null && usage?.percent !== undefined ? `${usage.percent.toFixed(1)}%` : "?";
146
- const ctxWin = usage?.contextWindow ?? ctx.model?.contextWindow ?? 0;
147
- parts.push(`${ctxPct}/${fmt(ctxWin)}`);
148
-
149
- const statsLeft = theme.fg("dim", parts.join(" "));
150
- const modelRight = theme.fg("dim", ctx.model?.id || "no-model");
151
- const statsLeftW = visibleWidth(statsLeft);
152
- const modelRightW = visibleWidth(modelRight);
153
- const pad2 = " ".repeat(Math.max(2, width - statsLeftW - modelRightW));
154
- const row2 = truncateToWidth(statsLeft + pad2 + modelRight, width);
155
-
156
- return [row1, row2];
157
- },
158
- };
159
- });
160
- }
161
-
162
- function stopFlash() {
163
- if (flashTimer) { clearInterval(flashTimer); flashTimer = null; }
164
- footerTui = null;
165
- }
166
-
167
- async function toggleVoice(ctx: ExtensionContext) {
168
- if (active) {
169
- killRecognizer();
170
- active = false;
171
- setVoiceFooter(ctx, false);
172
- return;
173
- }
174
-
175
- if (IS_DARWIN) {
176
- if (!ensureBinary()) {
177
- ctx.ui.notify("Voice: failed to compile speech recognizer (need Xcode CLI tools)", "error");
178
- return;
179
- }
180
- } else if (IS_LINUX) {
181
- if (!ensureLinuxReady(ctx)) {
182
- return;
183
- }
184
- }
185
-
186
- active = true;
187
- setVoiceFooter(ctx, true);
188
- await runVoiceSession(ctx);
189
- }
190
-
191
- pi.registerCommand("voice", {
192
- description: "Toggle voice mode",
193
- handler: async (_args, ctx) => toggleVoice(ctx),
194
- });
195
-
196
- pi.registerShortcut("ctrl+alt+v", {
197
- description: shortcutDesc("Toggle voice mode", "/voice"),
198
- handler: async (ctx) => toggleVoice(ctx),
199
- });
200
-
201
- function killRecognizer() {
202
- if (recognizerProcess) { recognizerProcess.kill("SIGTERM"); recognizerProcess = null; }
203
- }
204
-
205
- function startRecognizer(
206
- onPartial: (text: string) => void,
207
- onFinal: (text: string) => void,
208
- onError: (msg: string) => void,
209
- onReady: () => void,
210
- ) {
211
- if (IS_LINUX) {
212
- recognizerProcess = spawn(linuxPython(), [PYTHON_SCRIPT], {
213
- stdio: ["pipe", "pipe", "pipe"],
214
- });
215
- } else {
216
- recognizerProcess = spawn(RECOGNIZER_BIN, [], { stdio: ["pipe", "pipe", "pipe"] });
217
- }
218
- const rl = readline.createInterface({ input: recognizerProcess.stdout! });
219
- rl.on("line", (line: string) => {
220
- if (line === "READY") { onReady(); return; }
221
- if (line.startsWith("PARTIAL:")) onPartial(line.slice(8));
222
- else if (line.startsWith("FINAL:")) onFinal(line.slice(6));
223
- else if (line.startsWith("ERROR:")) onError(line.slice(6));
224
- });
225
- recognizerProcess.on("error", (err) => onError(err.message));
226
- recognizerProcess.on("exit", () => { recognizerProcess = null; });
227
- }
228
-
229
- async function runVoiceSession(ctx: ExtensionContext): Promise<void> {
230
- return new Promise<void>((resolve) => {
231
- // The Swift recognizer handles accumulation across pause-induced
232
- // transcription resets. Both PARTIAL and FINAL messages contain
233
- // the full accumulated text, so we just pass them through.
234
- startRecognizer(
235
- (text) => {
236
- ctx.ui.setEditorText(text);
237
- },
238
- (text) => {
239
- ctx.ui.setEditorText(text);
240
- },
241
- (msg) => ctx.ui.notify(`Voice: ${msg}`, "error"),
242
- () => {},
243
- );
244
-
245
- ctx.ui.custom<void>(
246
- (_tui, _theme, _kb, done) => ({
247
- render(): string[] { return []; },
248
- handleInput(data: string) {
249
- if (isKeyRelease(data)) return;
250
- if (matchesKey(data, Key.escape) || matchesKey(data, Key.enter)) {
251
- killRecognizer();
252
- active = false;
253
- setVoiceFooter(ctx, false);
254
- done();
255
- }
256
- },
257
- invalidate() {},
258
- }),
259
- { overlay: true, overlayOptions: { anchor: "bottom-center", width: "100%" } },
260
- ).then(() => resolve());
261
- });
262
- }
82
+ export default function(pi: ExtensionAPI) {
83
+ if (!IS_DARWIN && !IS_LINUX) return;
84
+
85
+ let active = false;
86
+ let activationMode: VoiceActivationMode | null = null;
87
+ let recognizerProcess: ChildProcess | null = null;
88
+ let flashOn = true;
89
+ let flashTimer: ReturnType<typeof setInterval> | null = null;
90
+ let footerTui: { requestRender: () => void } | null = null;
91
+ let closeVoiceOverlay: (() => void) | null = null;
92
+ let voiceSessionPromise: Promise<void> | null = null;
93
+ let holdToTalkUnsupportedNotified = false;
94
+
95
+ function setVoiceFooter(ctx: ExtensionContext, on: boolean) {
96
+ if (!on) {
97
+ stopFlash();
98
+ ctx.ui.setFooter(undefined);
99
+ return;
100
+ }
101
+
102
+ flashOn = true;
103
+ flashTimer = setInterval(() => {
104
+ flashOn = !flashOn;
105
+ footerTui?.requestRender();
106
+ }, 500);
107
+
108
+ ctx.ui.setFooter((tui, theme, footerData) => {
109
+ footerTui = tui;
110
+ const branchUnsub = footerData.onBranchChange(() => tui.requestRender());
111
+
112
+ return {
113
+ dispose: branchUnsub,
114
+ invalidate() { },
115
+ render(width: number): string[] {
116
+ // Row 1: pwd (branch) ... ● transcribing
117
+ let pwd = process.cwd();
118
+ const home = process.env.HOME || process.env.USERPROFILE;
119
+ if (home && pwd.startsWith(home)) pwd = `~${pwd.slice(home.length)}`;
120
+ const branch = footerData.getGitBranch();
121
+ if (branch) pwd = `${pwd} (${branch})`;
122
+
123
+ const dot = flashOn ? theme.fg("error", "●") : theme.fg("dim", "●");
124
+ const voiceTag = `${dot} ${theme.fg("error", "transcribing")}`;
125
+ const voiceTagWidth = visibleWidth(voiceTag);
126
+
127
+ const maxPwdWidth = width - voiceTagWidth - 2;
128
+ const pwdStr = truncateToWidth(theme.fg("dim", pwd), maxPwdWidth, theme.fg("dim", "..."));
129
+ const pad1 = " ".repeat(Math.max(1, width - visibleWidth(pwdStr) - voiceTagWidth));
130
+ const row1 = truncateToWidth(pwdStr + pad1 + voiceTag, width);
131
+
132
+ // Row 2: stats ... model
133
+ let totalInput = 0, totalOutput = 0, totalCost = 0;
134
+ for (const entry of ctx.sessionManager.getEntries()) {
135
+ if (entry.type === "message" && entry.message.role === "assistant") {
136
+ const m = entry.message as AssistantMessage;
137
+ totalInput += m.usage.input;
138
+ totalOutput += m.usage.output;
139
+ totalCost += m.usage.cost.total;
140
+ }
141
+ }
142
+
143
+ const fmt = (n: number) => n < 1000 ? `${n}` : n < 10000 ? `${(n / 1000).toFixed(1)}k` : `${Math.round(n / 1000)}k`;
144
+ const parts: string[] = [];
145
+ if (totalInput) parts.push(`↑${fmt(totalInput)}`);
146
+ if (totalOutput) parts.push(`↓${fmt(totalOutput)}`);
147
+ if (totalCost) parts.push(`$${totalCost.toFixed(3)}`);
148
+
149
+ const usage = ctx.getContextUsage();
150
+ const ctxPct = usage?.percent !== null && usage?.percent !== undefined ? `${usage.percent.toFixed(1)}%` : "?";
151
+ const ctxWin = usage?.contextWindow ?? ctx.model?.contextWindow ?? 0;
152
+ parts.push(`${ctxPct}/${fmt(ctxWin)}`);
153
+
154
+ const statsLeft = theme.fg("dim", parts.join(" "));
155
+ const modelRight = theme.fg("dim", ctx.model?.id || "no-model");
156
+ const statsLeftW = visibleWidth(statsLeft);
157
+ const modelRightW = visibleWidth(modelRight);
158
+ const pad2 = " ".repeat(Math.max(2, width - statsLeftW - modelRightW));
159
+ const row2 = truncateToWidth(statsLeft + pad2 + modelRight, width);
160
+
161
+ return [row1, row2];
162
+ },
163
+ };
164
+ });
165
+ }
166
+
167
+ function stopFlash() {
168
+ if (flashTimer) {
169
+ clearInterval(flashTimer);
170
+ flashTimer = null;
171
+ }
172
+ footerTui = null;
173
+ }
174
+
175
+ function killRecognizer() {
176
+ if (recognizerProcess) {
177
+ recognizerProcess.kill("SIGTERM");
178
+ recognizerProcess = null;
179
+ }
180
+ }
181
+
182
+ function prepareVoice(ctx: ExtensionContext): boolean {
183
+ if (IS_DARWIN) {
184
+ if (!ensureBinary()) {
185
+ ctx.ui.notify("Voice: failed to compile speech recognizer (need Xcode CLI tools)", "error");
186
+ return false;
187
+ }
188
+ } else if (IS_LINUX) {
189
+ if (!ensureLinuxReady(ctx)) {
190
+ return false;
191
+ }
192
+ }
193
+ return true;
194
+ }
195
+
196
+ async function startVoice(ctx: ExtensionContext, mode: VoiceActivationMode): Promise<boolean> {
197
+ if (active) return false;
198
+ if (!prepareVoice(ctx)) return false;
199
+
200
+ active = true;
201
+ activationMode = mode;
202
+ setVoiceFooter(ctx, true);
203
+ voiceSessionPromise = runVoiceSession(ctx).finally(() => {
204
+ if (voiceSessionPromise) {
205
+ voiceSessionPromise = null;
206
+ }
207
+ });
208
+ return true;
209
+ }
210
+
211
+ async function stopVoice(ctx: ExtensionContext): Promise<void> {
212
+ if (!active && !closeVoiceOverlay && !recognizerProcess) return;
213
+
214
+ killRecognizer();
215
+ active = false;
216
+ activationMode = null;
217
+ setVoiceFooter(ctx, false);
218
+
219
+ const close = closeVoiceOverlay;
220
+ closeVoiceOverlay = null;
221
+ close?.();
222
+
223
+ await voiceSessionPromise;
224
+ }
225
+
226
+ async function toggleVoice(ctx: ExtensionContext) {
227
+ if (active) {
228
+ await stopVoice(ctx);
229
+ return;
230
+ }
231
+
232
+ await startVoice(ctx, "toggle");
233
+ }
234
+
235
+ function startRecognizer(
236
+ onPartial: (text: string) => void,
237
+ onFinal: (text: string) => void,
238
+ onError: (msg: string) => void,
239
+ onReady: () => void,
240
+ ) {
241
+ if (IS_LINUX) {
242
+ recognizerProcess = spawn(linuxPython(), [PYTHON_SCRIPT], {
243
+ stdio: ["pipe", "pipe", "pipe"],
244
+ });
245
+ } else {
246
+ recognizerProcess = spawn(RECOGNIZER_BIN, [], { stdio: ["pipe", "pipe", "pipe"] });
247
+ }
248
+ const rl = readline.createInterface({ input: recognizerProcess.stdout! });
249
+ rl.on("line", (line: string) => {
250
+ if (line === "READY") { onReady(); return; }
251
+ if (line.startsWith("PARTIAL:")) onPartial(line.slice(8));
252
+ else if (line.startsWith("FINAL:")) onFinal(line.slice(6));
253
+ else if (line.startsWith("ERROR:")) onError(line.slice(6));
254
+ });
255
+ recognizerProcess.on("error", (err) => onError(err.message));
256
+ recognizerProcess.on("exit", () => { recognizerProcess = null; });
257
+ }
258
+
259
+ async function runVoiceSession(ctx: ExtensionContext): Promise<void> {
260
+ return new Promise<void>((resolve) => {
261
+ // The Swift recognizer handles accumulation across pause-induced
262
+ // transcription resets. Both PARTIAL and FINAL messages contain
263
+ // the full accumulated text, so we just pass them through.
264
+ startRecognizer(
265
+ (text) => {
266
+ ctx.ui.setEditorText(text);
267
+ },
268
+ (text) => {
269
+ ctx.ui.setEditorText(text);
270
+ },
271
+ (msg) => ctx.ui.notify(`Voice: ${msg}`, "error"),
272
+ () => { },
273
+ );
274
+
275
+ ctx.ui.custom<void>(
276
+ (_tui, _theme, _kb, done) => {
277
+ const close = () => done();
278
+ closeVoiceOverlay = close;
279
+ return {
280
+ render(): string[] { return []; },
281
+ handleInput(data: string) {
282
+ if (isKeyRelease(data)) return;
283
+ if (matchesKey(data, Key.escape) || matchesKey(data, Key.enter)) {
284
+ void stopVoice(ctx);
285
+ }
286
+ },
287
+ invalidate() { },
288
+ dispose() {
289
+ if (closeVoiceOverlay === close) {
290
+ closeVoiceOverlay = null;
291
+ }
292
+ },
293
+ };
294
+ },
295
+ { overlay: true, overlayOptions: { anchor: "bottom-center", width: "100%" } },
296
+ ).then(() => resolve());
297
+ });
298
+ }
299
+
300
+ pi.on("session_start", async (_event, ctx) => {
301
+ ctx.ui.onTerminalInput((data) => handlePushToTalkInput(data, {
302
+ active,
303
+ activationMode,
304
+ editorText: ctx.ui.getEditorText(),
305
+ holdToTalkSupported: isKittyProtocolActive(),
306
+ onUnsupported: () => {
307
+ if (holdToTalkUnsupportedNotified) return;
308
+ holdToTalkUnsupportedNotified = true;
309
+ ctx.ui.notify("Voice: hold Space requires Kitty key-release support in this terminal — use /voice or Ctrl+Alt+V", "warning");
310
+ },
311
+ startPushToTalk: async () => {
312
+ await startVoice(ctx, "push-to-talk");
313
+ },
314
+ stopVoice: async () => {
315
+ await stopVoice(ctx);
316
+ },
317
+ }));
318
+ });
319
+
320
+ pi.on("session_shutdown", async (_event, ctx) => {
321
+ await stopVoice(ctx);
322
+ });
323
+
324
+ pi.registerCommand("voice", {
325
+ description: "Toggle voice mode",
326
+ handler: async (_args, ctx) => toggleVoice(ctx),
327
+ });
328
+
329
+ pi.registerShortcut("ctrl+alt+v", {
330
+ description: shortcutDesc("Toggle voice mode", "/voice"),
331
+ handler: async (ctx) => toggleVoice(ctx),
332
+ });
263
333
  }
@@ -0,0 +1,42 @@
1
+ import { isKeyRelease, Key, matchesKey } from "@gsd/pi-tui";
2
+ import type { TerminalInputHandler } from "@gsd/pi-coding-agent";
3
+
4
+ export type VoiceActivationMode = "toggle" | "push-to-talk";
5
+
6
+ export interface PushToTalkState {
7
+ active: boolean;
8
+ activationMode: VoiceActivationMode | null;
9
+ editorText: string;
10
+ holdToTalkSupported: boolean;
11
+ onUnsupported?(): void;
12
+ startPushToTalk(): void | Promise<void>;
13
+ stopVoice(): void | Promise<void>;
14
+ }
15
+
16
+ export function handlePushToTalkInput(data: string, state: PushToTalkState): ReturnType<TerminalInputHandler> {
17
+ if (!matchesKey(data, Key.space)) return undefined;
18
+
19
+ if (isKeyRelease(data)) {
20
+ if (state.activationMode === "push-to-talk") {
21
+ void Promise.resolve(state.stopVoice());
22
+ return { consume: true };
23
+ }
24
+ return undefined;
25
+ }
26
+
27
+ if (state.activationMode === "push-to-talk") {
28
+ // Consume repeat events while the key is held so we do not leak spaces.
29
+ return { consume: true };
30
+ }
31
+
32
+ if (state.active) return undefined;
33
+ if (state.editorText.length > 0) return undefined;
34
+
35
+ if (!state.holdToTalkSupported) {
36
+ state.onUnsupported?.();
37
+ return { consume: true };
38
+ }
39
+
40
+ void Promise.resolve(state.startPushToTalk());
41
+ return { consume: true };
42
+ }