@astralyn/pi 0.87.1 → 0.87.3

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 (328) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/bundle/chunks/{chunk-CDMUKIXY.js → chunk-MJ45I4SM.js} +1 -1
  3. package/dist/bundle/chunks/{chunk-772GXEFD.js → chunk-TYOQD6VQ.js} +97 -84
  4. package/dist/bundle/chunks/{virtual-modules-LQO2MG3T.js → virtual-modules-NDGCHNG7.js} +1 -1
  5. package/dist/bundle/cli.js +1 -1
  6. package/dist/bundle/index.js +1 -1
  7. package/dist/bundle/rpc-entry.js +1 -1
  8. package/dist/config.d.ts +1 -1
  9. package/dist/config.d.ts.map +1 -1
  10. package/dist/config.js.map +1 -1
  11. package/dist/core/agent-session-runtime.d.ts +9 -0
  12. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  13. package/dist/core/agent-session-runtime.js +158 -157
  14. package/dist/core/agent-session-runtime.js.map +1 -1
  15. package/dist/core/agent-session.d.ts +14 -0
  16. package/dist/core/agent-session.d.ts.map +1 -1
  17. package/dist/core/agent-session.js +437 -434
  18. package/dist/core/agent-session.js.map +1 -1
  19. package/dist/core/background/types.d.ts +2 -0
  20. package/dist/core/background/types.d.ts.map +1 -1
  21. package/dist/core/background/types.js.map +1 -1
  22. package/dist/core/extensions/distribution-api.d.ts +41 -0
  23. package/dist/core/extensions/distribution-api.d.ts.map +1 -0
  24. package/dist/core/extensions/distribution-api.js +12 -0
  25. package/dist/core/extensions/distribution-api.js.map +1 -0
  26. package/dist/core/extensions/index.d.ts +1 -1
  27. package/dist/core/extensions/index.d.ts.map +1 -1
  28. package/dist/core/extensions/index.js.map +1 -1
  29. package/dist/core/extensions/loader.d.ts.map +1 -1
  30. package/dist/core/extensions/loader.js +1 -0
  31. package/dist/core/extensions/loader.js.map +1 -1
  32. package/dist/core/extensions/runner.d.ts.map +1 -1
  33. package/dist/core/extensions/runner.js +0 -2
  34. package/dist/core/extensions/runner.js.map +1 -1
  35. package/dist/core/extensions/types.d.ts +15 -45
  36. package/dist/core/extensions/types.d.ts.map +1 -1
  37. package/dist/core/extensions/types.js +1 -11
  38. package/dist/core/extensions/types.js.map +1 -1
  39. package/dist/core/extensions/virtual-modules.d.ts.map +1 -1
  40. package/dist/core/extensions/virtual-modules.js +2 -1
  41. package/dist/core/extensions/virtual-modules.js.map +1 -1
  42. package/dist/core/keybinding-registry.d.ts +11 -0
  43. package/dist/core/keybinding-registry.d.ts.map +1 -0
  44. package/dist/core/keybinding-registry.js +24 -0
  45. package/dist/core/keybinding-registry.js.map +1 -0
  46. package/dist/core/keybindings.d.ts +4 -64
  47. package/dist/core/keybindings.d.ts.map +1 -1
  48. package/dist/core/keybindings.js +2 -41
  49. package/dist/core/keybindings.js.map +1 -1
  50. package/dist/core/model-config.d.ts +70 -2
  51. package/dist/core/model-config.d.ts.map +1 -1
  52. package/dist/core/model-config.js +2 -26
  53. package/dist/core/model-config.js.map +1 -1
  54. package/dist/core/resource-loader.d.ts.map +1 -1
  55. package/dist/core/resource-loader.js +5 -11
  56. package/dist/core/resource-loader.js.map +1 -1
  57. package/dist/core/tools/bash.d.ts.map +1 -1
  58. package/dist/core/tools/bash.js +2 -2
  59. package/dist/core/tools/bash.js.map +1 -1
  60. package/dist/core/tools/shell-execution.d.ts +1 -0
  61. package/dist/core/tools/shell-execution.d.ts.map +1 -1
  62. package/dist/core/tools/shell-execution.js +21 -0
  63. package/dist/core/tools/shell-execution.js.map +1 -1
  64. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  65. package/dist/core/tools/tool-definition-wrapper.js +114 -1
  66. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  67. package/dist/core/trust-manager.d.ts.map +1 -1
  68. package/dist/core/trust-manager.js +11 -48
  69. package/dist/core/trust-manager.js.map +1 -1
  70. package/dist/extensions/background/actions.d.ts.map +1 -1
  71. package/dist/extensions/background/actions.js +107 -55
  72. package/dist/extensions/background/actions.js.map +1 -1
  73. package/dist/extensions/background/index.d.ts +2 -0
  74. package/dist/extensions/background/index.d.ts.map +1 -1
  75. package/dist/extensions/background/index.js +20 -3
  76. package/dist/extensions/background/index.js.map +1 -1
  77. package/dist/extensions/background/keybindings.d.ts +10 -0
  78. package/dist/extensions/background/keybindings.d.ts.map +1 -0
  79. package/dist/extensions/background/keybindings.js +14 -0
  80. package/dist/extensions/background/keybindings.js.map +1 -0
  81. package/dist/extensions/background/manager.d.ts +2 -0
  82. package/dist/extensions/background/manager.d.ts.map +1 -1
  83. package/dist/extensions/background/manager.js +1 -0
  84. package/dist/extensions/background/manager.js.map +1 -1
  85. package/dist/extensions/btw/controller.d.ts +1 -0
  86. package/dist/extensions/btw/controller.d.ts.map +1 -1
  87. package/dist/extensions/btw/controller.js +5 -5
  88. package/dist/extensions/btw/controller.js.map +1 -1
  89. package/dist/extensions/btw/index.js +1 -2
  90. package/dist/extensions/btw/index.js.map +1 -1
  91. package/dist/extensions/btw/keybindings.d.ts +10 -0
  92. package/dist/extensions/btw/keybindings.d.ts.map +1 -0
  93. package/dist/extensions/btw/keybindings.js +8 -0
  94. package/dist/extensions/btw/keybindings.js.map +1 -0
  95. package/dist/extensions/btw/panel.d.ts +1 -0
  96. package/dist/extensions/btw/panel.d.ts.map +1 -1
  97. package/dist/extensions/btw/panel.js +1 -0
  98. package/dist/extensions/btw/panel.js.map +1 -1
  99. package/dist/extensions/list-keybindings.d.ts +8 -0
  100. package/dist/extensions/list-keybindings.d.ts.map +1 -0
  101. package/dist/extensions/list-keybindings.js +8 -0
  102. package/dist/extensions/list-keybindings.js.map +1 -0
  103. package/dist/extensions/provider/compat-fields.d.ts.map +1 -1
  104. package/dist/extensions/provider/compat-fields.js +23 -3
  105. package/dist/extensions/provider/compat-fields.js.map +1 -1
  106. package/dist/extensions/provider/connection.d.ts.map +1 -1
  107. package/dist/extensions/provider/connection.js +1 -1
  108. package/dist/extensions/provider/connection.js.map +1 -1
  109. package/dist/extensions/provider/constants.d.ts +2 -0
  110. package/dist/extensions/provider/constants.d.ts.map +1 -1
  111. package/dist/extensions/provider/constants.js +2 -0
  112. package/dist/extensions/provider/constants.js.map +1 -1
  113. package/dist/extensions/provider/keybindings.d.ts +9 -0
  114. package/dist/extensions/provider/keybindings.d.ts.map +1 -0
  115. package/dist/extensions/provider/keybindings.js +12 -0
  116. package/dist/extensions/provider/keybindings.js.map +1 -0
  117. package/dist/extensions/provider/probe.d.ts +8 -4
  118. package/dist/extensions/provider/probe.d.ts.map +1 -1
  119. package/dist/extensions/provider/probe.js +169 -11
  120. package/dist/extensions/provider/probe.js.map +1 -1
  121. package/dist/extensions/provider/ui/builtin-data.d.ts +1 -0
  122. package/dist/extensions/provider/ui/builtin-data.d.ts.map +1 -1
  123. package/dist/extensions/provider/ui/builtin-data.js +1 -0
  124. package/dist/extensions/provider/ui/builtin-data.js.map +1 -1
  125. package/dist/extensions/provider/ui/compat.d.ts +1 -0
  126. package/dist/extensions/provider/ui/compat.d.ts.map +1 -1
  127. package/dist/extensions/provider/ui/compat.js +1 -0
  128. package/dist/extensions/provider/ui/compat.js.map +1 -1
  129. package/dist/extensions/provider/ui/dictionary.d.ts +1 -0
  130. package/dist/extensions/provider/ui/dictionary.d.ts.map +1 -1
  131. package/dist/extensions/provider/ui/dictionary.js +1 -0
  132. package/dist/extensions/provider/ui/dictionary.js.map +1 -1
  133. package/dist/extensions/provider/ui/editor.d.ts +1 -0
  134. package/dist/extensions/provider/ui/editor.d.ts.map +1 -1
  135. package/dist/extensions/provider/ui/editor.js +1 -0
  136. package/dist/extensions/provider/ui/editor.js.map +1 -1
  137. package/dist/extensions/provider/ui/fetch-models.d.ts +5 -3
  138. package/dist/extensions/provider/ui/fetch-models.d.ts.map +1 -1
  139. package/dist/extensions/provider/ui/fetch-models.js +28 -3
  140. package/dist/extensions/provider/ui/fetch-models.js.map +1 -1
  141. package/dist/extensions/provider/ui/model-fields.d.ts +1 -0
  142. package/dist/extensions/provider/ui/model-fields.d.ts.map +1 -1
  143. package/dist/extensions/provider/ui/model-fields.js +1 -0
  144. package/dist/extensions/provider/ui/model-fields.js.map +1 -1
  145. package/dist/extensions/provider/ui/model-options.d.ts +1 -0
  146. package/dist/extensions/provider/ui/model-options.d.ts.map +1 -1
  147. package/dist/extensions/provider/ui/model-options.js +1 -0
  148. package/dist/extensions/provider/ui/model-options.js.map +1 -1
  149. package/dist/extensions/provider/ui/provider-fields.d.ts +1 -0
  150. package/dist/extensions/provider/ui/provider-fields.d.ts.map +1 -1
  151. package/dist/extensions/provider/ui/provider-fields.js +1 -0
  152. package/dist/extensions/provider/ui/provider-fields.js.map +1 -1
  153. package/dist/extensions/question/dialog.d.ts +1 -0
  154. package/dist/extensions/question/dialog.d.ts.map +1 -1
  155. package/dist/extensions/question/dialog.js +1 -0
  156. package/dist/extensions/question/dialog.js.map +1 -1
  157. package/dist/extensions/question/index.d.ts.map +1 -1
  158. package/dist/extensions/question/index.js +3 -1
  159. package/dist/extensions/question/index.js.map +1 -1
  160. package/dist/extensions/question/keybindings.d.ts +2 -0
  161. package/dist/extensions/question/keybindings.d.ts.map +1 -0
  162. package/dist/extensions/question/keybindings.js +3 -0
  163. package/dist/extensions/question/keybindings.js.map +1 -0
  164. package/dist/extensions/question/schema.d.ts +18 -0
  165. package/dist/extensions/question/schema.d.ts.map +1 -1
  166. package/dist/extensions/question/schema.js +52 -0
  167. package/dist/extensions/question/schema.js.map +1 -1
  168. package/dist/extensions/subagent/index.d.ts.map +1 -1
  169. package/dist/extensions/subagent/index.js +3 -1
  170. package/dist/extensions/subagent/index.js.map +1 -1
  171. package/dist/extensions/subagent/resolve.d.ts +14 -1
  172. package/dist/extensions/subagent/resolve.d.ts.map +1 -1
  173. package/dist/extensions/subagent/resolve.js +32 -18
  174. package/dist/extensions/subagent/resolve.js.map +1 -1
  175. package/dist/extensions/subagent/runner.d.ts.map +1 -1
  176. package/dist/extensions/subagent/runner.js +4 -1
  177. package/dist/extensions/subagent/runner.js.map +1 -1
  178. package/dist/extensions/subagent/schema.d.ts +9 -0
  179. package/dist/extensions/subagent/schema.d.ts.map +1 -1
  180. package/dist/extensions/subagent/schema.js +30 -1
  181. package/dist/extensions/subagent/schema.js.map +1 -1
  182. package/dist/extensions/subagent/types.d.ts +2 -0
  183. package/dist/extensions/subagent/types.d.ts.map +1 -1
  184. package/dist/extensions/subagent/types.js.map +1 -1
  185. package/dist/extensions/todo/constants.d.ts +1 -1
  186. package/dist/extensions/todo/constants.d.ts.map +1 -1
  187. package/dist/extensions/todo/constants.js +8 -8
  188. package/dist/extensions/todo/constants.js.map +1 -1
  189. package/dist/extensions/todo/index.d.ts.map +1 -1
  190. package/dist/extensions/todo/index.js +1 -0
  191. package/dist/extensions/todo/index.js.map +1 -1
  192. package/dist/extensions/todo/schema.d.ts +30 -25
  193. package/dist/extensions/todo/schema.d.ts.map +1 -1
  194. package/dist/extensions/todo/schema.js +26 -20
  195. package/dist/extensions/todo/schema.js.map +1 -1
  196. package/dist/extensions/todo/state.d.ts +12 -8
  197. package/dist/extensions/todo/state.d.ts.map +1 -1
  198. package/dist/extensions/todo/state.js +255 -171
  199. package/dist/extensions/todo/state.js.map +1 -1
  200. package/dist/extensions/todo/view.d.ts +6 -2
  201. package/dist/extensions/todo/view.d.ts.map +1 -1
  202. package/dist/extensions/todo/view.js +234 -144
  203. package/dist/extensions/todo/view.js.map +1 -1
  204. package/dist/index.d.ts +1 -1
  205. package/dist/index.d.ts.map +1 -1
  206. package/dist/index.js.map +1 -1
  207. package/dist/modes/interactive/editor-host.d.ts +43 -0
  208. package/dist/modes/interactive/editor-host.d.ts.map +1 -0
  209. package/dist/modes/interactive/editor-host.js +83 -0
  210. package/dist/modes/interactive/editor-host.js.map +1 -0
  211. package/dist/modes/interactive/interactive-mode.d.ts +1 -11
  212. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  213. package/dist/modes/interactive/interactive-mode.js +300 -360
  214. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  215. package/dist/modes/interactive/tool-chat.d.ts +21 -0
  216. package/dist/modes/interactive/tool-chat.d.ts.map +1 -0
  217. package/dist/modes/interactive/tool-chat.js +58 -0
  218. package/dist/modes/interactive/tool-chat.js.map +1 -0
  219. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  220. package/dist/modes/rpc/rpc-mode.js +0 -2
  221. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  222. package/dist/utils/path-identity.d.ts +12 -0
  223. package/dist/utils/path-identity.d.ts.map +1 -0
  224. package/dist/utils/path-identity.js +63 -0
  225. package/dist/utils/path-identity.js.map +1 -0
  226. package/dist/utils/shell.d.ts +0 -1
  227. package/dist/utils/shell.d.ts.map +1 -1
  228. package/dist/utils/shell.js +0 -21
  229. package/dist/utils/shell.js.map +1 -1
  230. package/dist/utils/syntax-highlight.d.ts +2 -0
  231. package/dist/utils/syntax-highlight.d.ts.map +1 -1
  232. package/dist/utils/syntax-highlight.js +2 -0
  233. package/dist/utils/syntax-highlight.js.map +1 -1
  234. package/docs/bundled/extensions/background.md +1 -1
  235. package/docs/bundled/extensions/provider.md +7 -3
  236. package/docs/bundled/extensions/question.md +1 -0
  237. package/docs/bundled/extensions/subagent.md +3 -1
  238. package/docs/bundled/extensions/todo.md +35 -23
  239. package/docs/extensions.md +17 -5
  240. package/examples/extensions/README.md +34 -14
  241. package/examples/extensions/auto-commit-on-exit.ts +1 -1
  242. package/examples/extensions/bash-spawn-hook.ts +2 -2
  243. package/examples/extensions/bookmark.ts +1 -1
  244. package/examples/extensions/border-status-editor.ts +6 -1
  245. package/examples/extensions/built-in-tool-renderer.ts +2 -2
  246. package/examples/extensions/claude-rules.ts +1 -1
  247. package/examples/extensions/commands.ts +1 -1
  248. package/examples/extensions/confirm-destructive.ts +1 -1
  249. package/examples/extensions/custom-compaction.ts +2 -2
  250. package/examples/extensions/custom-footer.ts +1 -1
  251. package/examples/extensions/custom-header.ts +2 -2
  252. package/examples/extensions/custom-provider-anthropic/index.ts +6 -6
  253. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  254. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  255. package/examples/extensions/custom-provider-gitlab-duo/index.ts +3 -3
  256. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  257. package/examples/extensions/dirty-repo-guard.ts +1 -1
  258. package/examples/extensions/doom-overlay/index.ts +1 -1
  259. package/examples/extensions/dynamic-resources/dynamic.json +3 -1
  260. package/examples/extensions/dynamic-resources/index.ts +1 -1
  261. package/examples/extensions/dynamic-tools.ts +1 -1
  262. package/examples/extensions/entry-renderer.ts +1 -1
  263. package/examples/extensions/event-bus.ts +1 -1
  264. package/examples/extensions/file-trigger.ts +1 -1
  265. package/examples/extensions/git-checkpoint.ts +1 -1
  266. package/examples/extensions/git-merge-and-resolve.ts +1 -1
  267. package/examples/extensions/github-issue-autocomplete.ts +1 -1
  268. package/examples/extensions/gondolin/index.ts +9 -7
  269. package/examples/extensions/gondolin/package-lock.json +2 -2
  270. package/examples/extensions/gondolin/package.json +1 -1
  271. package/examples/extensions/handoff.ts +2 -2
  272. package/examples/extensions/hello.ts +1 -1
  273. package/examples/extensions/hidden-thinking-label.ts +1 -1
  274. package/examples/extensions/inline-bash.ts +1 -1
  275. package/examples/extensions/input-transform-streaming.ts +1 -1
  276. package/examples/extensions/input-transform.ts +1 -1
  277. package/examples/extensions/interactive-shell.ts +1 -1
  278. package/examples/extensions/mac-system-theme.ts +1 -1
  279. package/examples/extensions/message-renderer.ts +1 -1
  280. package/examples/extensions/minimal-mode.ts +2 -2
  281. package/examples/extensions/modal-editor.ts +1 -1
  282. package/examples/extensions/model-status.ts +1 -1
  283. package/examples/extensions/notify.ts +1 -1
  284. package/examples/extensions/overlay-qa-tests.ts +3 -3
  285. package/examples/extensions/overlay-test.ts +1 -1
  286. package/examples/extensions/permission-gate.ts +1 -1
  287. package/examples/extensions/pirate.ts +1 -1
  288. package/examples/extensions/plan-mode/index.ts +1 -1
  289. package/examples/extensions/preset.ts +2 -2
  290. package/examples/extensions/project-trust.ts +3 -3
  291. package/examples/extensions/prompt-customizer.ts +1 -1
  292. package/examples/extensions/protected-paths.ts +1 -1
  293. package/examples/extensions/provider-payload.ts +1 -1
  294. package/examples/extensions/qna.ts +2 -2
  295. package/examples/extensions/question.ts +1 -1
  296. package/examples/extensions/questionnaire.ts +1 -1
  297. package/examples/extensions/rainbow-editor.ts +1 -1
  298. package/examples/extensions/reload-runtime.ts +1 -1
  299. package/examples/extensions/rpc-demo.ts +1 -1
  300. package/examples/extensions/sandbox/index.ts +2 -2
  301. package/examples/extensions/sandbox/package-lock.json +2 -2
  302. package/examples/extensions/sandbox/package.json +1 -1
  303. package/examples/extensions/send-user-message.ts +1 -1
  304. package/examples/extensions/session-name.ts +1 -1
  305. package/examples/extensions/shutdown-command.ts +1 -1
  306. package/examples/extensions/snake.ts +1 -1
  307. package/examples/extensions/space-invaders.ts +1 -1
  308. package/examples/extensions/ssh.ts +2 -2
  309. package/examples/extensions/status-line.ts +1 -1
  310. package/examples/extensions/structured-output.ts +1 -1
  311. package/examples/extensions/summarize.ts +2 -3
  312. package/examples/extensions/system-prompt-header.ts +1 -1
  313. package/examples/extensions/tic-tac-toe.ts +1 -1
  314. package/examples/extensions/timed-confirm.ts +1 -1
  315. package/examples/extensions/titlebar-spinner.ts +1 -1
  316. package/examples/extensions/todo.ts +1 -1
  317. package/examples/extensions/tool-override.ts +1 -1
  318. package/examples/extensions/tools.ts +2 -2
  319. package/examples/extensions/trigger-compact.ts +1 -1
  320. package/examples/extensions/truncated-tool.ts +4 -4
  321. package/examples/extensions/widget-placement.ts +1 -1
  322. package/examples/extensions/with-deps/index.ts +1 -1
  323. package/examples/extensions/with-deps/package-lock.json +2 -2
  324. package/examples/extensions/with-deps/package.json +1 -1
  325. package/examples/extensions/working-indicator.ts +1 -1
  326. package/examples/extensions/working-message-test.ts +1 -1
  327. package/npm-shrinkwrap.json +2 -2
  328. package/package.json +1 -1
