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
@@ -0,0 +1,172 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import stripAnsi from "strip-ansi";
4
+ import type { Context, Model, SimpleStreamOptions, Message } from "@gsd/pi-ai";
5
+ import type { TUI } from "@gsd/pi-tui";
6
+ import { BtwOverlayComponent } from "./btw-overlay.js";
7
+ import { getMarkdownTheme, initTheme } from "../theme/theme.js";
8
+
9
+ initTheme();
10
+
11
+ function makeUi(rows = 40): TUI {
12
+ return { terminal: { rows } } as unknown as TUI;
13
+ }
14
+
15
+ function makeModel(): Model<any> {
16
+ return {
17
+ id: "test-model",
18
+ name: "Test Model",
19
+ api: "openai-completions",
20
+ provider: "test",
21
+ baseUrl: "",
22
+ reasoning: false,
23
+ input: ["text"],
24
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
25
+ contextWindow: 4096,
26
+ maxTokens: 1024,
27
+ } as unknown as Model<any>;
28
+ }
29
+
30
+ function makeMessages(): Message[] {
31
+ return [
32
+ {
33
+ role: "user",
34
+ content: [{ type: "text", text: "hello" }],
35
+ timestamp: Date.now(),
36
+ },
37
+ ];
38
+ }
39
+
40
+ function renderText(component: BtwOverlayComponent): string {
41
+ return stripAnsi(component.render(80).join("\n"));
42
+ }
43
+
44
+ async function flushTurns(count = 3): Promise<void> {
45
+ for (let i = 0; i < count; i++) {
46
+ await Promise.resolve();
47
+ await new Promise((resolve) => setTimeout(resolve, 0));
48
+ }
49
+ }
50
+
51
+ test("BtwOverlayComponent renders initial question and placeholder before response", async () => {
52
+ const component = new BtwOverlayComponent(
53
+ "What is btw?",
54
+ makeModel(),
55
+ "system",
56
+ makeMessages(),
57
+ getMarkdownTheme(),
58
+ makeUi(),
59
+ () => undefined,
60
+ () => undefined,
61
+ () => ({
62
+ async *[Symbol.asyncIterator]() {
63
+ await new Promise(() => undefined);
64
+ },
65
+ }) as any,
66
+ );
67
+
68
+ await flushTurns();
69
+ const text = renderText(component);
70
+ assert.match(text, /btw/);
71
+ assert.match(text, /You/);
72
+ assert.match(text, /What is btw\?/);
73
+ assert.match(text, /Awaiting response/);
74
+ assert.match(text, /Ask follow-up/);
75
+ component.dispose();
76
+ });
77
+
78
+ test("BtwOverlayComponent supports follow-up turns and reuses overlay-local history", async () => {
79
+ const calls: Array<{ context: Context; options?: SimpleStreamOptions }> = [];
80
+ const responses = [
81
+ [
82
+ { type: "text_delta", delta: "First answer" },
83
+ { type: "done" },
84
+ ],
85
+ [
86
+ { type: "text_delta", delta: "Second answer" },
87
+ { type: "done" },
88
+ ],
89
+ ];
90
+
91
+ const streamFn = ((_: Model<any>, context: Context, options?: SimpleStreamOptions) => {
92
+ calls.push({ context, options });
93
+ const response = responses[calls.length - 1] ?? [];
94
+ return {
95
+ async *[Symbol.asyncIterator]() {
96
+ for (const event of response) {
97
+ yield event;
98
+ }
99
+ },
100
+ };
101
+ }) as any;
102
+
103
+ const component = new BtwOverlayComponent(
104
+ "What is btw?",
105
+ makeModel(),
106
+ "system",
107
+ makeMessages(),
108
+ getMarkdownTheme(),
109
+ makeUi(),
110
+ () => undefined,
111
+ () => undefined,
112
+ streamFn,
113
+ { apiKey: "test-key", sessionId: "session-1" },
114
+ );
115
+
116
+ await flushTurns();
117
+ for (const ch of "next") {
118
+ component.handleInput(ch);
119
+ }
120
+ component.handleInput("\n");
121
+ await flushTurns();
122
+
123
+ assert.equal(calls.length, 2);
124
+ assert.equal(calls[0]?.options?.apiKey, "test-key");
125
+ assert.equal(calls[1]?.options?.sessionId, "session-1");
126
+ assert.equal(calls[0]?.context.messages.length, 2);
127
+ assert.equal(calls[1]?.context.messages.length, 4);
128
+ assert.equal(calls[1]?.context.messages[1]?.role, "user");
129
+ assert.equal(calls[1]?.context.messages[2]?.role, "assistant");
130
+ assert.deepEqual(calls[1]?.context.messages[3]?.content, [{ type: "text", text: "next" }]);
131
+
132
+ const text = renderText(component);
133
+ assert.match(text, /First answer/);
134
+ assert.match(text, /Second answer/);
135
+ component.dispose();
136
+ });
137
+
138
+ test("BtwOverlayComponent aborts active stream on Escape", async () => {
139
+ let capturedSignal: AbortSignal | undefined;
140
+ let dismissed = false;
141
+ const streamFn = ((_model: any, _context: any, options?: { signal?: AbortSignal }) => {
142
+ capturedSignal = options?.signal;
143
+ return {
144
+ async *[Symbol.asyncIterator]() {
145
+ await new Promise<void>((resolve) => {
146
+ capturedSignal?.addEventListener("abort", () => resolve(), { once: true });
147
+ });
148
+ },
149
+ };
150
+ }) as any;
151
+
152
+ const component = new BtwOverlayComponent(
153
+ "What is btw?",
154
+ makeModel(),
155
+ "system",
156
+ makeMessages(),
157
+ getMarkdownTheme(),
158
+ makeUi(),
159
+ () => {
160
+ dismissed = true;
161
+ },
162
+ () => undefined,
163
+ streamFn,
164
+ );
165
+
166
+ await flushTurns();
167
+ component.handleInput("\u001b");
168
+ await flushTurns();
169
+
170
+ assert.equal(dismissed, true);
171
+ assert.equal(capturedSignal?.aborted, true);
172
+ });
@@ -0,0 +1,402 @@
1
+ import type { AssistantMessage, Context, Message, Model, SimpleStreamOptions, StopReason, Usage } from "@gsd/pi-ai";
2
+ import { streamSimple } from "@gsd/pi-ai";
3
+ import {
4
+ Input,
5
+ Markdown,
6
+ type MarkdownTheme,
7
+ parseKey,
8
+ truncateToWidth,
9
+ type Component,
10
+ type Focusable,
11
+ type TUI,
12
+ visibleWidth,
13
+ } from "@gsd/pi-tui";
14
+ import { theme } from "../theme/theme.js";
15
+
16
+ const EMPTY_USAGE: Usage = {
17
+ input: 0,
18
+ output: 0,
19
+ cacheRead: 0,
20
+ cacheWrite: 0,
21
+ totalTokens: 0,
22
+ cost: {
23
+ input: 0,
24
+ output: 0,
25
+ cacheRead: 0,
26
+ cacheWrite: 0,
27
+ total: 0,
28
+ },
29
+ };
30
+
31
+ interface BtwTurn {
32
+ role: "user" | "assistant";
33
+ text: string;
34
+ isError?: boolean;
35
+ isStreaming?: boolean;
36
+ }
37
+
38
+ function clamp(value: number, min: number, max: number): number {
39
+ return Math.max(min, Math.min(max, value));
40
+ }
41
+
42
+ function fitWidth(text: string, width: number): string {
43
+ if (width <= 0) return "";
44
+ const visible = visibleWidth(text);
45
+ if (visible === width) return text;
46
+ if (visible < width) return text + " ".repeat(width - visible);
47
+ return truncateToWidth(text, width, "");
48
+ }
49
+
50
+ function formatAssistantError(message: AssistantMessage): string {
51
+ if (message.errorMessage?.trim()) {
52
+ return message.errorMessage.trim();
53
+ }
54
+
55
+ const text = message.content
56
+ .map((part) => (part.type === "text" ? part.text.trim() : ""))
57
+ .filter(Boolean)
58
+ .join("\n")
59
+ .trim();
60
+
61
+ return text || `Request failed (${message.stopReason})`;
62
+ }
63
+
64
+ export class BtwOverlayComponent implements Component, Focusable {
65
+ private _focused = false;
66
+
67
+ private readonly markdownTheme: MarkdownTheme;
68
+ private readonly model: Model<any>;
69
+ private readonly systemPrompt: string | undefined;
70
+ private readonly baseMessages: Message[];
71
+ private readonly ui: TUI;
72
+ private readonly input = new Input();
73
+ private readonly onDismiss: () => void;
74
+ private readonly requestRender: () => void;
75
+ private readonly streamFn: typeof streamSimple;
76
+ private readonly streamOptions: Pick<SimpleStreamOptions, "apiKey" | "sessionId">;
77
+
78
+ private readonly btwHistory: Message[] = [];
79
+ private readonly turns: BtwTurn[] = [];
80
+ private currentAbortController: AbortController | undefined;
81
+ private disposed = false;
82
+ private isStreaming = false;
83
+ private scrollOffset = 0;
84
+ private followTail = true;
85
+ private lastWidth = 0;
86
+
87
+ get focused(): boolean {
88
+ return this._focused;
89
+ }
90
+
91
+ set focused(value: boolean) {
92
+ this._focused = value;
93
+ this.input.focused = value;
94
+ }
95
+
96
+ constructor(
97
+ question: string,
98
+ model: Model<any>,
99
+ systemPrompt: string | undefined,
100
+ messages: Message[],
101
+ markdownTheme: MarkdownTheme,
102
+ ui: TUI,
103
+ onDismiss: () => void,
104
+ requestRender: () => void,
105
+ streamer: typeof streamSimple = streamSimple,
106
+ streamOptions: Pick<SimpleStreamOptions, "apiKey" | "sessionId"> = {},
107
+ ) {
108
+ this.model = model;
109
+ this.systemPrompt = systemPrompt;
110
+ this.baseMessages = messages;
111
+ this.markdownTheme = markdownTheme;
112
+ this.ui = ui;
113
+ this.onDismiss = onDismiss;
114
+ this.requestRender = requestRender;
115
+ this.streamFn = streamer;
116
+ this.streamOptions = streamOptions;
117
+
118
+ this.input.placeholder = "Ask follow-up...";
119
+ this.input.onEscape = () => this.dismiss();
120
+ this.input.onSubmit = (value) => {
121
+ void this.submitTurn(value);
122
+ };
123
+
124
+ void this.submitTurn(question);
125
+ }
126
+
127
+ handleInput(data: string): void {
128
+ const key = parseKey(data);
129
+ switch (key) {
130
+ case "up":
131
+ this.scrollBy(-1);
132
+ return;
133
+ case "down":
134
+ this.scrollBy(1);
135
+ return;
136
+ case "pageUp":
137
+ this.scrollBy(-(this.getBodyHeight() - 1));
138
+ return;
139
+ case "pageDown":
140
+ this.scrollBy(this.getBodyHeight() - 1);
141
+ return;
142
+ }
143
+
144
+ this.input.handleInput(data);
145
+ if (!this.disposed) {
146
+ this.requestRender();
147
+ }
148
+ }
149
+
150
+ invalidate(): void {
151
+ // No cached subtree state to invalidate.
152
+ }
153
+
154
+ dispose(): void {
155
+ if (this.disposed) return;
156
+ this.disposed = true;
157
+ this.currentAbortController?.abort();
158
+ }
159
+
160
+ render(width: number): string[] {
161
+ this.lastWidth = width;
162
+
163
+ const totalHeight = this.getTotalHeight();
164
+ const bodyHeight = Math.max(1, totalHeight - 4);
165
+ const contentWidth = Math.max(1, width - 4);
166
+ const bodyLines = this.getBodyLines(contentWidth);
167
+ const maxOffset = Math.max(0, bodyLines.length - bodyHeight);
168
+ if (this.followTail) {
169
+ this.scrollOffset = maxOffset;
170
+ } else {
171
+ this.scrollOffset = clamp(this.scrollOffset, 0, maxOffset);
172
+ }
173
+
174
+ const visibleBody = bodyLines.slice(this.scrollOffset, this.scrollOffset + bodyHeight);
175
+ while (visibleBody.length < bodyHeight) {
176
+ visibleBody.push("");
177
+ }
178
+
179
+ const inputLines = this.input.render(contentWidth);
180
+ const inputLine = inputLines[0] ?? "";
181
+
182
+ return [
183
+ this.renderTopBorder(width),
184
+ ...visibleBody.map((line) => this.renderBodyLine(line, width)),
185
+ this.renderBodyLine(inputLine, width),
186
+ this.renderFooterLine(width, bodyLines.length > bodyHeight),
187
+ this.renderBottomBorder(width),
188
+ ];
189
+ }
190
+
191
+ private dismiss(): void {
192
+ this.dispose();
193
+ this.onDismiss();
194
+ }
195
+
196
+ private async submitTurn(rawQuestion: string): Promise<void> {
197
+ const question = rawQuestion.trim();
198
+ if (!question || this.disposed || this.isStreaming) {
199
+ return;
200
+ }
201
+
202
+ const userMessage = this.createUserMessage(question);
203
+ const context: Context = {
204
+ systemPrompt: this.systemPrompt,
205
+ messages: [...this.baseMessages, ...this.btwHistory, userMessage],
206
+ };
207
+
208
+ this.btwHistory.push(userMessage);
209
+ this.turns.push({ role: "user", text: question });
210
+ const assistantTurn: BtwTurn = { role: "assistant", text: "", isStreaming: true };
211
+ this.turns.push(assistantTurn);
212
+ this.input.setValue("");
213
+ this.isStreaming = true;
214
+ this.followTail = true;
215
+ this.requestRender();
216
+
217
+ const abortController = new AbortController();
218
+ this.currentAbortController = abortController;
219
+
220
+ try {
221
+ const eventStream = this.streamFn(this.model, context, {
222
+ signal: abortController.signal,
223
+ apiKey: this.streamOptions.apiKey,
224
+ sessionId: this.streamOptions.sessionId,
225
+ });
226
+
227
+ for await (const event of eventStream) {
228
+ if (this.disposed || this.currentAbortController !== abortController) {
229
+ break;
230
+ }
231
+
232
+ if (event.type === "text_delta") {
233
+ assistantTurn.text += event.delta;
234
+ this.requestRender();
235
+ continue;
236
+ }
237
+
238
+ if (event.type === "done") {
239
+ this.finishAssistantTurn(assistantTurn, "stop");
240
+ this.requestRender();
241
+ break;
242
+ }
243
+
244
+ if (event.type === "error") {
245
+ if (event.reason === "aborted") {
246
+ continue;
247
+ }
248
+
249
+ assistantTurn.isError = true;
250
+ if (!assistantTurn.text.trim()) {
251
+ assistantTurn.text = formatAssistantError(event.error);
252
+ }
253
+ this.finishAssistantTurn(assistantTurn, "error");
254
+ this.requestRender();
255
+ break;
256
+ }
257
+ }
258
+ } catch (error: unknown) {
259
+ if (this.disposed || abortController.signal.aborted || this.currentAbortController !== abortController) {
260
+ return;
261
+ }
262
+
263
+ assistantTurn.isError = true;
264
+ assistantTurn.text = error instanceof Error ? error.message : "Unknown error";
265
+ this.finishAssistantTurn(assistantTurn, "error");
266
+ this.requestRender();
267
+ } finally {
268
+ if (this.currentAbortController === abortController) {
269
+ this.currentAbortController = undefined;
270
+ this.isStreaming = false;
271
+ assistantTurn.isStreaming = false;
272
+ this.requestRender();
273
+ }
274
+ }
275
+ }
276
+
277
+ private finishAssistantTurn(turn: BtwTurn, stopReason: StopReason): void {
278
+ turn.isStreaming = false;
279
+ if (!turn.text.trim()) {
280
+ return;
281
+ }
282
+
283
+ this.btwHistory.push({
284
+ role: "assistant",
285
+ content: [{ type: "text", text: turn.text }],
286
+ api: this.model.api,
287
+ provider: this.model.provider,
288
+ model: this.model.id,
289
+ usage: EMPTY_USAGE,
290
+ stopReason,
291
+ errorMessage: turn.isError ? turn.text : undefined,
292
+ timestamp: Date.now(),
293
+ });
294
+ }
295
+
296
+ private createUserMessage(question: string): Message {
297
+ return {
298
+ role: "user",
299
+ content: [{ type: "text", text: question }],
300
+ timestamp: Date.now(),
301
+ };
302
+ }
303
+
304
+ private getTotalHeight(): number {
305
+ return Math.max(6, Math.floor(this.ui.terminal.rows * 0.7));
306
+ }
307
+
308
+ private getBodyHeight(): number {
309
+ return Math.max(1, this.getTotalHeight() - 4);
310
+ }
311
+
312
+ private scrollBy(delta: number): void {
313
+ const bodyHeight = this.getBodyHeight();
314
+ const bodyLines = this.getBodyLines(Math.max(1, this.lastWidth - 4));
315
+ const maxOffset = Math.max(0, bodyLines.length - bodyHeight);
316
+ this.scrollOffset = clamp(this.scrollOffset + delta, 0, maxOffset);
317
+ this.followTail = this.scrollOffset >= maxOffset;
318
+ this.requestRender();
319
+ }
320
+
321
+ private getBodyLines(contentWidth: number): string[] {
322
+ const lines: string[] = [];
323
+
324
+ for (const [index, turn] of this.turns.entries()) {
325
+ if (index > 0) {
326
+ lines.push("");
327
+ }
328
+ lines.push(turn.role === "user" ? theme.fg("accent", "You") : theme.fg("muted", "btw"));
329
+ lines.push(...this.renderTurnContent(turn, Math.max(1, contentWidth - 2)).map((line) => ` ${line}`));
330
+ }
331
+
332
+ if (lines.length === 0) {
333
+ lines.push(theme.fg("dim", "Awaiting response…"));
334
+ }
335
+
336
+ return lines;
337
+ }
338
+
339
+ private renderTurnContent(turn: BtwTurn, contentWidth: number): string[] {
340
+ if (!turn.text.trim()) {
341
+ return [theme.fg("dim", turn.isStreaming ? "Awaiting response…" : "No response received.")];
342
+ }
343
+
344
+ if (turn.isError) {
345
+ return [theme.fg("error", `Error: ${turn.text}`)];
346
+ }
347
+
348
+ const markdown = new Markdown(turn.text, 0, 0, this.markdownTheme, {
349
+ color: (text: string) => theme.fg("text", text),
350
+ });
351
+ return markdown.render(contentWidth);
352
+ }
353
+
354
+ private renderTopBorder(width: number): string {
355
+ if (width <= 2) {
356
+ return "┌┐".slice(0, width);
357
+ }
358
+
359
+ const innerWidth = width - 2;
360
+ const title = theme.fg("accent", " btw ");
361
+ const titleWidth = visibleWidth(title);
362
+
363
+ if (innerWidth <= titleWidth) {
364
+ return fitWidth(`┌${truncateToWidth(title, innerWidth, "")}┐`, width);
365
+ }
366
+
367
+ const dashCount = innerWidth - titleWidth;
368
+ const leftDashes = Math.floor(dashCount / 2);
369
+ const rightDashes = dashCount - leftDashes;
370
+ const border = theme.fg("border", "─");
371
+ return `┌${border.repeat(leftDashes)}${title}${border.repeat(rightDashes)}┐`;
372
+ }
373
+
374
+ private renderBottomBorder(width: number): string {
375
+ if (width <= 2) {
376
+ return "└┘".slice(0, width);
377
+ }
378
+ return `└${theme.fg("border", "─").repeat(width - 2)}┘`;
379
+ }
380
+
381
+ private renderBodyLine(text: string, width: number): string {
382
+ const innerWidth = Math.max(1, width - 4);
383
+ const fitted = fitWidth(truncateToWidth(text, innerWidth, ""), innerWidth);
384
+ return fitWidth(`│ ${fitted} │`, width);
385
+ }
386
+
387
+ private renderFooterLine(width: number, canScroll: boolean): string {
388
+ const innerWidth = Math.max(1, width - 4);
389
+ const parts: string[] = ["Enter send", "Esc dismiss"];
390
+ if (canScroll) {
391
+ parts.push("↑↓/PgUp/PgDn scroll");
392
+ }
393
+
394
+ const status = this.isStreaming
395
+ ? theme.fg("muted", "Streaming...")
396
+ : theme.fg("success", "Ready");
397
+
398
+ const footer = `${theme.fg("muted", parts.join(" • "))} ${status}`;
399
+ const fitted = fitWidth(truncateToWidth(footer, innerWidth, ""), innerWidth);
400
+ return fitWidth(`│ ${fitted} │`, width);
401
+ }
402
+ }
@@ -202,7 +202,7 @@ export class EmbeddedTerminalComponent implements Focusable {
202
202
  const command = theme.bold(`$ ${this.command}`);
203
203
  const hint = this.focused
204
204
  ? theme.fg("muted", "Tab/Esc return · Shift+Tab sends Tab")
205
- : theme.fg("muted", `${this.focusKeyLabel} focus`);
205
+ : theme.fg("muted", `${this.focusKeyLabel} to focus`);
206
206
 
207
207
  const left = `${prefix} ${command}`;
208
208
  const leftWidth = visibleWidth(left);
@@ -46,6 +46,7 @@ export function formatPromptCost(cost: number): string {
46
46
  export class FooterComponent implements Component {
47
47
  private autoCompactEnabled = true;
48
48
  private permissionMode: PermissionMode = "danger-full-access";
49
+ private notificationSoundEnabled = false;
49
50
 
50
51
  constructor(
51
52
  private session: AgentSession,
@@ -60,6 +61,10 @@ export class FooterComponent implements Component {
60
61
  this.permissionMode = mode;
61
62
  }
62
63
 
64
+ setNotificationSoundEnabled(enabled: boolean): void {
65
+ this.notificationSoundEnabled = enabled;
66
+ }
67
+
63
68
  /**
64
69
  * No-op: git branch caching now handled by provider.
65
70
  * Kept for compatibility with existing call sites in interactive-mode.
@@ -123,6 +128,9 @@ export class FooterComponent implements Component {
123
128
  const hotkeysHints = ["Ctrl+K • /hotkeys", "/hotkeys", "Ctrl+K"];
124
129
  const firstLineMinPadding = 2;
125
130
  const firstLineRightParts = cacheTimerStatus ? [cacheTimerStatus] : [];
131
+ if (this.notificationSoundEnabled) {
132
+ firstLineRightParts.push(theme.fg("success", "🔔"));
133
+ }
126
134
  const firstLineRightBase = firstLineRightParts.join(" ");
127
135
  const hotkeysHint = hotkeysHints.find((hint) => {
128
136
  const candidate = firstLineRightBase ? `${firstLineRightBase} ${hint}` : hint;
@@ -222,8 +230,16 @@ export class FooterComponent implements Component {
222
230
  let rightSideWithoutProvider = modelName;
223
231
  if (displayModel?.reasoning) {
224
232
  const thinkingLevel = state.thinkingLevel || "off";
225
- rightSideWithoutProvider =
226
- thinkingLevel === "off" ? `${modelName} • thinking off` : `${modelName} • ${thinkingLevel}`;
233
+ if (thinkingLevel === "off") {
234
+ rightSideWithoutProvider = `${modelName} • thinking off`;
235
+ } else if (thinkingLevel === "adaptive") {
236
+ const resolved = state.lastAdaptiveDecision?.level;
237
+ rightSideWithoutProvider = resolved
238
+ ? `${modelName} • adaptive→${resolved}`
239
+ : `${modelName} • adaptive`;
240
+ } else {
241
+ rightSideWithoutProvider = `${modelName} • ${thinkingLevel}`;
242
+ }
227
243
  }
228
244
 
229
245
  // Prepend the provider in parentheses if there are multiple providers and there's enough room