@@ -8,7 +8,7 @@ import * as os from "node:os";
8
8
  import * as path from "node:path";
9
9
  import { isRetryableAssistantError, } from "@earendil-works/pi-ai/compat";
10
10
  import * as TuiLayouts from "@earendil-works/pi-tui";
11
- import { CombinedAutocompleteProvider, Container, fuzzyFilter, Markdown, matchesKey, Spacer, setCapabilityOverrides, setKeybindings, Text, TruncatedText, TuiAltScreen, TuiMainScreen, visibleWidth, } from "@earendil-works/pi-tui";
11
+ import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Markdown, matchesKey, Spacer, setCapabilityOverrides, setKeybindings, Text, TruncatedText, TuiAltScreen, TuiMainScreen, visibleWidth, } from "@earendil-works/pi-tui";
12
12
  import chalk from "chalk";
13
13
  import { spawn } from "child_process";
14
14
  import { APP_NAME, APP_TITLE, CONFIG_DIR_NAME, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, INSTALL_TELEMETRY_URL, UPDATE_CHANGELOG_HINT, VERSION, } from "../../config.js";
@@ -39,6 +39,7 @@ import { parseGitUrl } from "../../utils/git.js";
39
39
  import { getCwdRelativePath } from "../../utils/paths.js";
40
40
  import { getPiUserAgent } from "../../utils/pi-user-agent.js";
41
41
  import { killTrackedDetachedChildren } from "../../utils/shell.js";
42
+ import { loadAllHighlightLanguages } from "../../utils/syntax-highlight.js";
42
43
  import { ensureTool } from "../../utils/tools-manager.js";
43
44
  import { checkForNewPiVersion } from "../../utils/version-check.js";
44
45
  import { reportBug } from "./bug-report.js";
@@ -58,7 +59,7 @@ import { ExtensionEditorComponent } from "./components/extension-editor.js";
58
59
  import { ExtensionInputComponent } from "./components/extension-input.js";
59
60
  import { ExtensionSelectorComponent } from "./components/extension-selector.js";
60
61
  import { FooterComponent, formatTokens } from "./components/footer.js";
61
- import { formatKeyText, keyDisplayText, keyHint, keyLabel, keyText, rawKeyHint, } from "./components/keybinding-hints.js";
62
+ import { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
62
63
  import { LoginDialogComponent } from "./components/login-dialog.js";
63
64
  import { createMermaidMarkdownTransformer } from "./components/mermaid.js";
64
65
  import { ModelSelectorComponent } from "./components/model-selector.js";
@@ -70,17 +71,18 @@ import { SkillInvocationMessageComponent } from "./components/skill-invocation-m
70
71
  import { BranchSummaryStatusIndicator, CompactionStatusIndicator, IdleStatus, RetryStatusIndicator, WorkingStatusIndicator, } from "./components/status-indicator.js";
71
72
  import { ThinkingSelectorComponent } from "./components/thinking-selector.js";
72
73
  import { ToolExecutionComponent } from "./components/tool-execution.js";
73
- import { ToolGroupComponent } from "./components/tool-group.js";
74
74
  import { TreeSelectorComponent } from "./components/tree-selector.js";
75
75
  import { TrustSelectorComponent } from "./components/trust-selector.js";
76
76
  import { UserMessageComponent } from "./components/user-message.js";
77
77
  import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
78
+ import { InteractiveEditorHost } from "./editor-host.js";
78
79
  import { editInExternalEditor } from "./external-editor.js";
79
80
  import { refreshModelCatalogs } from "./model-catalog-refresh.js";
80
81
  import { getModelSearchText } from "./model-search.js";
81
82
  import { shareSession } from "./session-share.js";
82
83
  import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, onThemeChange, setRegisteredThemes, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
83
84
  import { InteractiveThemeController } from "./theme/theme-controller.js";
85
+ import { appendToolRow, disposeChatRows, isToolChatComponent, PendingToolMap, ToolChatContainer, toolDetachHint, } from "./tool-chat.js";
84
86
  import { createInteractiveTui, createInteractiveTuiReference } from "./tui-renderer.js";
85
87
  export { createInteractiveTui, createInteractiveTuiReference } from "./tui-renderer.js";
86
88
  function isWorkingStatusEditor(editor) {
@@ -207,10 +209,6 @@ function formatLoginProviderCompletionDescription(provider) {
207
209
  const authTypes = provider.authTypes.map(formatAuthSelectorProviderType).join("/");
208
210
  return provider.name === provider.id ? authTypes : `${provider.name} · ${authTypes}`;
209
211
  }
210
- /** Chat children that host tool presentation and share image display settings. */
211
- function isToolChatComponent(child) {
212
- return child instanceof ToolExecutionComponent || child instanceof ToolGroupComponent;
213
- }
214
212
  export class InteractiveMode {
215
213
  // Convenience accessors
216
214
  get session() {
@@ -252,7 +250,19 @@ export class InteractiveMode {
252
250
  this.entriesRenderedByBoundaryCompaction = new Set();
253
251
  this.streamingMessage = undefined;
254
252
  // Tool execution tracking: toolCallId -> component
255
- this.pendingTools = new Map();
253
+ this.pendingTools = new PendingToolMap();
254
+ this.editorHost = new InteractiveEditorHost({
255
+ isSessionCommand: (name) => this.session.extensionRunner.getCommand(name) !== undefined ||
256
+ this.session.promptTemplates.some((template) => template.name === name) ||
257
+ this.skillCommands.has(name),
258
+ getEditor: () => this.editor,
259
+ getFocusedComponent: () => this.renderer.getFocusedComponent(),
260
+ hasOverlay: () => this.ui.hasOverlay(),
261
+ addTerminalInputListener: (handler) => this.addExtensionTerminalInputListener(handler),
262
+ setEditorText: (text) => this.editor.setText(text),
263
+ requestRender: () => this.ui.requestRender(),
264
+ showError: (message) => this.showError(message),
265
+ });
256
266
  // Tool output expansion state
257
267
  this.toolOutputExpanded = false;
258
268
  // Thinking block visibility state
@@ -281,7 +291,6 @@ export class InteractiveMode {
281
291
  this.extensionSelector = undefined;
282
292
  this.extensionInput = undefined;
283
293
  this.extensionEditor = undefined;
284
- this.extensionEditorSubmitHandlers = new Set();
285
294
  this.extensionTerminalInputSubscriptions = new Set();
286
295
  // Extension widgets (components rendered above/below the editor)
287
296
  this.extensionWidgetsAbove = new Map();
@@ -326,7 +335,7 @@ export class InteractiveMode {
326
335
  this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
327
336
  this.headerContainer = new Container();
328
337
  this.loadedResourcesContainer = new Container();
329
- this.chatContainer = new Container();
338
+ this.chatContainer = new ToolChatContainer();
330
339
  this.documentContainer = new Container();
331
340
  this.documentContainer.addChild(this.headerContainer);
332
341
  this.documentContainer.addChild(this.loadedResourcesContainer);
@@ -585,7 +594,6 @@ export class InteractiveMode {
585
594
  return true;
586
595
  nextUi.start();
587
596
  this.themeController.rebindTui();
588
- this.setupBackgroundInputListener();
589
597
  this.rebindExtensionTerminalInputListeners();
590
598
  if (restoreProgress &&
591
599
  this.settingsManager.getShowTerminalProgress() &&
@@ -722,6 +730,14 @@ export class InteractiveMode {
722
730
  });
723
731
  // Initialize available provider count for footer display
724
732
  await this.updateAvailableProviderCount();
733
+ // Flush the completed startup state before loading the remaining syntax grammars.
734
+ this.ui.renderNow();
735
+ void loadAllHighlightLanguages().then(() => {
736
+ if (!this.isInitialized)
737
+ return;
738
+ this.ui.invalidate();
739
+ this.ui.requestRender();
740
+ });
725
741
  }
726
742
  /**
727
743
  * Update terminal title with session name and cwd.
@@ -923,15 +939,14 @@ export class InteractiveMode {
923
939
  return undefined;
924
940
  }
925
941
  reportInstallTelemetry(version) {
926
- if (!INSTALL_TELEMETRY_URL) {
927
- return;
928
- }
929
942
  if (process.env.PI_OFFLINE) {
930
943
  return;
931
944
  }
932
945
  if (!isInstallTelemetryEnabled(this.settingsManager)) {
933
946
  return;
934
947
  }
948
+ if (!INSTALL_TELEMETRY_URL)
949
+ return;
935
950
  void fetch(`${INSTALL_TELEMETRY_URL}?version=${encodeURIComponent(version)}`, {
936
951
  headers: {
937
952
  "User-Agent": getPiUserAgent(version),
@@ -1438,7 +1453,7 @@ export class InteractiveMode {
1438
1453
  if (result.cancelled) {
1439
1454
  return { cancelled: true };
1440
1455
  }
1441
- this.clearChat();
1456
+ this.chatContainer.clear();
1442
1457
  this.renderInitialMessages();
1443
1458
  if (result.editorText && !this.editor.getText().trim()) {
1444
1459
  this.editor.setText(result.editorText);
@@ -1575,32 +1590,14 @@ export class InteractiveMode {
1575
1590
  return;
1576
1591
  this.suggestBugReport();
1577
1592
  }
1578
- disposeChatToolComponents() {
1579
- for (const child of this.chatContainer.children) {
1580
- const disposable = child;
1581
- if (typeof disposable.dispose === "function") {
1582
- disposable.dispose();
1583
- }
1584
- }
1585
- }
1586
- clearChat() {
1587
- this.disposeChatToolComponents();
1588
- this.chatContainer.clear();
1589
- }
1590
- clearPendingTools() {
1591
- for (const component of this.pendingTools.values()) {
1592
- component.dispose();
1593
- }
1594
- this.pendingTools.clear();
1595
- }
1596
1593
  renderCurrentSessionState() {
1597
1594
  this.loadedResourcesContainer.clear();
1598
- this.clearChat();
1595
+ this.chatContainer.clear();
1599
1596
  this.pendingMessagesContainer.clear();
1600
1597
  this.compactionQueuedMessages = [];
1601
1598
  this.streamingComponent = undefined;
1602
1599
  this.streamingMessage = undefined;
1603
- this.clearPendingTools();
1600
+ this.pendingTools.clear();
1604
1601
  this.renderInitialMessages();
1605
1602
  }
1606
1603
  /**
@@ -1613,39 +1610,6 @@ export class InteractiveMode {
1613
1610
  getRegisteredToolDefinition(toolName) {
1614
1611
  return withBuiltInRenderers(toolName, this.session.getToolDefinition(toolName));
1615
1612
  }
1616
- createToolExecutionComponent(toolName, toolCallId, args) {
1617
- const component = new ToolExecutionComponent(toolName, toolCallId, args, {
1618
- showImages: this.settingsManager.getShowImages(),
1619
- imageWidthCells: this.settingsManager.getImageWidthCells(),
1620
- detachHint: {
1621
- // Everything BackgroundService.detachForeground() can move: shell tools and subagents.
1622
- isDetachable: (name) => name === "bash" || name === "powershell" || name === "subagent",
1623
- keyLabel: () => keyLabel("app.backgroundTasks.detach", { keybindings: this.keybindings }),
1624
- },
1625
- }, this.getRegisteredToolDefinition(toolName), this.ui, this.sessionManager.getCwd());
1626
- component.setExpanded(this.toolOutputExpanded);
1627
- return component;
1628
- }
1629
- appendToolExecutionComponent(component) {
1630
- const toolGroup = component.toolGroup?.trim();
1631
- if (!toolGroup) {
1632
- this.chatContainer.addChild(component);
1633
- return;
1634
- }
1635
- const children = this.chatContainer.children;
1636
- const lastChild = children[children.length - 1];
1637
- if (lastChild instanceof ToolGroupComponent && lastChild.toolGroup === toolGroup) {
1638
- lastChild.addTool(component);
1639
- return;
1640
- }
1641
- if (lastChild instanceof ToolExecutionComponent && lastChild.toolGroup?.trim() === toolGroup) {
1642
- const group = new ToolGroupComponent(toolGroup, [lastChild, component]);
1643
- group.setExpanded(this.toolOutputExpanded);
1644
- children[children.length - 1] = group;
1645
- return;
1646
- }
1647
- this.chatContainer.addChild(component);
1648
- }
1649
1613
  getMarkdownTransformers() {
1650
1614
  return [this.mermaidMarkdownTransformer, ...this.session.extensionRunner.getMarkdownTransformers()];
1651
1615
  }
@@ -1801,7 +1765,7 @@ export class InteractiveMode {
1801
1765
  this.renderWidgets();
1802
1766
  }
1803
1767
  resetExtensionUI() {
1804
- this.extensionEditorSubmitHandlers.clear();
1768
+ this.editorHost.clear();
1805
1769
  if (this.extensionSelector) {
1806
1770
  this.hideExtensionSelector();
1807
1771
  }
@@ -1920,19 +1884,8 @@ export class InteractiveMode {
1920
1884
  }
1921
1885
  this.ui.requestRender();
1922
1886
  }
1923
- addExtensionTerminalInputListener(handler, options) {
1924
- const scopedHandler = (data) => {
1925
- if (options?.scope === "editor") {
1926
- const editor = this.editor;
1927
- if (this.renderer.getFocusedComponent() !== editor ||
1928
- this.ui.hasOverlay() ||
1929
- editor.isShowingAutocomplete?.()) {
1930
- return undefined;
1931
- }
1932
- }
1933
- return handler(data);
1934
- };
1935
- const subscription = { handler: scopedHandler, unsubscribe: this.ui.addInputListener(scopedHandler) };
1887
+ addExtensionTerminalInputListener(handler) {
1888
+ const subscription = { handler, unsubscribe: this.ui.addInputListener(handler) };
1936
1889
  this.extensionTerminalInputSubscriptions.add(subscription);
1937
1890
  return () => {
1938
1891
  subscription.unsubscribe();
@@ -1973,11 +1926,8 @@ export class InteractiveMode {
1973
1926
  confirm: (title, message, opts) => this.showExtensionConfirm(title, message, opts),
1974
1927
  input: (title, placeholder, opts) => this.showExtensionInput(title, placeholder, opts),
1975
1928
  notify: (message, type) => this.showExtensionNotify(message, type),
1976
- onTerminalInput: (handler, options) => this.addExtensionTerminalInputListener(handler, options),
1977
- onEditorSubmit: (handler) => {
1978
- this.extensionEditorSubmitHandlers.add(handler);
1979
- return () => this.extensionEditorSubmitHandlers.delete(handler);
1980
- },
1929
+ onTerminalInput: (handler) => this.addExtensionTerminalInputListener(handler),
1930
+ editorHost: this.editorHost,
1981
1931
  setStatus: (key, text) => this.setExtensionStatus(key, text),
1982
1932
  setWorkingMessage: (message) => {
1983
1933
  this.workingMessage = message;
@@ -1996,10 +1946,6 @@ export class InteractiveMode {
1996
1946
  pasteToEditor: (text) => this.editor.handleInput(`\x1b[200~${text}\x1b[201~`),
1997
1947
  setEditorText: (text) => this.editor.setText(text),
1998
1948
  getEditorText: () => this.editor.getExpandedText?.() ?? this.editor.getText(),
1999
- getEditorCursor: () => {
2000
- const editor = this.editor;
2001
- return editor.getCursor?.();
2002
- },
2003
1949
  editor: (title, prefill) => this.showExtensionEditor(title, prefill),
2004
1950
  addAutocompleteProvider: (factory) => {
2005
1951
  this.autocompleteProviderWrappers.push(factory);
@@ -2332,24 +2278,10 @@ export class InteractiveMode {
2332
2278
  }
2333
2279
  this.ui.requestRender();
2334
2280
  }
2335
- setupBackgroundInputListener() {
2336
- this.backgroundInputUnsubscribe?.();
2337
- this.backgroundInputUnsubscribe = this.ui.addInputListener((data) => {
2338
- if (!this.keybindings.matches(data, "app.backgroundTasks.detach"))
2339
- return undefined;
2340
- if (this.isShuttingDown)
2341
- return { consume: true };
2342
- const count = this.session.background.detachForeground();
2343
- // Nothing can move: let the key fall through to editor bindings instead of
2344
- // spending it on a "nothing happened" status line.
2345
- if (count === 0)
2346
- return undefined;
2347
- this.showStatus(`Moved ${count} execution${count === 1 ? "" : "s"} to the background. Use /bg to manage tasks.`);
2348
- return { consume: true };
2349
- });
2350
- }
2281
+ // =========================================================================
2282
+ // Key Handlers
2283
+ // =========================================================================
2351
2284
  setupKeyHandlers() {
2352
- this.setupBackgroundInputListener();
2353
2285
  // Set up handlers on defaultEditor - they use this.editor for text access
2354
2286
  // so they work correctly regardless of which editor is active
2355
2287
  this.defaultEditor.onEscape = () => {
@@ -2461,240 +2393,203 @@ export class InteractiveMode {
2461
2393
  this.showStatus("Startup is still in progress");
2462
2394
  }
2463
2395
  setupEditorSubmitHandler() {
2464
- this.defaultEditor.onSubmit = (text) => this.handleEditorSubmit(text, "steer");
2465
- }
2466
- classifyEditorSubmit(text) {
2467
- if (text.startsWith("!"))
2468
- return "bash";
2469
- if (!text.startsWith("/"))
2470
- return "prompt";
2471
- const name = text.slice(1).split(/\s/, 1)[0];
2472
- return BUILTIN_SLASH_COMMANDS.some((command) => command.name === name) ||
2473
- ["debug", "arminsayshi", "dementedelves"].includes(name) ||
2474
- this.session.extensionRunner.getCommand(name) ||
2475
- this.session.promptTemplates.some((template) => template.name === name) ||
2476
- this.skillCommands.has(name)
2477
- ? "command"
2478
- : "prompt";
2479
- }
2480
- interceptEditorSubmit(text, mode) {
2481
- if (this.extensionEditorSubmitHandlers.size === 0)
2482
- return false;
2483
- const event = { text, mode, kind: this.classifyEditorSubmit(text) };
2484
- for (const handler of this.extensionEditorSubmitHandlers) {
2485
- try {
2486
- const result = handler(event);
2487
- if (!result?.handled)
2488
- continue;
2489
- this.editor.setText(result.editorText ?? "");
2490
- this.ui.requestRender();
2491
- return true;
2396
+ this.defaultEditor.onSubmit = async (text) => {
2397
+ text = text.trim();
2398
+ if (!text)
2399
+ return;
2400
+ if (this.editorHost.intercept(text, "steer"))
2401
+ return;
2402
+ // Handle commands
2403
+ if (text === "/settings") {
2404
+ this.showSettingsSelector();
2405
+ this.editor.setText("");
2406
+ return;
2492
2407
  }
2493
- catch (error) {
2494
- // A failed interceptor must not accidentally send private input to the main agent.
2495
- this.editor.setText(text);
2496
- this.showError(`Editor submit handler failed: ${error instanceof Error ? error.message : String(error)}`);
2497
- return true;
2408
+ if (text === "/scoped-models") {
2409
+ this.editor.setText("");
2410
+ await this.showModelsSelector();
2411
+ return;
2498
2412
  }
2499
- }
2500
- return false;
2501
- }
2502
- async handleEditorSubmit(text, mode) {
2503
- text = text.trim();
2504
- if (!text)
2505
- return;
2506
- if (this.interceptEditorSubmit(text, mode))
2507
- return;
2508
- // Handle commands
2509
- if (text === "/settings") {
2510
- this.showSettingsSelector();
2511
- this.editor.setText("");
2512
- return;
2513
- }
2514
- if (text === "/scoped-models") {
2515
- this.editor.setText("");
2516
- await this.showModelsSelector();
2517
- return;
2518
- }
2519
- if (text === "/model" || text.startsWith("/model ")) {
2520
- const searchTerm = text.startsWith("/model ") ? text.slice(7).trim() : undefined;
2521
- this.editor.setText("");
2522
- await this.handleModelCommand(searchTerm);
2523
- return;
2524
- }
2525
- if (text === "/thinking" || text.startsWith("/thinking ")) {
2526
- const searchTerm = text.startsWith("/thinking ") ? text.slice(10).trim() : undefined;
2527
- this.editor.setText("");
2528
- this.handleThinkingCommand(searchTerm);
2529
- return;
2530
- }
2531
- if (text === "/export" || text.startsWith("/export ")) {
2532
- await this.handleExportCommand(text);
2533
- this.editor.setText("");
2534
- return;
2535
- }
2536
- if (text === "/import" || text.startsWith("/import ")) {
2537
- await this.handleImportCommand(text);
2538
- this.editor.setText("");
2539
- return;
2540
- }
2541
- if (text === "/share") {
2542
- await this.handleShareCommand();
2543
- this.editor.setText("");
2544
- return;
2545
- }
2546
- if (text === "/bug" || text.startsWith("/bug ")) {
2547
- const hint = text.slice("/bug".length).trim();
2548
- this.editor.setText("");
2549
- await this.handleBugCommand(hint ? hint : undefined);
2550
- return;
2551
- }
2552
- if (text === "/copy") {
2553
- await this.handleCopyCommand();
2554
- this.editor.setText("");
2555
- return;
2556
- }
2557
- if (text === "/name" || text.startsWith("/name ")) {
2558
- this.handleNameCommand(text);
2559
- this.editor.setText("");
2560
- return;
2561
- }
2562
- if (text === "/session") {
2563
- this.handleSessionCommand();
2564
- this.editor.setText("");
2565
- return;
2566
- }
2567
- if (text === "/changelog") {
2568
- this.handleChangelogCommand();
2569
- this.editor.setText("");
2570
- return;
2571
- }
2572
- if (text === "/hotkeys") {
2573
- this.handleHotkeysCommand();
2574
- this.editor.setText("");
2575
- return;
2576
- }
2577
- if (text === "/fork") {
2578
- this.showUserMessageSelector();
2579
- this.editor.setText("");
2580
- return;
2581
- }
2582
- if (text === "/clone") {
2583
- this.editor.setText("");
2584
- await this.handleCloneCommand();
2585
- return;
2586
- }
2587
- if (text === "/tree") {
2588
- this.showTreeSelector();
2589
- this.editor.setText("");
2590
- return;
2591
- }
2592
- if (text === "/trust") {
2593
- this.showTrustSelector();
2594
- this.editor.setText("");
2595
- return;
2596
- }
2597
- if (text === "/login" || text.startsWith("/login ")) {
2598
- const providerRef = text.startsWith("/login ") ? text.slice(7).trim() : undefined;
2599
- this.editor.setText("");
2600
- await this.handleLoginCommand(providerRef);
2601
- return;
2602
- }
2603
- if (text === "/logout") {
2604
- this.showOAuthSelector("logout");
2605
- this.editor.setText("");
2606
- return;
2607
- }
2608
- if (text === "/new") {
2609
- this.editor.setText("");
2610
- await this.handleClearCommand();
2611
- return;
2612
- }
2613
- if (text === "/compact" || text.startsWith("/compact ")) {
2614
- const customInstructions = text.startsWith("/compact ") ? text.slice(9).trim() : undefined;
2615
- this.editor.setText("");
2616
- await this.handleCompactCommand(customInstructions);
2617
- return;
2618
- }
2619
- if (text === "/reload") {
2620
- this.editor.setText("");
2621
- await this.handleReloadCommand();
2622
- return;
2623
- }
2624
- if (text === "/debug") {
2625
- this.handleDebugCommand();
2626
- this.editor.setText("");
2627
- return;
2628
- }
2629
- if (text === "/arminsayshi") {
2630
- this.handleArminSaysHi();
2631
- this.editor.setText("");
2632
- return;
2633
- }
2634
- if (text === "/dementedelves") {
2635
- this.handleDementedDelves();
2636
- this.editor.setText("");
2637
- return;
2638
- }
2639
- if (text === "/resume") {
2640
- this.showSessionSelector();
2641
- this.editor.setText("");
2642
- return;
2643
- }
2644
- if (text === "/quit") {
2645
- this.editor.setText("");
2646
- await this.shutdown();
2647
- return;
2648
- }
2649
- // Handle bash command (! for normal, !! for excluded from context)
2650
- if (text.startsWith("!")) {
2651
- const isExcluded = text.startsWith("!!");
2652
- const command = isExcluded ? text.slice(2).trim() : text.slice(1).trim();
2653
- if (command) {
2654
- if (this.session.isBashRunning) {
2655
- this.showWarning("A bash command is already running. Press Esc to cancel it first.");
2656
- this.editor.setText(text);
2413
+ if (text === "/model" || text.startsWith("/model ")) {
2414
+ const searchTerm = text.startsWith("/model ") ? text.slice(7).trim() : undefined;
2415
+ this.editor.setText("");
2416
+ await this.handleModelCommand(searchTerm);
2417
+ return;
2418
+ }
2419
+ if (text === "/thinking" || text.startsWith("/thinking ")) {
2420
+ const searchTerm = text.startsWith("/thinking ") ? text.slice(10).trim() : undefined;
2421
+ this.editor.setText("");
2422
+ this.handleThinkingCommand(searchTerm);
2423
+ return;
2424
+ }
2425
+ if (text === "/export" || text.startsWith("/export ")) {
2426
+ await this.handleExportCommand(text);
2427
+ this.editor.setText("");
2428
+ return;
2429
+ }
2430
+ if (text === "/import" || text.startsWith("/import ")) {
2431
+ await this.handleImportCommand(text);
2432
+ this.editor.setText("");
2433
+ return;
2434
+ }
2435
+ if (text === "/share") {
2436
+ await this.handleShareCommand();
2437
+ this.editor.setText("");
2438
+ return;
2439
+ }
2440
+ if (text === "/bug" || text.startsWith("/bug ")) {
2441
+ const hint = text.slice("/bug".length).trim();
2442
+ this.editor.setText("");
2443
+ await this.handleBugCommand(hint ? hint : undefined);
2444
+ return;
2445
+ }
2446
+ if (text === "/copy") {
2447
+ await this.handleCopyCommand();
2448
+ this.editor.setText("");
2449
+ return;
2450
+ }
2451
+ if (text === "/name" || text.startsWith("/name ")) {
2452
+ this.handleNameCommand(text);
2453
+ this.editor.setText("");
2454
+ return;
2455
+ }
2456
+ if (text === "/session") {
2457
+ this.handleSessionCommand();
2458
+ this.editor.setText("");
2459
+ return;
2460
+ }
2461
+ if (text === "/changelog") {
2462
+ this.handleChangelogCommand();
2463
+ this.editor.setText("");
2464
+ return;
2465
+ }
2466
+ if (text === "/hotkeys") {
2467
+ this.handleHotkeysCommand();
2468
+ this.editor.setText("");
2469
+ return;
2470
+ }
2471
+ if (text === "/fork") {
2472
+ this.showUserMessageSelector();
2473
+ this.editor.setText("");
2474
+ return;
2475
+ }
2476
+ if (text === "/clone") {
2477
+ this.editor.setText("");
2478
+ await this.handleCloneCommand();
2479
+ return;
2480
+ }
2481
+ if (text === "/tree") {
2482
+ this.showTreeSelector();
2483
+ this.editor.setText("");
2484
+ return;
2485
+ }
2486
+ if (text === "/trust") {
2487
+ this.showTrustSelector();
2488
+ this.editor.setText("");
2489
+ return;
2490
+ }
2491
+ if (text === "/login" || text.startsWith("/login ")) {
2492
+ const providerRef = text.startsWith("/login ") ? text.slice(7).trim() : undefined;
2493
+ this.editor.setText("");
2494
+ await this.handleLoginCommand(providerRef);
2495
+ return;
2496
+ }
2497
+ if (text === "/logout") {
2498
+ this.showOAuthSelector("logout");
2499
+ this.editor.setText("");
2500
+ return;
2501
+ }
2502
+ if (text === "/new") {
2503
+ this.editor.setText("");
2504
+ await this.handleClearCommand();
2505
+ return;
2506
+ }
2507
+ if (text === "/compact" || text.startsWith("/compact ")) {
2508
+ const customInstructions = text.startsWith("/compact ") ? text.slice(9).trim() : undefined;
2509
+ this.editor.setText("");
2510
+ await this.handleCompactCommand(customInstructions);
2511
+ return;
2512
+ }
2513
+ if (text === "/reload") {
2514
+ this.editor.setText("");
2515
+ await this.handleReloadCommand();
2516
+ return;
2517
+ }
2518
+ if (text === "/debug") {
2519
+ this.handleDebugCommand();
2520
+ this.editor.setText("");
2521
+ return;
2522
+ }
2523
+ if (text === "/arminsayshi") {
2524
+ this.handleArminSaysHi();
2525
+ this.editor.setText("");
2526
+ return;
2527
+ }
2528
+ if (text === "/dementedelves") {
2529
+ this.handleDementedDelves();
2530
+ this.editor.setText("");
2531
+ return;
2532
+ }
2533
+ if (text === "/resume") {
2534
+ this.showSessionSelector();
2535
+ this.editor.setText("");
2536
+ return;
2537
+ }
2538
+ if (text === "/quit") {
2539
+ this.editor.setText("");
2540
+ await this.shutdown();
2541
+ return;
2542
+ }
2543
+ // Handle bash command (! for normal, !! for excluded from context)
2544
+ if (text.startsWith("!")) {
2545
+ const isExcluded = text.startsWith("!!");
2546
+ const command = isExcluded ? text.slice(2).trim() : text.slice(1).trim();
2547
+ if (command) {
2548
+ if (this.session.isBashRunning) {
2549
+ this.showWarning("A bash command is already running. Press Esc to cancel it first.");
2550
+ this.editor.setText(text);
2551
+ return;
2552
+ }
2553
+ this.editor.addToHistory?.(text);
2554
+ await this.handleBashCommand(command, isExcluded);
2555
+ this.isBashMode = false;
2556
+ this.updateEditorBorderColor();
2657
2557
  return;
2658
2558
  }
2659
- this.editor.addToHistory?.(text);
2660
- await this.handleBashCommand(command, isExcluded);
2661
- this.isBashMode = false;
2662
- this.updateEditorBorderColor();
2559
+ }
2560
+ // Queue input during compaction (extension commands execute immediately)
2561
+ if (this.session.isCompacting) {
2562
+ if (this.isExtensionCommand(text)) {
2563
+ this.editor.addToHistory?.(text);
2564
+ this.editor.setText("");
2565
+ await this.session.prompt(text);
2566
+ }
2567
+ else {
2568
+ this.queueCompactionMessage(text, "steer");
2569
+ }
2663
2570
  return;
2664
2571
  }
2665
- }
2666
- // Queue input during compaction (extension commands execute immediately)
2667
- if (this.session.isCompacting) {
2668
- if (this.isExtensionCommand(text)) {
2572
+ // If streaming, use prompt() with steer behavior
2573
+ // This handles extension commands (execute immediately), prompt template expansion, and queueing
2574
+ if (this.session.isStreaming) {
2669
2575
  this.editor.addToHistory?.(text);
2670
2576
  this.editor.setText("");
2671
- await this.session.prompt(text);
2577
+ await this.session.prompt(text, { streamingBehavior: "steer" });
2578
+ this.updatePendingMessagesDisplay();
2579
+ this.ui.requestRender();
2580
+ return;
2581
+ }
2582
+ // Normal message submission
2583
+ // First, move any pending bash components to chat
2584
+ this.flushPendingBashComponents();
2585
+ if (this.onInputCallback) {
2586
+ this.onInputCallback(text);
2672
2587
  }
2673
2588
  else {
2674
- this.queueCompactionMessage(text, mode);
2589
+ this.pendingUserInputs.push(text);
2675
2590
  }
2676
- return;
2677
- }
2678
- // If streaming, use prompt() with steer behavior
2679
- // This handles extension commands (execute immediately), prompt template expansion, and queueing
2680
- if (this.session.isStreaming) {
2681
2591
  this.editor.addToHistory?.(text);
2682
- this.editor.setText("");
2683
- await this.session.prompt(text, { streamingBehavior: mode });
2684
- this.updatePendingMessagesDisplay();
2685
- this.ui.requestRender();
2686
- return;
2687
- }
2688
- // Normal message submission
2689
- // First, move any pending bash components to chat
2690
- this.flushPendingBashComponents();
2691
- if (this.onInputCallback) {
2692
- this.onInputCallback(text);
2693
- }
2694
- else {
2695
- this.pendingUserInputs.push(text);
2696
- }
2697
- this.editor.addToHistory?.(text);
2592
+ };
2698
2593
  }
2699
2594
  subscribeToAgent() {
2700
2595
  this.unsubscribe = this.session.subscribe(async (event) => {
@@ -2708,7 +2603,7 @@ export class InteractiveMode {
2708
2603
  this.footer.invalidate();
2709
2604
  switch (event.type) {
2710
2605
  case "agent_start":
2711
- this.clearPendingTools();
2606
+ this.pendingTools.clear();
2712
2607
  // Restore main escape handler if retry handler is still active
2713
2608
  // (retry success event fires later, but we need main handler now)
2714
2609
  if (this.retryEscapeHandler) {
@@ -2808,8 +2703,13 @@ export class InteractiveMode {
2808
2703
  for (const content of this.streamingMessage.content) {
2809
2704
  if (content.type === "toolCall") {
2810
2705
  if (!this.pendingTools.has(content.id)) {
2811
- const component = this.createToolExecutionComponent(content.name, content.id, content.arguments);
2812
- this.appendToolExecutionComponent(component);
2706
+ const component = new ToolExecutionComponent(content.name, content.id, content.arguments, {
2707
+ showImages: this.settingsManager.getShowImages(),
2708
+ imageWidthCells: this.settingsManager.getImageWidthCells(),
2709
+ detachHint: toolDetachHint(this.keybindings),
2710
+ }, this.getRegisteredToolDefinition(content.name), this.ui, this.sessionManager.getCwd());
2711
+ component.setExpanded(this.toolOutputExpanded);
2712
+ appendToolRow(this.chatContainer, component, this.toolOutputExpanded);
2813
2713
  this.pendingTools.set(content.id, component);
2814
2714
  }
2815
2715
  else {
@@ -2871,8 +2771,13 @@ export class InteractiveMode {
2871
2771
  case "tool_execution_start": {
2872
2772
  let component = this.pendingTools.get(event.toolCallId);
2873
2773
  if (!component) {
2874
- component = this.createToolExecutionComponent(event.toolName, event.toolCallId, event.args);
2875
- this.appendToolExecutionComponent(component);
2774
+ component = new ToolExecutionComponent(event.toolName, event.toolCallId, event.args, {
2775
+ showImages: this.settingsManager.getShowImages(),
2776
+ imageWidthCells: this.settingsManager.getImageWidthCells(),
2777
+ detachHint: toolDetachHint(this.keybindings),
2778
+ }, this.getRegisteredToolDefinition(event.toolName), this.ui, this.sessionManager.getCwd());
2779
+ component.setExpanded(this.toolOutputExpanded);
2780
+ appendToolRow(this.chatContainer, component, this.toolOutputExpanded);
2876
2781
  this.pendingTools.set(event.toolCallId, component);
2877
2782
  }
2878
2783
  component.markExecutionStarted();
@@ -2906,7 +2811,7 @@ export class InteractiveMode {
2906
2811
  this.streamingComponent = undefined;
2907
2812
  this.streamingMessage = undefined;
2908
2813
  }
2909
- this.clearPendingTools();
2814
+ this.pendingTools.clear();
2910
2815
  this.ui.requestRender();
2911
2816
  break;
2912
2817
  case "agent_settled":
@@ -2947,7 +2852,7 @@ export class InteractiveMode {
2947
2852
  if (entries[0]?.type !== "compaction") {
2948
2853
  throw new Error("Completed compaction is missing from the session context");
2949
2854
  }
2950
- this.clearChat();
2855
+ this.chatContainer.clear();
2951
2856
  // The latest compaction is prepended for model context; append it below at its chronological position.
2952
2857
  this.renderSessionEntries(entries.slice(1));
2953
2858
  this.addMessageToChat(createCompactionSummaryMessage(event.result.summary, event.result.tokensBefore, new Date().toISOString()));
@@ -3165,7 +3070,7 @@ export class InteractiveMode {
3165
3070
  }
3166
3071
  }
3167
3072
  renderSessionItems(items, options = {}) {
3168
- this.clearPendingTools();
3073
+ this.pendingTools.clear();
3169
3074
  const renderedPendingTools = new Map();
3170
3075
  // Cache misses are not persisted, unlike successful cache-warming usage.
3171
3076
  // Re-derive them and inject them after the assistant messages that paid for them.
@@ -3196,8 +3101,13 @@ export class InteractiveMode {
3196
3101
  // Render tool call components
3197
3102
  for (const content of message.content) {
3198
3103
  if (content.type === "toolCall") {
3199
- const component = this.createToolExecutionComponent(content.name, content.id, content.arguments);
3200
- this.appendToolExecutionComponent(component);
3104
+ const component = new ToolExecutionComponent(content.name, content.id, content.arguments, {
3105
+ showImages: this.settingsManager.getShowImages(),
3106
+ imageWidthCells: this.settingsManager.getImageWidthCells(),
3107
+ detachHint: toolDetachHint(this.keybindings),
3108
+ }, this.getRegisteredToolDefinition(content.name), this.ui, this.sessionManager.getCwd());
3109
+ component.setExpanded(this.toolOutputExpanded);
3110
+ appendToolRow(this.chatContainer, component, this.toolOutputExpanded);
3201
3111
  if (message.stopReason === "aborted" || message.stopReason === "error") {
3202
3112
  let errorMessage;
3203
3113
  if (message.stopReason === "aborted") {
@@ -3383,7 +3293,7 @@ export class InteractiveMode {
3383
3293
  });
3384
3294
  }
3385
3295
  rebuildChatFromMessages() {
3386
- this.clearChat();
3296
+ this.chatContainer.clear();
3387
3297
  this.renderSessionEntries(this.sessionManager.buildContextEntries());
3388
3298
  }
3389
3299
  // =========================================================================
@@ -3571,8 +3481,34 @@ export class InteractiveMode {
3571
3481
  const text = (this.editor.getExpandedText?.() ?? this.editor.getText()).trim();
3572
3482
  if (!text)
3573
3483
  return;
3574
- this.editor.setText("");
3575
- await this.handleEditorSubmit(text, "followUp");
3484
+ if (this.editorHost.intercept(text, "followUp"))
3485
+ return;
3486
+ // Queue input during compaction (extension commands execute immediately)
3487
+ if (this.session.isCompacting) {
3488
+ if (this.isExtensionCommand(text)) {
3489
+ this.editor.addToHistory?.(text);
3490
+ this.editor.setText("");
3491
+ await this.session.prompt(text);
3492
+ }
3493
+ else {
3494
+ this.queueCompactionMessage(text, "followUp");
3495
+ }
3496
+ return;
3497
+ }
3498
+ // Alt+Enter queues a follow-up message (waits until agent finishes)
3499
+ // This handles extension commands (execute immediately), prompt template expansion, and queueing
3500
+ if (this.session.isStreaming) {
3501
+ this.editor.addToHistory?.(text);
3502
+ this.editor.setText("");
3503
+ await this.session.prompt(text, { streamingBehavior: "followUp" });
3504
+ this.updatePendingMessagesDisplay();
3505
+ this.ui.requestRender();
3506
+ }
3507
+ // If not streaming, Alt+Enter acts like regular Enter (trigger onSubmit)
3508
+ else if (this.editor.onSubmit) {
3509
+ this.editor.setText("");
3510
+ this.editor.onSubmit(text);
3511
+ }
3576
3512
  }
3577
3513
  handleDequeue() {
3578
3514
  const restored = this.restoreQueuedMessagesToEditor();
@@ -3697,7 +3633,13 @@ export class InteractiveMode {
3697
3633
  showNewVersionNotification(release) {
3698
3634
  const action = theme.fg("accent", `${APP_NAME} update`);
3699
3635
  const updateInstruction = theme.fg("muted", `New version ${release.version} is available. Run `) + action;
3700
- const changelogLine = theme.fg("muted", UPDATE_CHANGELOG_HINT);
3636
+ const changelogUrl = "https://pi.dev/changelog";
3637
+ const changelogLink = getCapabilities().hyperlinks
3638
+ ? hyperlink(theme.fg("accent", changelogUrl), changelogUrl)
3639
+ : theme.fg("accent", changelogUrl);
3640
+ const changelogLine = UPDATE_CHANGELOG_HINT
3641
+ ? theme.fg("muted", UPDATE_CHANGELOG_HINT)
3642
+ : theme.fg("muted", "Changelog: ") + changelogLink;
3701
3643
  const note = release.note?.trim();
3702
3644
  this.chatContainer.addChild(new Spacer(1));
3703
3645
  this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
@@ -3905,15 +3847,8 @@ export class InteractiveMode {
3905
3847
  showSelector(create) {
3906
3848
  const token = {};
3907
3849
  let dispose;
3908
- let disposed = false;
3909
- const disposeOnce = () => {
3910
- if (disposed)
3911
- return;
3912
- disposed = true;
3913
- dispose?.();
3914
- };
3915
3850
  const done = () => {
3916
- disposeOnce();
3851
+ dispose?.();
3917
3852
  if (this.activeSelectorToken !== token)
3918
3853
  return;
3919
3854
  this.activeSelectorToken = undefined;
@@ -3923,10 +3858,17 @@ export class InteractiveMode {
3923
3858
  this.ui.setFocus(this.editor);
3924
3859
  };
3925
3860
  const created = create(done);
3926
- dispose = created.dispose;
3861
+ const release = created.dispose;
3862
+ let released = false;
3863
+ dispose = () => {
3864
+ if (released)
3865
+ return;
3866
+ released = true;
3867
+ release?.();
3868
+ };
3927
3869
  this.disposeActiveSelector();
3928
3870
  this.activeSelectorToken = token;
3929
- this.activeSelectorDispose = disposeOnce;
3871
+ this.activeSelectorDispose = dispose;
3930
3872
  this.editorContainer.clear();
3931
3873
  this.editorContainer.addChild(created.component);
3932
3874
  this.ui.setFocus(created.focus);
@@ -4613,7 +4555,7 @@ export class InteractiveMode {
4613
4555
  return;
4614
4556
  }
4615
4557
  // Update UI
4616
- this.clearChat();
4558
+ this.chatContainer.clear();
4617
4559
  this.renderInitialMessages();
4618
4560
  if (result.editorText && !this.editor.getText().trim()) {
4619
4561
  this.editor.setText(result.editorText);
@@ -5754,9 +5696,6 @@ export class InteractiveMode {
5754
5696
  }
5755
5697
  }
5756
5698
  stop(fullscreenExitOutput = this.settingsManager.getFullscreenExitOutput()) {
5757
- this.extensionEditorSubmitHandlers.clear();
5758
- this.backgroundInputUnsubscribe?.();
5759
- this.backgroundInputUnsubscribe = undefined;
5760
5699
  this.disposeActiveSelector();
5761
5700
  if (this.settingsManager.getShowTerminalProgress()) {
5762
5701
  this.ui.terminal.setProgress(false);
@@ -5764,8 +5703,9 @@ export class InteractiveMode {
5764
5703
  this.clearStatusIndicator();
5765
5704
  this.themeController.disableAutoSync();
5766
5705
  this.clearExtensionTerminalInputListeners();
5767
- this.clearPendingTools();
5768
- this.disposeChatToolComponents();
5706
+ this.editorHost.clear();
5707
+ this.pendingTools.clear();
5708
+ disposeChatRows(this.chatContainer);
5769
5709
  this.footer.dispose();
5770
5710
  this.footerDataProvider.dispose();
5771
5711
  if (this.unsubscribe) {