@vikasitai/vikasit-code 2.0.4 → 2.0.5

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 (711) hide show
  1. package/AGENTS.md +69 -0
  2. package/BUN_SHELL_MIGRATION_PLAN.md +136 -0
  3. package/Dockerfile +18 -0
  4. package/README.md +15 -0
  5. package/bunfig.toml +7 -0
  6. package/drizzle.config.ts +10 -0
  7. package/git +0 -0
  8. package/migration/20260127222353_familiar_lady_ursula/migration.sql +90 -0
  9. package/migration/20260127222353_familiar_lady_ursula/snapshot.json +796 -0
  10. package/migration/20260211171708_add_project_commands/migration.sql +1 -0
  11. package/migration/20260211171708_add_project_commands/snapshot.json +806 -0
  12. package/migration/20260213144116_wakeful_the_professor/migration.sql +11 -0
  13. package/migration/20260213144116_wakeful_the_professor/snapshot.json +897 -0
  14. package/migration/20260225215848_workspace/migration.sql +7 -0
  15. package/migration/20260225215848_workspace/snapshot.json +959 -0
  16. package/migration/20260227213759_add_session_workspace_id/migration.sql +2 -0
  17. package/migration/20260227213759_add_session_workspace_id/snapshot.json +983 -0
  18. package/migration/20260228203230_blue_harpoon/migration.sql +17 -0
  19. package/migration/20260228203230_blue_harpoon/snapshot.json +1102 -0
  20. package/migration/20260303231226_add_workspace_fields/migration.sql +5 -0
  21. package/migration/20260303231226_add_workspace_fields/snapshot.json +1013 -0
  22. package/migration/20260309230000_move_org_to_state/migration.sql +3 -0
  23. package/migration/20260309230000_move_org_to_state/snapshot.json +1156 -0
  24. package/migration/20260312043431_session_message_cursor/migration.sql +4 -0
  25. package/migration/20260312043431_session_message_cursor/snapshot.json +1168 -0
  26. package/migration/20260323234822_events/migration.sql +13 -0
  27. package/migration/20260323234822_events/snapshot.json +1271 -0
  28. package/package.json +170 -17
  29. package/parsers-config.ts +290 -0
  30. package/script/build-all.sh +40 -0
  31. package/script/build-node.ts +54 -0
  32. package/script/build.ts +281 -0
  33. package/script/check-migrations.ts +16 -0
  34. package/script/fetch-models.ts +101 -0
  35. package/script/publish.ts +184 -0
  36. package/script/schema.ts +63 -0
  37. package/script/seed-e2e.ts +75 -0
  38. package/script/upgrade-opentui.ts +64 -0
  39. package/specs/claude-code-integration.md +563 -0
  40. package/specs/effect-migration.md +291 -0
  41. package/specs/tui-plugins.md +410 -0
  42. package/src/account/account.sql.ts +39 -0
  43. package/src/account/index.ts +397 -0
  44. package/src/account/repo.ts +163 -0
  45. package/src/account/schema.ts +91 -0
  46. package/src/acp/README.md +174 -0
  47. package/src/acp/agent.ts +1743 -0
  48. package/src/acp/session.ts +116 -0
  49. package/src/acp/types.ts +24 -0
  50. package/src/agent/agent-display.ts +82 -0
  51. package/src/agent/agent.ts +475 -0
  52. package/src/agent/extract-names.cjs +42 -0
  53. package/src/agent/generate.txt +75 -0
  54. package/src/agent/mailbox.ts +124 -0
  55. package/src/agent/orchestrator.ts +170 -0
  56. package/src/agent/prompt/code-reviewer.txt +11 -0
  57. package/src/agent/prompt/compaction.txt +14 -0
  58. package/src/agent/prompt/explore.txt +18 -0
  59. package/src/agent/prompt/general.txt +17 -0
  60. package/src/agent/prompt/summary.txt +11 -0
  61. package/src/agent/prompt/title.txt +44 -0
  62. package/src/agent/prompt/verification.txt +11 -0
  63. package/src/auth/index.ts +109 -0
  64. package/src/bun/index.ts +129 -0
  65. package/src/bun/registry.ts +50 -0
  66. package/src/bus/bus-event.ts +40 -0
  67. package/src/bus/global.ts +10 -0
  68. package/src/bus/index.ts +184 -0
  69. package/src/cli/bootstrap.ts +17 -0
  70. package/src/cli/cmd/account.ts +257 -0
  71. package/src/cli/cmd/acp.ts +70 -0
  72. package/src/cli/cmd/agent.ts +245 -0
  73. package/src/cli/cmd/auto-updater.ts +71 -0
  74. package/src/cli/cmd/cmd.ts +7 -0
  75. package/src/cli/cmd/db.ts +119 -0
  76. package/src/cli/cmd/debug/agent.ts +167 -0
  77. package/src/cli/cmd/debug/config.ts +16 -0
  78. package/src/cli/cmd/debug/file.ts +97 -0
  79. package/src/cli/cmd/debug/index.ts +48 -0
  80. package/src/cli/cmd/debug/lsp.ts +53 -0
  81. package/src/cli/cmd/debug/ripgrep.ts +87 -0
  82. package/src/cli/cmd/debug/scrap.ts +16 -0
  83. package/src/cli/cmd/debug/skill.ts +16 -0
  84. package/src/cli/cmd/debug/snapshot.ts +52 -0
  85. package/src/cli/cmd/export.ts +89 -0
  86. package/src/cli/cmd/generate.ts +38 -0
  87. package/src/cli/cmd/github.ts +1646 -0
  88. package/src/cli/cmd/import.ts +207 -0
  89. package/src/cli/cmd/mcp.ts +754 -0
  90. package/src/cli/cmd/models.ts +78 -0
  91. package/src/cli/cmd/plug.ts +231 -0
  92. package/src/cli/cmd/pr.ts +127 -0
  93. package/src/cli/cmd/providers.ts +484 -0
  94. package/src/cli/cmd/run.ts +676 -0
  95. package/src/cli/cmd/search.ts +137 -0
  96. package/src/cli/cmd/serve.ts +24 -0
  97. package/src/cli/cmd/session.ts +159 -0
  98. package/src/cli/cmd/stats.ts +405 -0
  99. package/src/cli/cmd/tui/app.tsx +996 -0
  100. package/src/cli/cmd/tui/attach.ts +88 -0
  101. package/src/cli/cmd/tui/component/border.tsx +21 -0
  102. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  103. package/src/cli/cmd/tui/component/dialog-command.tsx +171 -0
  104. package/src/cli/cmd/tui/component/dialog-login.tsx +156 -0
  105. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  106. package/src/cli/cmd/tui/component/dialog-model.tsx +179 -0
  107. package/src/cli/cmd/tui/component/dialog-provider.tsx +329 -0
  108. package/src/cli/cmd/tui/component/dialog-session-list.tsx +108 -0
  109. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  110. package/src/cli/cmd/tui/component/dialog-skill.tsx +36 -0
  111. package/src/cli/cmd/tui/component/dialog-stash.tsx +87 -0
  112. package/src/cli/cmd/tui/component/dialog-status.tsx +168 -0
  113. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  114. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  115. package/src/cli/cmd/tui/component/dialog-variant.tsx +39 -0
  116. package/src/cli/cmd/tui/component/dialog-workspace-list.tsx +320 -0
  117. package/src/cli/cmd/tui/component/error-component.tsx +91 -0
  118. package/src/cli/cmd/tui/component/login-gate.tsx +125 -0
  119. package/src/cli/cmd/tui/component/logo.tsx +85 -0
  120. package/src/cli/cmd/tui/component/plugin-route-missing.tsx +14 -0
  121. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +667 -0
  122. package/src/cli/cmd/tui/component/prompt/frecency.tsx +90 -0
  123. package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
  124. package/src/cli/cmd/tui/component/prompt/index.tsx +1224 -0
  125. package/src/cli/cmd/tui/component/prompt/part.ts +16 -0
  126. package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
  127. package/src/cli/cmd/tui/component/spinner.tsx +24 -0
  128. package/src/cli/cmd/tui/component/startup-loading.tsx +63 -0
  129. package/src/cli/cmd/tui/component/status-bar.ts +54 -0
  130. package/src/cli/cmd/tui/component/textarea-keybindings.ts +73 -0
  131. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  132. package/src/cli/cmd/tui/component/workspace/dialog-session-list.tsx +151 -0
  133. package/src/cli/cmd/tui/context/args.tsx +15 -0
  134. package/src/cli/cmd/tui/context/directory.ts +13 -0
  135. package/src/cli/cmd/tui/context/exit.tsx +60 -0
  136. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  137. package/src/cli/cmd/tui/context/keybind.tsx +105 -0
  138. package/src/cli/cmd/tui/context/kv.tsx +52 -0
  139. package/src/cli/cmd/tui/context/local.tsx +412 -0
  140. package/src/cli/cmd/tui/context/plugin-keybinds.ts +41 -0
  141. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  142. package/src/cli/cmd/tui/context/route.tsx +52 -0
  143. package/src/cli/cmd/tui/context/sdk.tsx +128 -0
  144. package/src/cli/cmd/tui/context/sync.tsx +504 -0
  145. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  146. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  147. package/src/cli/cmd/tui/context/theme/carbonfox.json +248 -0
  148. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
  149. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  150. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  151. package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
  152. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  153. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  154. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  155. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  156. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  157. package/src/cli/cmd/tui/context/theme/gruvbox.json +242 -0
  158. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  159. package/src/cli/cmd/tui/context/theme/lucent-orng.json +237 -0
  160. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  161. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  162. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  163. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  164. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  165. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  166. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  167. package/src/cli/cmd/tui/context/theme/orng.json +249 -0
  168. package/src/cli/cmd/tui/context/theme/osaka-jade.json +93 -0
  169. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  170. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  171. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  172. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  173. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  174. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  175. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  176. package/src/cli/cmd/tui/context/theme/vikasit.json +245 -0
  177. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  178. package/src/cli/cmd/tui/context/theme.tsx +1236 -0
  179. package/src/cli/cmd/tui/context/tui-config.tsx +9 -0
  180. package/src/cli/cmd/tui/event.ts +52 -0
  181. package/src/cli/cmd/tui/feature-plugins/home/footer.tsx +93 -0
  182. package/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx +152 -0
  183. package/src/cli/cmd/tui/feature-plugins/home/tips.tsx +50 -0
  184. package/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx +63 -0
  185. package/src/cli/cmd/tui/feature-plugins/sidebar/files.tsx +62 -0
  186. package/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx +99 -0
  187. package/src/cli/cmd/tui/feature-plugins/sidebar/lsp.tsx +66 -0
  188. package/src/cli/cmd/tui/feature-plugins/sidebar/mcp.tsx +96 -0
  189. package/src/cli/cmd/tui/feature-plugins/sidebar/todo.tsx +48 -0
  190. package/src/cli/cmd/tui/feature-plugins/system/invite.tsx +148 -0
  191. package/src/cli/cmd/tui/feature-plugins/system/plugins.tsx +270 -0
  192. package/src/cli/cmd/tui/plugin/api.tsx +420 -0
  193. package/src/cli/cmd/tui/plugin/index.ts +3 -0
  194. package/src/cli/cmd/tui/plugin/internal.ts +29 -0
  195. package/src/cli/cmd/tui/plugin/runtime.ts +998 -0
  196. package/src/cli/cmd/tui/plugin/slots.tsx +61 -0
  197. package/src/cli/cmd/tui/routes/home.tsx +84 -0
  198. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +65 -0
  199. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +110 -0
  200. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
  201. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  202. package/src/cli/cmd/tui/routes/session/footer.tsx +91 -0
  203. package/src/cli/cmd/tui/routes/session/index.tsx +2259 -0
  204. package/src/cli/cmd/tui/routes/session/permission.tsx +685 -0
  205. package/src/cli/cmd/tui/routes/session/question.tsx +467 -0
  206. package/src/cli/cmd/tui/routes/session/sidebar.tsx +68 -0
  207. package/src/cli/cmd/tui/routes/session/subagent-footer.tsx +124 -0
  208. package/src/cli/cmd/tui/thread.ts +232 -0
  209. package/src/cli/cmd/tui/ui/dialog-alert.tsx +59 -0
  210. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +89 -0
  211. package/src/cli/cmd/tui/ui/dialog-export-options.tsx +208 -0
  212. package/src/cli/cmd/tui/ui/dialog-help.tsx +40 -0
  213. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +106 -0
  214. package/src/cli/cmd/tui/ui/dialog-select.tsx +402 -0
  215. package/src/cli/cmd/tui/ui/dialog.tsx +192 -0
  216. package/src/cli/cmd/tui/ui/link.tsx +28 -0
  217. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  218. package/src/cli/cmd/tui/ui/toast.tsx +100 -0
  219. package/src/cli/cmd/tui/util/clipboard.ts +192 -0
  220. package/src/cli/cmd/tui/util/editor.ts +37 -0
  221. package/src/cli/cmd/tui/util/selection.ts +25 -0
  222. package/src/cli/cmd/tui/util/signal.ts +7 -0
  223. package/src/cli/cmd/tui/util/terminal.ts +114 -0
  224. package/src/cli/cmd/tui/util/transcript.ts +98 -0
  225. package/src/cli/cmd/tui/win32.ts +129 -0
  226. package/src/cli/cmd/tui/worker.ts +172 -0
  227. package/src/cli/cmd/uninstall.ts +353 -0
  228. package/src/cli/cmd/upgrade.ts +73 -0
  229. package/src/cli/cmd/web.ts +81 -0
  230. package/src/cli/effect/prompt.ts +25 -0
  231. package/src/cli/error.ts +46 -0
  232. package/src/cli/logo.ts +20 -0
  233. package/src/cli/network.ts +60 -0
  234. package/src/cli/ui.ts +116 -0
  235. package/src/cli/upgrade.ts +31 -0
  236. package/src/command/index.ts +251 -0
  237. package/src/command/template/branch.txt +1 -0
  238. package/src/command/template/commit.txt +7 -0
  239. package/src/command/template/diff.txt +1 -0
  240. package/src/command/template/initialize.txt +10 -0
  241. package/src/command/template/memory.txt +1 -0
  242. package/src/command/template/review.txt +101 -0
  243. package/src/command/template/summary.txt +1 -0
  244. package/src/config/config.ts +1619 -0
  245. package/src/config/markdown.ts +99 -0
  246. package/src/config/migrate-tui-config.ts +155 -0
  247. package/src/config/paths.ts +174 -0
  248. package/src/config/tui-schema.ts +36 -0
  249. package/src/config/tui.ts +222 -0
  250. package/src/context/contextOptimizer.ts +277 -0
  251. package/src/control-plane/adaptors/index.ts +20 -0
  252. package/src/control-plane/adaptors/worktree.ts +38 -0
  253. package/src/control-plane/schema.ts +17 -0
  254. package/src/control-plane/sse.ts +66 -0
  255. package/src/control-plane/types.ts +21 -0
  256. package/src/control-plane/workspace.sql.ts +17 -0
  257. package/src/control-plane/workspace.ts +154 -0
  258. package/src/cost/index.ts +173 -0
  259. package/src/cost/schema.ts +43 -0
  260. package/src/cron/index.ts +200 -0
  261. package/src/effect/cross-spawn-spawner.ts +479 -0
  262. package/src/effect/instance-registry.ts +12 -0
  263. package/src/effect/instance-state.ts +47 -0
  264. package/src/effect/run-service.ts +19 -0
  265. package/src/effect/runner.ts +216 -0
  266. package/src/env/index.ts +28 -0
  267. package/src/file/ignore.ts +82 -0
  268. package/src/file/index.ts +698 -0
  269. package/src/file/protected.ts +59 -0
  270. package/src/file/ripgrep.ts +376 -0
  271. package/src/file/time.ts +128 -0
  272. package/src/file/watcher.ts +171 -0
  273. package/src/filesystem/index.ts +226 -0
  274. package/src/flag/flag.ts +175 -0
  275. package/src/format/formatter.ts +396 -0
  276. package/src/format/index.ts +199 -0
  277. package/src/global/index.ts +54 -0
  278. package/src/hook/index.ts +132 -0
  279. package/src/hook/schema.ts +31 -0
  280. package/src/id/id.ts +85 -0
  281. package/src/ide/index.ts +74 -0
  282. package/src/index.ts +249 -0
  283. package/src/installation/index.ts +363 -0
  284. package/src/lsp/client.ts +252 -0
  285. package/src/lsp/index.ts +558 -0
  286. package/src/lsp/language.ts +120 -0
  287. package/src/lsp/launch.ts +21 -0
  288. package/src/lsp/server.ts +2093 -0
  289. package/src/mcp/auth.ts +181 -0
  290. package/src/mcp/index.ts +926 -0
  291. package/src/mcp/oauth-callback.ts +215 -0
  292. package/src/mcp/oauth-provider.ts +185 -0
  293. package/src/memory/memory.ts +140 -0
  294. package/src/node.ts +1 -0
  295. package/src/patch/index.ts +680 -0
  296. package/src/permission/arity.ts +163 -0
  297. package/src/permission/auto-classify.ts +114 -0
  298. package/src/permission/evaluate.ts +15 -0
  299. package/src/permission/index.ts +322 -0
  300. package/src/permission/schema.ts +17 -0
  301. package/src/plugin/codex.ts +596 -0
  302. package/src/plugin/copilot.ts +343 -0
  303. package/src/plugin/index.ts +322 -0
  304. package/src/plugin/install.ts +417 -0
  305. package/src/plugin/loader.ts +137 -0
  306. package/src/plugin/meta.ts +188 -0
  307. package/src/plugin/shared.ts +272 -0
  308. package/src/project/bootstrap.ts +31 -0
  309. package/src/project/instance.ts +167 -0
  310. package/src/project/project.sql.ts +16 -0
  311. package/src/project/project.ts +519 -0
  312. package/src/project/schema.ts +16 -0
  313. package/src/project/state.ts +70 -0
  314. package/src/project/vcs.ts +124 -0
  315. package/src/provider/auth.ts +252 -0
  316. package/src/provider/error.ts +197 -0
  317. package/src/provider/github-models.ts +53 -0
  318. package/src/provider/models.ts +139 -0
  319. package/src/provider/provider.ts +1650 -0
  320. package/src/provider/schema.ts +38 -0
  321. package/src/provider/sdk/copilot/README.md +5 -0
  322. package/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts +170 -0
  323. package/src/provider/sdk/copilot/chat/get-response-metadata.ts +15 -0
  324. package/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts +19 -0
  325. package/src/provider/sdk/copilot/chat/openai-compatible-api-types.ts +64 -0
  326. package/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts +815 -0
  327. package/src/provider/sdk/copilot/chat/openai-compatible-chat-options.ts +28 -0
  328. package/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts +44 -0
  329. package/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts +83 -0
  330. package/src/provider/sdk/copilot/copilot-provider.ts +100 -0
  331. package/src/provider/sdk/copilot/index.ts +2 -0
  332. package/src/provider/sdk/copilot/openai-compatible-error.ts +27 -0
  333. package/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts +335 -0
  334. package/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts +22 -0
  335. package/src/provider/sdk/copilot/responses/openai-config.ts +18 -0
  336. package/src/provider/sdk/copilot/responses/openai-error.ts +22 -0
  337. package/src/provider/sdk/copilot/responses/openai-responses-api-types.ts +214 -0
  338. package/src/provider/sdk/copilot/responses/openai-responses-language-model.ts +1769 -0
  339. package/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts +173 -0
  340. package/src/provider/sdk/copilot/responses/openai-responses-settings.ts +1 -0
  341. package/src/provider/sdk/copilot/responses/tool/code-interpreter.ts +87 -0
  342. package/src/provider/sdk/copilot/responses/tool/file-search.ts +127 -0
  343. package/src/provider/sdk/copilot/responses/tool/image-generation.ts +114 -0
  344. package/src/provider/sdk/copilot/responses/tool/local-shell.ts +64 -0
  345. package/src/provider/sdk/copilot/responses/tool/web-search-preview.ts +103 -0
  346. package/src/provider/sdk/copilot/responses/tool/web-search.ts +102 -0
  347. package/src/provider/transform.ts +1045 -0
  348. package/src/pty/index.ts +397 -0
  349. package/src/pty/schema.ts +17 -0
  350. package/src/question/index.ts +221 -0
  351. package/src/question/schema.ts +17 -0
  352. package/src/server/error.ts +36 -0
  353. package/src/server/event.ts +7 -0
  354. package/src/server/instance.ts +285 -0
  355. package/src/server/mdns.ts +60 -0
  356. package/src/server/middleware.ts +33 -0
  357. package/src/server/projectors.ts +28 -0
  358. package/src/server/router.ts +99 -0
  359. package/src/server/routes/config.ts +92 -0
  360. package/src/server/routes/event.ts +83 -0
  361. package/src/server/routes/experimental.ts +271 -0
  362. package/src/server/routes/file.ts +197 -0
  363. package/src/server/routes/global.ts +312 -0
  364. package/src/server/routes/instance/httpapi/app.ts +21 -0
  365. package/src/server/routes/mcp.ts +225 -0
  366. package/src/server/routes/permission.ts +69 -0
  367. package/src/server/routes/project.ts +118 -0
  368. package/src/server/routes/provider.ts +171 -0
  369. package/src/server/routes/pty.ts +211 -0
  370. package/src/server/routes/question.ts +99 -0
  371. package/src/server/routes/session.ts +1031 -0
  372. package/src/server/routes/tui.ts +379 -0
  373. package/src/server/routes/workspace.ts +94 -0
  374. package/src/server/server.ts +314 -0
  375. package/src/session/compaction.ts +432 -0
  376. package/src/session/index.ts +885 -0
  377. package/src/session/instruction.ts +192 -0
  378. package/src/session/llm.ts +365 -0
  379. package/src/session/memory.ts +126 -0
  380. package/src/session/message-v2.ts +1030 -0
  381. package/src/session/message.ts +191 -0
  382. package/src/session/overflow.ts +22 -0
  383. package/src/session/processor.ts +697 -0
  384. package/src/session/projectors.ts +135 -0
  385. package/src/session/prompt/anthropic.txt +125 -0
  386. package/src/session/prompt/beast.txt +161 -0
  387. package/src/session/prompt/build-switch.txt +5 -0
  388. package/src/session/prompt/codex.txt +90 -0
  389. package/src/session/prompt/copilot-gpt-5.txt +143 -0
  390. package/src/session/prompt/default.txt +116 -0
  391. package/src/session/prompt/gemini.txt +173 -0
  392. package/src/session/prompt/gpt.txt +120 -0
  393. package/src/session/prompt/kimi.txt +131 -0
  394. package/src/session/prompt/max-steps.txt +16 -0
  395. package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
  396. package/src/session/prompt/plan.txt +26 -0
  397. package/src/session/prompt/trinity.txt +109 -0
  398. package/src/session/prompt.ts +1898 -0
  399. package/src/session/rate-limit.ts +176 -0
  400. package/src/session/retry.ts +107 -0
  401. package/src/session/revert.ts +135 -0
  402. package/src/session/schema.ts +38 -0
  403. package/src/session/session.sql.ts +103 -0
  404. package/src/session/status.ts +102 -0
  405. package/src/session/summary.ts +169 -0
  406. package/src/session/system.ts +76 -0
  407. package/src/session/todo.ts +57 -0
  408. package/src/share/share-next.ts +287 -0
  409. package/src/share/share.sql.ts +13 -0
  410. package/src/shell/shell.ts +110 -0
  411. package/src/skill/discovery.ts +116 -0
  412. package/src/skill/index.ts +277 -0
  413. package/src/snapshot/index.ts +489 -0
  414. package/src/sql.d.ts +4 -0
  415. package/src/storage/db.bun.ts +8 -0
  416. package/src/storage/db.node.ts +8 -0
  417. package/src/storage/db.ts +177 -0
  418. package/src/storage/json-migration.ts +425 -0
  419. package/src/storage/schema.sql.ts +10 -0
  420. package/src/storage/schema.ts +5 -0
  421. package/src/storage/storage.ts +353 -0
  422. package/src/sync/README.md +179 -0
  423. package/src/sync/event.sql.ts +16 -0
  424. package/src/sync/index.ts +263 -0
  425. package/src/sync/schema.ts +14 -0
  426. package/src/task/index.ts +89 -0
  427. package/src/task/schema.ts +39 -0
  428. package/src/tool/apply_patch.ts +281 -0
  429. package/src/tool/apply_patch.txt +33 -0
  430. package/src/tool/bash-security.ts +214 -0
  431. package/src/tool/bash.ts +526 -0
  432. package/src/tool/bash.txt +129 -0
  433. package/src/tool/batch.ts +183 -0
  434. package/src/tool/batch.txt +24 -0
  435. package/src/tool/codesearch.ts +132 -0
  436. package/src/tool/codesearch.txt +12 -0
  437. package/src/tool/cron-create.ts +38 -0
  438. package/src/tool/cron-create.txt +10 -0
  439. package/src/tool/cron-delete.ts +29 -0
  440. package/src/tool/cron-delete.txt +1 -0
  441. package/src/tool/cron-list.ts +37 -0
  442. package/src/tool/cron-list.txt +1 -0
  443. package/src/tool/edit.ts +667 -0
  444. package/src/tool/edit.txt +10 -0
  445. package/src/tool/external-directory.ts +37 -0
  446. package/src/tool/glob.ts +78 -0
  447. package/src/tool/glob.txt +6 -0
  448. package/src/tool/grep.ts +156 -0
  449. package/src/tool/grep.txt +8 -0
  450. package/src/tool/invalid.ts +17 -0
  451. package/src/tool/ls.ts +121 -0
  452. package/src/tool/ls.txt +1 -0
  453. package/src/tool/lsp.ts +97 -0
  454. package/src/tool/lsp.txt +19 -0
  455. package/src/tool/multiedit.ts +46 -0
  456. package/src/tool/multiedit.txt +41 -0
  457. package/src/tool/notebook-edit.ts +136 -0
  458. package/src/tool/notebook-edit.txt +10 -0
  459. package/src/tool/plan-enter.txt +14 -0
  460. package/src/tool/plan-exit.txt +13 -0
  461. package/src/tool/plan.ts +130 -0
  462. package/src/tool/question.ts +33 -0
  463. package/src/tool/question.txt +10 -0
  464. package/src/tool/read.ts +296 -0
  465. package/src/tool/read.txt +14 -0
  466. package/src/tool/registry.ts +333 -0
  467. package/src/tool/schema.ts +17 -0
  468. package/src/tool/sendmessage.ts +57 -0
  469. package/src/tool/sendmessage.txt +6 -0
  470. package/src/tool/skill.ts +105 -0
  471. package/src/tool/task-create.ts +25 -0
  472. package/src/tool/task-create.txt +1 -0
  473. package/src/tool/task-get.ts +27 -0
  474. package/src/tool/task-get.txt +1 -0
  475. package/src/tool/task-list.ts +45 -0
  476. package/src/tool/task-list.txt +1 -0
  477. package/src/tool/task-output.ts +28 -0
  478. package/src/tool/task-output.txt +1 -0
  479. package/src/tool/task-stop.ts +27 -0
  480. package/src/tool/task-stop.txt +1 -0
  481. package/src/tool/task-update.ts +38 -0
  482. package/src/tool/task-update.txt +1 -0
  483. package/src/tool/task.ts +246 -0
  484. package/src/tool/task.txt +65 -0
  485. package/src/tool/todo.ts +31 -0
  486. package/src/tool/todowrite.txt +167 -0
  487. package/src/tool/tool.ts +98 -0
  488. package/src/tool/toolsearch.ts +117 -0
  489. package/src/tool/toolsearch.txt +10 -0
  490. package/src/tool/truncate.ts +144 -0
  491. package/src/tool/truncation-dir.ts +4 -0
  492. package/src/tool/webfetch.ts +206 -0
  493. package/src/tool/webfetch.txt +13 -0
  494. package/src/tool/websearch.ts +150 -0
  495. package/src/tool/websearch.txt +14 -0
  496. package/src/tool/worktree-enter.ts +38 -0
  497. package/src/tool/worktree-enter.txt +8 -0
  498. package/src/tool/worktree-exit.ts +43 -0
  499. package/src/tool/worktree-exit.txt +3 -0
  500. package/src/tool/write.ts +84 -0
  501. package/src/tool/write.txt +8 -0
  502. package/src/util/abort.ts +35 -0
  503. package/src/util/archive.ts +17 -0
  504. package/src/util/color.ts +19 -0
  505. package/src/util/context.ts +25 -0
  506. package/src/util/data-url.ts +9 -0
  507. package/src/util/defer.ts +12 -0
  508. package/src/util/effect-http-client.ts +11 -0
  509. package/src/util/effect-zod.ts +98 -0
  510. package/src/util/error.ts +77 -0
  511. package/src/util/filesystem.ts +220 -0
  512. package/src/util/flock.ts +333 -0
  513. package/src/util/fn.ts +21 -0
  514. package/src/util/format.ts +20 -0
  515. package/src/util/git.ts +35 -0
  516. package/src/util/glob.ts +34 -0
  517. package/src/util/hash.ts +7 -0
  518. package/src/util/iife.ts +3 -0
  519. package/src/util/keybind.ts +103 -0
  520. package/src/util/lazy.ts +23 -0
  521. package/src/util/locale.ts +81 -0
  522. package/src/util/lock.ts +98 -0
  523. package/src/util/log.ts +182 -0
  524. package/src/util/network.ts +9 -0
  525. package/src/util/process.ts +172 -0
  526. package/src/util/queue.ts +32 -0
  527. package/src/util/record.ts +3 -0
  528. package/src/util/rpc.ts +66 -0
  529. package/src/util/schema.ts +53 -0
  530. package/src/util/scrap.ts +10 -0
  531. package/src/util/signal.ts +12 -0
  532. package/src/util/timeout.ts +14 -0
  533. package/src/util/token.ts +7 -0
  534. package/src/util/update-schema.ts +13 -0
  535. package/src/util/which.ts +14 -0
  536. package/src/util/wildcard.ts +59 -0
  537. package/src/vim/agent-namer.ts +86 -0
  538. package/src/vim/types.ts +29 -0
  539. package/src/worktree/index.ts +638 -0
  540. package/sst-env.d.ts +10 -0
  541. package/test/AGENTS.md +81 -0
  542. package/test/account/repo.test.ts +326 -0
  543. package/test/account/service.test.ts +282 -0
  544. package/test/acp/agent-interface.test.ts +51 -0
  545. package/test/acp/event-subscription.test.ts +685 -0
  546. package/test/agent/agent.test.ts +717 -0
  547. package/test/auth/auth.test.ts +58 -0
  548. package/test/bun.test.ts +137 -0
  549. package/test/bus/bus-effect.test.ts +164 -0
  550. package/test/bus/bus-integration.test.ts +87 -0
  551. package/test/bus/bus.test.ts +219 -0
  552. package/test/cli/account.test.ts +26 -0
  553. package/test/cli/cmd/tui/prompt-part.test.ts +47 -0
  554. package/test/cli/github-action.test.ts +198 -0
  555. package/test/cli/github-remote.test.ts +80 -0
  556. package/test/cli/import.test.ts +54 -0
  557. package/test/cli/plugin-auth-picker.test.ts +120 -0
  558. package/test/cli/tui/keybind-plugin.test.ts +90 -0
  559. package/test/cli/tui/plugin-add.test.ts +61 -0
  560. package/test/cli/tui/plugin-install.test.ts +89 -0
  561. package/test/cli/tui/plugin-lifecycle.test.ts +225 -0
  562. package/test/cli/tui/plugin-loader-entrypoint.test.ts +492 -0
  563. package/test/cli/tui/plugin-loader-pure.test.ts +72 -0
  564. package/test/cli/tui/plugin-loader.test.ts +752 -0
  565. package/test/cli/tui/plugin-toggle.test.ts +159 -0
  566. package/test/cli/tui/theme-store.test.ts +51 -0
  567. package/test/cli/tui/thread.test.ts +128 -0
  568. package/test/cli/tui/transcript.test.ts +322 -0
  569. package/test/config/agent-color.test.ts +71 -0
  570. package/test/config/config.test.ts +2218 -0
  571. package/test/config/fixtures/empty-frontmatter.md +4 -0
  572. package/test/config/fixtures/frontmatter.md +28 -0
  573. package/test/config/fixtures/markdown-header.md +11 -0
  574. package/test/config/fixtures/no-frontmatter.md +1 -0
  575. package/test/config/fixtures/weird-model-id.md +13 -0
  576. package/test/config/markdown.test.ts +228 -0
  577. package/test/config/tui.test.ts +673 -0
  578. package/test/control-plane/sse.test.ts +56 -0
  579. package/test/effect/cross-spawn-spawner.test.ts +402 -0
  580. package/test/effect/instance-state.test.ts +384 -0
  581. package/test/effect/run-service.test.ts +46 -0
  582. package/test/effect/runner.test.ts +523 -0
  583. package/test/file/fsmonitor.test.ts +62 -0
  584. package/test/file/ignore.test.ts +10 -0
  585. package/test/file/index.test.ts +946 -0
  586. package/test/file/path-traversal.test.ts +198 -0
  587. package/test/file/ripgrep.test.ts +54 -0
  588. package/test/file/time.test.ts +354 -0
  589. package/test/file/watcher.test.ts +247 -0
  590. package/test/filesystem/filesystem.test.ts +319 -0
  591. package/test/fixture/db.ts +11 -0
  592. package/test/fixture/fixture.test.ts +26 -0
  593. package/test/fixture/fixture.ts +141 -0
  594. package/test/fixture/flock-worker.ts +72 -0
  595. package/test/fixture/lsp/fake-lsp-server.js +77 -0
  596. package/test/fixture/plug-worker.ts +93 -0
  597. package/test/fixture/plugin-meta-worker.ts +26 -0
  598. package/test/fixture/skills/agents-sdk/SKILL.md +152 -0
  599. package/test/fixture/skills/agents-sdk/references/callable.md +92 -0
  600. package/test/fixture/skills/cloudflare/SKILL.md +211 -0
  601. package/test/fixture/skills/index.json +6 -0
  602. package/test/fixture/tui-plugin.ts +335 -0
  603. package/test/fixture/tui-runtime.ts +27 -0
  604. package/test/format/format.test.ts +179 -0
  605. package/test/ide/ide.test.ts +82 -0
  606. package/test/installation/installation.test.ts +151 -0
  607. package/test/keybind.test.ts +421 -0
  608. package/test/lib/effect.ts +37 -0
  609. package/test/lib/filesystem.ts +10 -0
  610. package/test/lsp/client.test.ts +95 -0
  611. package/test/lsp/index.test.ts +55 -0
  612. package/test/lsp/launch.test.ts +22 -0
  613. package/test/lsp/lifecycle.test.ts +147 -0
  614. package/test/mcp/headers.test.ts +153 -0
  615. package/test/mcp/lifecycle.test.ts +750 -0
  616. package/test/mcp/oauth-auto-connect.test.ts +199 -0
  617. package/test/mcp/oauth-browser.test.ts +249 -0
  618. package/test/memory/abort-leak.test.ts +137 -0
  619. package/test/patch/patch.test.ts +348 -0
  620. package/test/permission/arity.test.ts +33 -0
  621. package/test/permission/next.test.ts +1148 -0
  622. package/test/permission-task.test.ts +323 -0
  623. package/test/plugin/auth-override.test.ts +74 -0
  624. package/test/plugin/codex.test.ts +123 -0
  625. package/test/plugin/install-concurrency.test.ts +140 -0
  626. package/test/plugin/install.test.ts +531 -0
  627. package/test/plugin/loader-shared.test.ts +836 -0
  628. package/test/plugin/meta.test.ts +137 -0
  629. package/test/plugin/trigger.test.ts +111 -0
  630. package/test/preload.ts +90 -0
  631. package/test/project/migrate-global.test.ts +140 -0
  632. package/test/project/project.test.ts +459 -0
  633. package/test/project/state.test.ts +115 -0
  634. package/test/project/vcs.test.ts +116 -0
  635. package/test/project/worktree-remove.test.ts +96 -0
  636. package/test/project/worktree.test.ts +173 -0
  637. package/test/provider/amazon-bedrock.test.ts +447 -0
  638. package/test/provider/copilot/convert-to-copilot-messages.test.ts +523 -0
  639. package/test/provider/copilot/copilot-chat-model.test.ts +592 -0
  640. package/test/provider/gitlab-duo.test.ts +412 -0
  641. package/test/provider/provider.test.ts +2284 -0
  642. package/test/provider/transform.test.ts +2758 -0
  643. package/test/pty/pty-output-isolation.test.ts +141 -0
  644. package/test/pty/pty-session.test.ts +92 -0
  645. package/test/pty/pty-shell.test.ts +59 -0
  646. package/test/question/question.test.ts +453 -0
  647. package/test/server/global-session-list.test.ts +89 -0
  648. package/test/server/project-init-git.test.ts +121 -0
  649. package/test/server/session-actions.test.ts +83 -0
  650. package/test/server/session-list.test.ts +98 -0
  651. package/test/server/session-messages.test.ts +159 -0
  652. package/test/server/session-select.test.ts +84 -0
  653. package/test/session/compaction.test.ts +1202 -0
  654. package/test/session/instruction.test.ts +170 -0
  655. package/test/session/llm.test.ts +1098 -0
  656. package/test/session/message-v2.test.ts +957 -0
  657. package/test/session/messages-pagination.test.ts +115 -0
  658. package/test/session/processor-effect.test.ts +872 -0
  659. package/test/session/prompt-concurrency.test.ts +247 -0
  660. package/test/session/prompt-effect.test.ts +1206 -0
  661. package/test/session/prompt.test.ts +518 -0
  662. package/test/session/retry.test.ts +232 -0
  663. package/test/session/revert-compact.test.ts +286 -0
  664. package/test/session/session.test.ts +142 -0
  665. package/test/session/structured-output-integration.test.ts +233 -0
  666. package/test/session/structured-output.test.ts +391 -0
  667. package/test/session/system.test.ts +59 -0
  668. package/test/share/share-next.test.ts +76 -0
  669. package/test/shell/shell.test.ts +73 -0
  670. package/test/skill/discovery.test.ts +116 -0
  671. package/test/skill/skill.test.ts +392 -0
  672. package/test/snapshot/snapshot.test.ts +1235 -0
  673. package/test/storage/db.test.ts +14 -0
  674. package/test/storage/json-migration.test.ts +849 -0
  675. package/test/storage/storage.test.ts +295 -0
  676. package/test/sync/index.test.ts +191 -0
  677. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  678. package/test/tool/apply_patch.test.ts +567 -0
  679. package/test/tool/bash.test.ts +984 -0
  680. package/test/tool/edit.test.ts +681 -0
  681. package/test/tool/external-directory.test.ts +198 -0
  682. package/test/tool/fixtures/large-image.png +0 -0
  683. package/test/tool/fixtures/models-api.json +65179 -0
  684. package/test/tool/grep.test.ts +111 -0
  685. package/test/tool/question.test.ts +108 -0
  686. package/test/tool/read.test.ts +546 -0
  687. package/test/tool/registry.test.ts +126 -0
  688. package/test/tool/skill.test.ts +167 -0
  689. package/test/tool/task.test.ts +49 -0
  690. package/test/tool/truncation.test.ts +161 -0
  691. package/test/tool/webfetch.test.ts +101 -0
  692. package/test/tool/write.test.ts +353 -0
  693. package/test/util/data-url.test.ts +14 -0
  694. package/test/util/effect-zod.test.ts +61 -0
  695. package/test/util/error.test.ts +38 -0
  696. package/test/util/filesystem.test.ts +567 -0
  697. package/test/util/flock.test.ts +383 -0
  698. package/test/util/format.test.ts +59 -0
  699. package/test/util/glob.test.ts +164 -0
  700. package/test/util/iife.test.ts +36 -0
  701. package/test/util/lazy.test.ts +50 -0
  702. package/test/util/lock.test.ts +72 -0
  703. package/test/util/module.test.ts +59 -0
  704. package/test/util/process.test.ts +128 -0
  705. package/test/util/timeout.test.ts +21 -0
  706. package/test/util/which.test.ts +100 -0
  707. package/test/util/wildcard.test.ts +90 -0
  708. package/tsconfig.json +23 -0
  709. package/vikasitai-vikasit-code-2.0.5.tgz +0 -0
  710. package/LICENSE +0 -21
  711. /package/{postinstall.mjs → script/postinstall.mjs} +0 -0
@@ -0,0 +1,1224 @@
1
+ import { BoxRenderable, TextareaRenderable, MouseEvent, PasteEvent, decodePasteBytes, t, dim, fg } from "@opentui/core"
2
+ import { createEffect, createMemo, type JSX, onMount, createSignal, onCleanup, on, Show, Switch, Match } from "solid-js"
3
+ import "opentui-spinner/solid"
4
+ import path from "path"
5
+ import { Filesystem } from "@/util/filesystem"
6
+ import { useLocal } from "@tui/context/local"
7
+ import { useTheme } from "@tui/context/theme"
8
+ import { EmptyBorder, SplitBorder } from "@tui/component/border"
9
+ import { useSDK } from "@tui/context/sdk"
10
+ import { useRoute } from "@tui/context/route"
11
+ import { useSync } from "@tui/context/sync"
12
+ import { MessageID, PartID } from "@/session/schema"
13
+ import { createStore, produce } from "solid-js/store"
14
+ import { useKeybind } from "@tui/context/keybind"
15
+ import { usePromptHistory, type PromptInfo } from "./history"
16
+ import { assign } from "./part"
17
+ import { usePromptStash } from "./stash"
18
+ import { DialogStash } from "../dialog-stash"
19
+ import { type AutocompleteRef, Autocomplete } from "./autocomplete"
20
+ import { useCommandDialog } from "../dialog-command"
21
+ import { useKeyboard, useRenderer } from "@opentui/solid"
22
+ import { Editor } from "@tui/util/editor"
23
+ import { useExit } from "../../context/exit"
24
+ import { Clipboard } from "../../util/clipboard"
25
+ import type { AssistantMessage, FilePart } from "@vikasit-code/sdk/v2"
26
+ import { TuiEvent } from "../../event"
27
+ import { iife } from "@/util/iife"
28
+ import { Locale } from "@/util/locale"
29
+ import { formatDuration } from "@/util/format"
30
+ import { createColors, createFrames } from "../../ui/spinner.ts"
31
+ import { useDialog } from "@tui/ui/dialog"
32
+ import { DialogProvider as DialogProviderConnect } from "../dialog-provider"
33
+ import { DialogAlert } from "../../ui/dialog-alert"
34
+ import { useToast } from "../../ui/toast"
35
+ import { useKV } from "../../context/kv"
36
+ import { useTextareaKeybindings } from "../textarea-keybindings"
37
+ import { DialogSkill } from "../dialog-skill"
38
+
39
+ export type PromptProps = {
40
+ sessionID?: string
41
+ workspaceID?: string
42
+ visible?: boolean
43
+ disabled?: boolean
44
+ onSubmit?: () => void
45
+ ref?: (ref: PromptRef) => void
46
+ hint?: JSX.Element
47
+ showPlaceholder?: boolean
48
+ placeholders?: {
49
+ normal?: string[]
50
+ shell?: string[]
51
+ }
52
+ }
53
+
54
+ export type PromptRef = {
55
+ focused: boolean
56
+ current: PromptInfo
57
+ set(prompt: PromptInfo): void
58
+ reset(): void
59
+ blur(): void
60
+ focus(): void
61
+ submit(): void
62
+ }
63
+
64
+ const money = new Intl.NumberFormat("en-US", {
65
+ style: "currency",
66
+ currency: "USD",
67
+ })
68
+
69
+ function randomIndex(count: number) {
70
+ if (count <= 0) return 0
71
+ return Math.floor(Math.random() * count)
72
+ }
73
+
74
+ export function Prompt(props: PromptProps) {
75
+ let input: TextareaRenderable
76
+ let anchor: BoxRenderable
77
+ let autocomplete: AutocompleteRef
78
+
79
+ const keybind = useKeybind()
80
+ const local = useLocal()
81
+ const sdk = useSDK()
82
+ const route = useRoute()
83
+ const sync = useSync()
84
+ const dialog = useDialog()
85
+ const toast = useToast()
86
+ const status = createMemo(() => sync.data.session_status?.[props.sessionID ?? ""] ?? { type: "idle" })
87
+ const history = usePromptHistory()
88
+ const stash = usePromptStash()
89
+ const command = useCommandDialog()
90
+ const renderer = useRenderer()
91
+ const { theme, syntax } = useTheme()
92
+ const kv = useKV()
93
+ const list = createMemo(() => props.placeholders?.normal ?? [])
94
+ const shell = createMemo(() => props.placeholders?.shell ?? [])
95
+
96
+ function promptModelWarning() {
97
+ toast.show({
98
+ variant: "warning",
99
+ message: "Connect a provider to send prompts",
100
+ duration: 3000,
101
+ })
102
+ if (sync.data.provider.length === 0) {
103
+ dialog.replace(() => <DialogProviderConnect />)
104
+ }
105
+ }
106
+
107
+ const textareaKeybindings = useTextareaKeybindings()
108
+
109
+ const fileStyleId = syntax().getStyleId("extmark.file")!
110
+ const agentStyleId = syntax().getStyleId("extmark.agent")!
111
+ const pasteStyleId = syntax().getStyleId("extmark.paste")!
112
+ let promptPartTypeId = 0
113
+
114
+ sdk.event.on(TuiEvent.PromptAppend.type, (evt) => {
115
+ if (!input || input.isDestroyed) return
116
+ input.insertText(evt.properties.text)
117
+ setTimeout(() => {
118
+ // setTimeout is a workaround and needs to be addressed properly
119
+ if (!input || input.isDestroyed) return
120
+ input.getLayoutNode().markDirty()
121
+ input.gotoBufferEnd()
122
+ renderer.requestRender()
123
+ }, 0)
124
+ })
125
+
126
+ createEffect(() => {
127
+ if (props.disabled) input.cursorColor = theme.backgroundElement
128
+ if (!props.disabled) input.cursorColor = theme.text
129
+ })
130
+
131
+ const lastUserMessage = createMemo(() => {
132
+ if (!props.sessionID) return undefined
133
+ const messages = sync.data.message[props.sessionID]
134
+ if (!messages) return undefined
135
+ return messages.findLast((m) => m.role === "user")
136
+ })
137
+
138
+ const usage = createMemo(() => {
139
+ if (!props.sessionID) return
140
+ const msg = sync.data.message[props.sessionID] ?? []
141
+ const last = msg.findLast((item): item is AssistantMessage => item.role === "assistant" && item.tokens.output > 0)
142
+ if (!last) return
143
+
144
+ const tokens =
145
+ last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write
146
+ if (tokens <= 0) return
147
+
148
+ const model = sync.data.provider.find((item) => item.id === last.providerID)?.models[last.modelID]
149
+ const pct = model?.limit.context ? `${Math.round((tokens / model.limit.context) * 100)}%` : undefined
150
+ return {
151
+ context: pct ? `${Locale.number(tokens)} tokens (${pct})` : `${Locale.number(tokens)} tokens`,
152
+ cost: undefined,
153
+ }
154
+ })
155
+
156
+ const [store, setStore] = createStore<{
157
+ prompt: PromptInfo
158
+ mode: "normal" | "shell"
159
+ extmarkToPartIndex: Map<number, number>
160
+ interrupt: number
161
+ placeholder: number
162
+ }>({
163
+ placeholder: randomIndex(list().length),
164
+ prompt: {
165
+ input: "",
166
+ parts: [],
167
+ },
168
+ mode: "normal",
169
+ extmarkToPartIndex: new Map(),
170
+ interrupt: 0,
171
+ })
172
+
173
+ createEffect(
174
+ on(
175
+ () => props.sessionID,
176
+ () => {
177
+ setStore("placeholder", randomIndex(list().length))
178
+ },
179
+ { defer: true },
180
+ ),
181
+ )
182
+
183
+ // Initialize agent/model/variant from last user message when session changes
184
+ let syncedSessionID: string | undefined
185
+ createEffect(() => {
186
+ const sessionID = props.sessionID
187
+ const msg = lastUserMessage()
188
+
189
+ if (sessionID !== syncedSessionID) {
190
+ if (!sessionID || !msg) return
191
+
192
+ syncedSessionID = sessionID
193
+
194
+ // Only set agent if it's a primary agent (not a subagent)
195
+ const isPrimaryAgent = local.agent.list().some((x) => x.name === msg.agent)
196
+ if (msg.agent && isPrimaryAgent) {
197
+ local.agent.set(msg.agent)
198
+ if (msg.model) local.model.set(msg.model)
199
+ if (msg.variant) local.model.variant.set(msg.variant)
200
+ }
201
+ }
202
+ })
203
+
204
+ command.register(() => {
205
+ return [
206
+ {
207
+ title: "Clear prompt",
208
+ value: "prompt.clear",
209
+ category: "Prompt",
210
+ hidden: true,
211
+ onSelect: (dialog) => {
212
+ input.extmarks.clear()
213
+ input.clear()
214
+ dialog.clear()
215
+ },
216
+ },
217
+ {
218
+ title: "Submit prompt",
219
+ value: "prompt.submit",
220
+ keybind: "input_submit",
221
+ category: "Prompt",
222
+ hidden: true,
223
+ onSelect: (dialog) => {
224
+ if (!input.focused) return
225
+ submit()
226
+ dialog.clear()
227
+ },
228
+ },
229
+ {
230
+ title: "Paste",
231
+ value: "prompt.paste",
232
+ keybind: "input_paste",
233
+ category: "Prompt",
234
+ hidden: true,
235
+ onSelect: async () => {
236
+ const content = await Clipboard.read()
237
+ if (content?.mime.startsWith("image/")) {
238
+ await pasteImage({
239
+ filename: "clipboard",
240
+ mime: content.mime,
241
+ content: content.data,
242
+ })
243
+ }
244
+ },
245
+ },
246
+ {
247
+ title: "Interrupt session",
248
+ value: "session.interrupt",
249
+ keybind: "session_interrupt",
250
+ category: "Session",
251
+ hidden: true,
252
+ enabled: status().type !== "idle",
253
+ onSelect: (dialog) => {
254
+ if (autocomplete.visible) return
255
+ if (!input.focused) return
256
+ // TODO: this should be its own command
257
+ if (store.mode === "shell") {
258
+ setStore("mode", "normal")
259
+ return
260
+ }
261
+ if (!props.sessionID) return
262
+
263
+ setStore("interrupt", store.interrupt + 1)
264
+
265
+ setTimeout(() => {
266
+ setStore("interrupt", 0)
267
+ }, 5000)
268
+
269
+ if (store.interrupt >= 2) {
270
+ sdk.client.session.abort({
271
+ sessionID: props.sessionID,
272
+ })
273
+ setStore("interrupt", 0)
274
+ }
275
+ dialog.clear()
276
+ },
277
+ },
278
+ {
279
+ title: "Open editor",
280
+ category: "Session",
281
+ keybind: "editor_open",
282
+ value: "prompt.editor",
283
+ slash: {
284
+ name: "editor",
285
+ },
286
+ onSelect: async (dialog) => {
287
+ dialog.clear()
288
+
289
+ // replace summarized text parts with the actual text
290
+ const text = store.prompt.parts
291
+ .filter((p) => p.type === "text")
292
+ .reduce((acc, p) => {
293
+ if (!p.source) return acc
294
+ return acc.replace(p.source.text.value, p.text)
295
+ }, store.prompt.input)
296
+
297
+ const nonTextParts = store.prompt.parts.filter((p) => p.type !== "text")
298
+
299
+ const value = text
300
+ const content = await Editor.open({ value, renderer })
301
+ if (!content) return
302
+
303
+ input.setText(content)
304
+
305
+ // Update positions for nonTextParts based on their location in new content
306
+ // Filter out parts whose virtual text was deleted
307
+ // this handles a case where the user edits the text in the editor
308
+ // such that the virtual text moves around or is deleted
309
+ const updatedNonTextParts = nonTextParts
310
+ .map((part) => {
311
+ let virtualText = ""
312
+ if (part.type === "file" && part.source?.text) {
313
+ virtualText = part.source.text.value
314
+ } else if (part.type === "agent" && part.source) {
315
+ virtualText = part.source.value
316
+ }
317
+
318
+ if (!virtualText) return part
319
+
320
+ const newStart = content.indexOf(virtualText)
321
+ // if the virtual text is deleted, remove the part
322
+ if (newStart === -1) return null
323
+
324
+ const newEnd = newStart + virtualText.length
325
+
326
+ if (part.type === "file" && part.source?.text) {
327
+ return {
328
+ ...part,
329
+ source: {
330
+ ...part.source,
331
+ text: {
332
+ ...part.source.text,
333
+ start: newStart,
334
+ end: newEnd,
335
+ },
336
+ },
337
+ }
338
+ }
339
+
340
+ if (part.type === "agent" && part.source) {
341
+ return {
342
+ ...part,
343
+ source: {
344
+ ...part.source,
345
+ start: newStart,
346
+ end: newEnd,
347
+ },
348
+ }
349
+ }
350
+
351
+ return part
352
+ })
353
+ .filter((part) => part !== null)
354
+
355
+ setStore("prompt", {
356
+ input: content,
357
+ // keep only the non-text parts because the text parts were
358
+ // already expanded inline
359
+ parts: updatedNonTextParts,
360
+ })
361
+ restoreExtmarksFromParts(updatedNonTextParts)
362
+ input.cursorOffset = Bun.stringWidth(content)
363
+ },
364
+ },
365
+ {
366
+ title: "Skills",
367
+ value: "prompt.skills",
368
+ category: "Prompt",
369
+ slash: {
370
+ name: "skills",
371
+ },
372
+ onSelect: () => {
373
+ dialog.replace(() => (
374
+ <DialogSkill
375
+ onSelect={(skill) => {
376
+ input.setText(`/${skill} `)
377
+ setStore("prompt", {
378
+ input: `/${skill} `,
379
+ parts: [],
380
+ })
381
+ input.gotoBufferEnd()
382
+ }}
383
+ />
384
+ ))
385
+ },
386
+ },
387
+ ]
388
+ })
389
+
390
+ // Windows Terminal 1.25+ handles Ctrl+V on keydown when kitty events are
391
+ // enabled, but still reports the kitty key-release event. Probe on release.
392
+ if (process.platform === "win32") {
393
+ useKeyboard(
394
+ (evt) => {
395
+ if (!input.focused) return
396
+ if (evt.name === "v" && evt.ctrl && evt.eventType === "release") {
397
+ command.trigger("prompt.paste")
398
+ }
399
+ },
400
+ { release: true },
401
+ )
402
+ }
403
+
404
+ const ref: PromptRef = {
405
+ get focused() {
406
+ return input.focused
407
+ },
408
+ get current() {
409
+ return store.prompt
410
+ },
411
+ focus() {
412
+ input.focus()
413
+ },
414
+ blur() {
415
+ input.blur()
416
+ },
417
+ set(prompt) {
418
+ input.setText(prompt.input)
419
+ setStore("prompt", prompt)
420
+ restoreExtmarksFromParts(prompt.parts)
421
+ input.gotoBufferEnd()
422
+ },
423
+ reset() {
424
+ input.clear()
425
+ input.extmarks.clear()
426
+ setStore("prompt", {
427
+ input: "",
428
+ parts: [],
429
+ })
430
+ setStore("extmarkToPartIndex", new Map())
431
+ },
432
+ submit() {
433
+ submit()
434
+ },
435
+ }
436
+
437
+ createEffect(() => {
438
+ if (props.visible !== false) input?.focus()
439
+ if (props.visible === false) input?.blur()
440
+ })
441
+
442
+ function restoreExtmarksFromParts(parts: PromptInfo["parts"]) {
443
+ input.extmarks.clear()
444
+ setStore("extmarkToPartIndex", new Map())
445
+
446
+ parts.forEach((part, partIndex) => {
447
+ let start = 0
448
+ let end = 0
449
+ let virtualText = ""
450
+ let styleId: number | undefined
451
+
452
+ if (part.type === "file" && part.source?.text) {
453
+ start = part.source.text.start
454
+ end = part.source.text.end
455
+ virtualText = part.source.text.value
456
+ styleId = fileStyleId
457
+ } else if (part.type === "agent" && part.source) {
458
+ start = part.source.start
459
+ end = part.source.end
460
+ virtualText = part.source.value
461
+ styleId = agentStyleId
462
+ } else if (part.type === "text" && part.source?.text) {
463
+ start = part.source.text.start
464
+ end = part.source.text.end
465
+ virtualText = part.source.text.value
466
+ styleId = pasteStyleId
467
+ }
468
+
469
+ if (virtualText) {
470
+ const extmarkId = input.extmarks.create({
471
+ start,
472
+ end,
473
+ virtual: true,
474
+ styleId,
475
+ typeId: promptPartTypeId,
476
+ })
477
+ setStore("extmarkToPartIndex", (map: Map<number, number>) => {
478
+ const newMap = new Map(map)
479
+ newMap.set(extmarkId, partIndex)
480
+ return newMap
481
+ })
482
+ }
483
+ })
484
+ }
485
+
486
+ function syncExtmarksWithPromptParts() {
487
+ const allExtmarks = input.extmarks.getAllForTypeId(promptPartTypeId)
488
+ setStore(
489
+ produce((draft) => {
490
+ const newMap = new Map<number, number>()
491
+ const newParts: typeof draft.prompt.parts = []
492
+
493
+ for (const extmark of allExtmarks) {
494
+ const partIndex = draft.extmarkToPartIndex.get(extmark.id)
495
+ if (partIndex !== undefined) {
496
+ const part = draft.prompt.parts[partIndex]
497
+ if (part) {
498
+ if (part.type === "agent" && part.source) {
499
+ part.source.start = extmark.start
500
+ part.source.end = extmark.end
501
+ } else if (part.type === "file" && part.source?.text) {
502
+ part.source.text.start = extmark.start
503
+ part.source.text.end = extmark.end
504
+ } else if (part.type === "text" && part.source?.text) {
505
+ part.source.text.start = extmark.start
506
+ part.source.text.end = extmark.end
507
+ }
508
+ newMap.set(extmark.id, newParts.length)
509
+ newParts.push(part)
510
+ }
511
+ }
512
+ }
513
+
514
+ draft.extmarkToPartIndex = newMap
515
+ draft.prompt.parts = newParts
516
+ }),
517
+ )
518
+ }
519
+
520
+ command.register(() => [
521
+ {
522
+ title: "Stash prompt",
523
+ value: "prompt.stash",
524
+ category: "Prompt",
525
+ enabled: !!store.prompt.input,
526
+ onSelect: (dialog) => {
527
+ if (!store.prompt.input) return
528
+ stash.push({
529
+ input: store.prompt.input,
530
+ parts: store.prompt.parts,
531
+ })
532
+ input.extmarks.clear()
533
+ input.clear()
534
+ setStore("prompt", { input: "", parts: [] })
535
+ setStore("extmarkToPartIndex", new Map())
536
+ dialog.clear()
537
+ },
538
+ },
539
+ {
540
+ title: "Stash pop",
541
+ value: "prompt.stash.pop",
542
+ category: "Prompt",
543
+ enabled: stash.list().length > 0,
544
+ onSelect: (dialog) => {
545
+ const entry = stash.pop()
546
+ if (entry) {
547
+ input.setText(entry.input)
548
+ setStore("prompt", { input: entry.input, parts: entry.parts })
549
+ restoreExtmarksFromParts(entry.parts)
550
+ input.gotoBufferEnd()
551
+ }
552
+ dialog.clear()
553
+ },
554
+ },
555
+ {
556
+ title: "Stash list",
557
+ value: "prompt.stash.list",
558
+ category: "Prompt",
559
+ enabled: stash.list().length > 0,
560
+ onSelect: (dialog) => {
561
+ dialog.replace(() => (
562
+ <DialogStash
563
+ onSelect={(entry) => {
564
+ input.setText(entry.input)
565
+ setStore("prompt", { input: entry.input, parts: entry.parts })
566
+ restoreExtmarksFromParts(entry.parts)
567
+ input.gotoBufferEnd()
568
+ }}
569
+ />
570
+ ))
571
+ },
572
+ },
573
+ ])
574
+
575
+ async function submit() {
576
+ if (props.disabled) return
577
+ if (autocomplete?.visible) return
578
+ if (!store.prompt.input) return
579
+ const trimmed = store.prompt.input.trim()
580
+ if (trimmed === "exit" || trimmed === "quit" || trimmed === ":q") {
581
+ exit()
582
+ return
583
+ }
584
+ const selectedModel = local.model.current()
585
+ if (!selectedModel) {
586
+ promptModelWarning()
587
+ return
588
+ }
589
+
590
+ let sessionID = props.sessionID
591
+ if (sessionID == null) {
592
+ const res = await sdk.client.session.create({
593
+ workspaceID: props.workspaceID,
594
+ })
595
+
596
+ if (res.error) {
597
+ console.log("Creating a session failed:", res.error)
598
+
599
+ toast.show({
600
+ message: "Creating a session failed. Open console for more details.",
601
+ variant: "error",
602
+ })
603
+
604
+ return
605
+ }
606
+
607
+ sessionID = res.data.id
608
+ }
609
+
610
+ const messageID = MessageID.ascending()
611
+ let inputText = store.prompt.input
612
+
613
+ // Expand pasted text inline before submitting
614
+ const allExtmarks = input.extmarks.getAllForTypeId(promptPartTypeId)
615
+ const sortedExtmarks = allExtmarks.sort((a: { start: number }, b: { start: number }) => b.start - a.start)
616
+
617
+ for (const extmark of sortedExtmarks) {
618
+ const partIndex = store.extmarkToPartIndex.get(extmark.id)
619
+ if (partIndex !== undefined) {
620
+ const part = store.prompt.parts[partIndex]
621
+ if (part?.type === "text" && part.text) {
622
+ const before = inputText.slice(0, extmark.start)
623
+ const after = inputText.slice(extmark.end)
624
+ inputText = before + part.text + after
625
+ }
626
+ }
627
+ }
628
+
629
+ // Filter out text parts (pasted content) since they're now expanded inline
630
+ const nonTextParts = store.prompt.parts.filter((part) => part.type !== "text")
631
+
632
+ // Capture mode before it gets reset
633
+ const currentMode = store.mode
634
+ const variant = local.model.variant.current()
635
+
636
+ if (store.mode === "shell") {
637
+ sdk.client.session.shell({
638
+ sessionID,
639
+ agent: local.agent.current().name,
640
+ model: {
641
+ providerID: selectedModel.providerID,
642
+ modelID: selectedModel.modelID,
643
+ },
644
+ command: inputText,
645
+ })
646
+ setStore("mode", "normal")
647
+ } else if (
648
+ inputText.startsWith("/") &&
649
+ iife(() => {
650
+ const firstLine = inputText.split("\n")[0]
651
+ const command = firstLine.split(" ")[0].slice(1)
652
+ return sync.data.command.some((x) => x.name === command)
653
+ })
654
+ ) {
655
+ // Parse command from first line, preserve multi-line content in arguments
656
+ const firstLineEnd = inputText.indexOf("\n")
657
+ const firstLine = firstLineEnd === -1 ? inputText : inputText.slice(0, firstLineEnd)
658
+ const [command, ...firstLineArgs] = firstLine.split(" ")
659
+ const restOfInput = firstLineEnd === -1 ? "" : inputText.slice(firstLineEnd + 1)
660
+ const args = firstLineArgs.join(" ") + (restOfInput ? "\n" + restOfInput : "")
661
+
662
+ sdk.client.session.command({
663
+ sessionID,
664
+ command: command.slice(1),
665
+ arguments: args,
666
+ agent: local.agent.current().name,
667
+ model: `${selectedModel.providerID}/${selectedModel.modelID}`,
668
+ messageID,
669
+ variant,
670
+ parts: nonTextParts
671
+ .filter((x) => x.type === "file")
672
+ .map((x) => ({
673
+ id: PartID.ascending(),
674
+ ...x,
675
+ })),
676
+ })
677
+ } else {
678
+ sdk.client.session
679
+ .prompt({
680
+ sessionID,
681
+ ...selectedModel,
682
+ messageID,
683
+ agent: local.agent.current().name,
684
+ model: selectedModel,
685
+ variant,
686
+ parts: [
687
+ {
688
+ id: PartID.ascending(),
689
+ type: "text",
690
+ text: inputText,
691
+ },
692
+ ...nonTextParts.map(assign),
693
+ ],
694
+ })
695
+ .catch(() => {})
696
+ }
697
+ history.append({
698
+ ...store.prompt,
699
+ mode: currentMode,
700
+ })
701
+ input.extmarks.clear()
702
+ setStore("prompt", {
703
+ input: "",
704
+ parts: [],
705
+ })
706
+ setStore("extmarkToPartIndex", new Map())
707
+ props.onSubmit?.()
708
+
709
+ // temporary hack to make sure the message is sent
710
+ if (!props.sessionID)
711
+ setTimeout(() => {
712
+ route.navigate({
713
+ type: "session",
714
+ sessionID,
715
+ })
716
+ }, 50)
717
+ input.clear()
718
+ }
719
+ const exit = useExit()
720
+
721
+ function pasteText(text: string, virtualText: string) {
722
+ const currentOffset = input.visualCursor.offset
723
+ const extmarkStart = currentOffset
724
+ const extmarkEnd = extmarkStart + virtualText.length
725
+
726
+ input.insertText(virtualText + " ")
727
+
728
+ const extmarkId = input.extmarks.create({
729
+ start: extmarkStart,
730
+ end: extmarkEnd,
731
+ virtual: true,
732
+ styleId: pasteStyleId,
733
+ typeId: promptPartTypeId,
734
+ })
735
+
736
+ setStore(
737
+ produce((draft) => {
738
+ const partIndex = draft.prompt.parts.length
739
+ draft.prompt.parts.push({
740
+ type: "text" as const,
741
+ text,
742
+ source: {
743
+ text: {
744
+ start: extmarkStart,
745
+ end: extmarkEnd,
746
+ value: virtualText,
747
+ },
748
+ },
749
+ })
750
+ draft.extmarkToPartIndex.set(extmarkId, partIndex)
751
+ }),
752
+ )
753
+ }
754
+
755
+ async function pasteImage(file: { filename?: string; content: string; mime: string }) {
756
+ const currentOffset = input.visualCursor.offset
757
+ const extmarkStart = currentOffset
758
+ const count = store.prompt.parts.filter((x) => x.type === "file" && x.mime.startsWith("image/")).length
759
+ const virtualText = `[Image ${count + 1}]`
760
+ const extmarkEnd = extmarkStart + virtualText.length
761
+ const textToInsert = virtualText + " "
762
+
763
+ input.insertText(textToInsert)
764
+
765
+ const extmarkId = input.extmarks.create({
766
+ start: extmarkStart,
767
+ end: extmarkEnd,
768
+ virtual: true,
769
+ styleId: pasteStyleId,
770
+ typeId: promptPartTypeId,
771
+ })
772
+
773
+ const part: Omit<FilePart, "id" | "messageID" | "sessionID"> = {
774
+ type: "file" as const,
775
+ mime: file.mime,
776
+ filename: file.filename,
777
+ url: `data:${file.mime};base64,${file.content}`,
778
+ source: {
779
+ type: "file",
780
+ path: file.filename ?? "",
781
+ text: {
782
+ start: extmarkStart,
783
+ end: extmarkEnd,
784
+ value: virtualText,
785
+ },
786
+ },
787
+ }
788
+ setStore(
789
+ produce((draft) => {
790
+ const partIndex = draft.prompt.parts.length
791
+ draft.prompt.parts.push(part)
792
+ draft.extmarkToPartIndex.set(extmarkId, partIndex)
793
+ }),
794
+ )
795
+ return
796
+ }
797
+
798
+ const highlight = createMemo(() => {
799
+ if (keybind.leader) return theme.border
800
+ if (store.mode === "shell") return theme.primary
801
+ return local.agent.color(local.agent.current().name)
802
+ })
803
+
804
+ const showVariant = createMemo(() => {
805
+ const variants = local.model.variant.list()
806
+ if (variants.length === 0) return false
807
+ const current = local.model.variant.current()
808
+ return !!current
809
+ })
810
+
811
+ const placeholderText = createMemo(() => {
812
+ if (props.showPlaceholder === false) return undefined
813
+ if (store.mode === "shell") {
814
+ if (!shell().length) return undefined
815
+ const example = shell()[store.placeholder % shell().length]
816
+ return `Run a command... "${example}"`
817
+ }
818
+ if (!list().length) return undefined
819
+ return `Ask anything... "${list()[store.placeholder % list().length]}"`
820
+ })
821
+
822
+ const spinnerDef = createMemo(() => {
823
+ const color = local.agent.color(local.agent.current().name)
824
+ return {
825
+ frames: createFrames({
826
+ color,
827
+ style: "blocks",
828
+ inactiveFactor: 0.6,
829
+ // enableFading: false,
830
+ minAlpha: 0.3,
831
+ }),
832
+ color: createColors({
833
+ color,
834
+ style: "blocks",
835
+ inactiveFactor: 0.6,
836
+ // enableFading: false,
837
+ minAlpha: 0.3,
838
+ }),
839
+ }
840
+ })
841
+
842
+ return (
843
+ <>
844
+ <Autocomplete
845
+ sessionID={props.sessionID}
846
+ ref={(r) => (autocomplete = r)}
847
+ anchor={() => anchor}
848
+ input={() => input}
849
+ setPrompt={(cb) => {
850
+ setStore("prompt", produce(cb))
851
+ }}
852
+ setExtmark={(partIndex, extmarkId) => {
853
+ setStore("extmarkToPartIndex", (map: Map<number, number>) => {
854
+ const newMap = new Map(map)
855
+ newMap.set(extmarkId, partIndex)
856
+ return newMap
857
+ })
858
+ }}
859
+ value={store.prompt.input}
860
+ fileStyleId={fileStyleId}
861
+ agentStyleId={agentStyleId}
862
+ promptPartTypeId={() => promptPartTypeId}
863
+ />
864
+ <box ref={(r) => (anchor = r)} visible={props.visible !== false}>
865
+ <box
866
+ border={["left"]}
867
+ borderColor={highlight()}
868
+ customBorderChars={{
869
+ ...SplitBorder.customBorderChars,
870
+ bottomLeft: "╹",
871
+ }}
872
+ >
873
+ <box
874
+ paddingLeft={2}
875
+ paddingRight={2}
876
+ paddingTop={1}
877
+ flexShrink={0}
878
+ backgroundColor={theme.backgroundElement}
879
+ flexGrow={1}
880
+ >
881
+ <textarea
882
+ placeholder={placeholderText()}
883
+ placeholderColor={theme.textMuted}
884
+ textColor={keybind.leader ? theme.textMuted : theme.text}
885
+ focusedTextColor={keybind.leader ? theme.textMuted : theme.text}
886
+ minHeight={1}
887
+ maxHeight={6}
888
+ onContentChange={() => {
889
+ const value = input.plainText
890
+ setStore("prompt", "input", value)
891
+ autocomplete.onInput(value)
892
+ syncExtmarksWithPromptParts()
893
+ }}
894
+ keyBindings={textareaKeybindings()}
895
+ onKeyDown={async (e) => {
896
+ if (props.disabled) {
897
+ e.preventDefault()
898
+ return
899
+ }
900
+ // Check clipboard for images before terminal-handled paste runs.
901
+ // This helps terminals that forward Ctrl+V to the app; Windows
902
+ // Terminal 1.25+ usually handles Ctrl+V before this path.
903
+ if (keybind.match("input_paste", e)) {
904
+ const content = await Clipboard.read()
905
+ if (content?.mime.startsWith("image/")) {
906
+ e.preventDefault()
907
+ await pasteImage({
908
+ filename: "clipboard",
909
+ mime: content.mime,
910
+ content: content.data,
911
+ })
912
+ return
913
+ }
914
+ // If no image, let the default paste behavior continue
915
+ }
916
+ if (keybind.match("input_clear", e) && store.prompt.input !== "") {
917
+ input.clear()
918
+ input.extmarks.clear()
919
+ setStore("prompt", {
920
+ input: "",
921
+ parts: [],
922
+ })
923
+ setStore("extmarkToPartIndex", new Map())
924
+ return
925
+ }
926
+ if (keybind.match("app_exit", e)) {
927
+ if (store.prompt.input === "") {
928
+ await exit()
929
+ // Don't preventDefault - let textarea potentially handle the event
930
+ e.preventDefault()
931
+ return
932
+ }
933
+ }
934
+ if (e.name === "!" && input.visualCursor.offset === 0) {
935
+ setStore("placeholder", randomIndex(shell().length))
936
+ setStore("mode", "shell")
937
+ e.preventDefault()
938
+ return
939
+ }
940
+ if (store.mode === "shell") {
941
+ if ((e.name === "backspace" && input.visualCursor.offset === 0) || e.name === "escape") {
942
+ setStore("mode", "normal")
943
+ e.preventDefault()
944
+ return
945
+ }
946
+ }
947
+ if (store.mode === "normal") autocomplete.onKeyDown(e)
948
+ if (!autocomplete.visible) {
949
+ if (
950
+ (keybind.match("history_previous", e) && input.cursorOffset === 0) ||
951
+ (keybind.match("history_next", e) && input.cursorOffset === input.plainText.length)
952
+ ) {
953
+ const direction = keybind.match("history_previous", e) ? -1 : 1
954
+ const item = history.move(direction, input.plainText)
955
+
956
+ if (item) {
957
+ input.setText(item.input)
958
+ setStore("prompt", item)
959
+ setStore("mode", item.mode ?? "normal")
960
+ restoreExtmarksFromParts(item.parts)
961
+ e.preventDefault()
962
+ if (direction === -1) input.cursorOffset = 0
963
+ if (direction === 1) input.cursorOffset = input.plainText.length
964
+ }
965
+ return
966
+ }
967
+
968
+ if (keybind.match("history_previous", e) && input.visualCursor.visualRow === 0) input.cursorOffset = 0
969
+ if (keybind.match("history_next", e) && input.visualCursor.visualRow === input.height - 1)
970
+ input.cursorOffset = input.plainText.length
971
+ }
972
+ }}
973
+ onSubmit={submit}
974
+ onPaste={async (event: PasteEvent) => {
975
+ if (props.disabled) {
976
+ event.preventDefault()
977
+ return
978
+ }
979
+
980
+ // Normalize line endings at the boundary
981
+ // Windows ConPTY/Terminal often sends CR-only newlines in bracketed paste
982
+ // Replace CRLF first, then any remaining CR
983
+ const normalizedText = decodePasteBytes(event.bytes).replace(/\r\n/g, "\n").replace(/\r/g, "\n")
984
+ const pastedContent = normalizedText.trim()
985
+
986
+ // Windows Terminal <1.25 can surface image-only clipboard as an
987
+ // empty bracketed paste. Windows Terminal 1.25+ does not.
988
+ if (!pastedContent) {
989
+ command.trigger("prompt.paste")
990
+ return
991
+ }
992
+
993
+ // trim ' from the beginning and end of the pasted content. just
994
+ // ' and nothing else
995
+ const filepath = pastedContent.replace(/^'+|'+$/g, "").replace(/\\ /g, " ")
996
+ const isUrl = /^(https?):\/\//.test(filepath)
997
+ if (!isUrl) {
998
+ try {
999
+ const mime = Filesystem.mimeType(filepath)
1000
+ const filename = path.basename(filepath)
1001
+ // Handle SVG as raw text content, not as base64 image
1002
+ if (mime === "image/svg+xml") {
1003
+ event.preventDefault()
1004
+ const content = await Filesystem.readText(filepath).catch(() => {})
1005
+ if (content) {
1006
+ pasteText(content, `[SVG: ${filename ?? "image"}]`)
1007
+ return
1008
+ }
1009
+ }
1010
+ if (mime.startsWith("image/")) {
1011
+ event.preventDefault()
1012
+ const content = await Filesystem.readArrayBuffer(filepath)
1013
+ .then((buffer) => Buffer.from(buffer).toString("base64"))
1014
+ .catch(() => {})
1015
+ if (content) {
1016
+ await pasteImage({
1017
+ filename,
1018
+ mime,
1019
+ content,
1020
+ })
1021
+ return
1022
+ }
1023
+ }
1024
+ } catch {}
1025
+ }
1026
+
1027
+ const lineCount = (pastedContent.match(/\n/g)?.length ?? 0) + 1
1028
+ if (
1029
+ (lineCount >= 3 || pastedContent.length > 150) &&
1030
+ !sync.data.config.experimental?.disable_paste_summary
1031
+ ) {
1032
+ event.preventDefault()
1033
+ pasteText(pastedContent, `[Pasted ~${lineCount} lines]`)
1034
+ return
1035
+ }
1036
+
1037
+ // Force layout update and render for the pasted content
1038
+ setTimeout(() => {
1039
+ // setTimeout is a workaround and needs to be addressed properly
1040
+ if (!input || input.isDestroyed) return
1041
+ input.getLayoutNode().markDirty()
1042
+ renderer.requestRender()
1043
+ }, 0)
1044
+ }}
1045
+ ref={(r: TextareaRenderable) => {
1046
+ input = r
1047
+ if (promptPartTypeId === 0) {
1048
+ promptPartTypeId = input.extmarks.registerType("prompt-part")
1049
+ }
1050
+ props.ref?.(ref)
1051
+ setTimeout(() => {
1052
+ // setTimeout is a workaround and needs to be addressed properly
1053
+ if (!input || input.isDestroyed) return
1054
+ input.cursorColor = theme.text
1055
+ }, 0)
1056
+ }}
1057
+ onMouseDown={(r: MouseEvent) => r.target?.focus()}
1058
+ focusedBackgroundColor={theme.backgroundElement}
1059
+ cursorColor={theme.text}
1060
+ syntaxStyle={syntax()}
1061
+ />
1062
+ <box flexDirection="row" flexShrink={0} paddingTop={1} gap={1}>
1063
+ <text fg={highlight()}>
1064
+ {store.mode === "shell" ? "Shell" : Locale.titlecase(local.agent.current().name)}{" "}
1065
+ </text>
1066
+ <Show when={store.mode === "normal"}>
1067
+ <box flexDirection="row" gap={1}>
1068
+ <text flexShrink={0} fg={keybind.leader ? theme.textMuted : theme.text}>
1069
+ {local.model.parsed().model}
1070
+ </text>
1071
+ <text fg={theme.textMuted}>{local.model.parsed().provider}</text>
1072
+ <Show when={showVariant()}>
1073
+ <text fg={theme.textMuted}>·</text>
1074
+ <text>
1075
+ <span style={{ fg: theme.warning, bold: true }}>{local.model.variant.current()}</span>
1076
+ </text>
1077
+ </Show>
1078
+ </box>
1079
+ </Show>
1080
+ </box>
1081
+ </box>
1082
+ </box>
1083
+ <box
1084
+ height={1}
1085
+ border={["left"]}
1086
+ borderColor={highlight()}
1087
+ customBorderChars={{
1088
+ ...EmptyBorder,
1089
+ vertical: theme.backgroundElement.a !== 0 ? "╹" : " ",
1090
+ }}
1091
+ >
1092
+ <box
1093
+ height={1}
1094
+ border={["bottom"]}
1095
+ borderColor={theme.backgroundElement}
1096
+ customBorderChars={
1097
+ theme.backgroundElement.a !== 0
1098
+ ? {
1099
+ ...EmptyBorder,
1100
+ horizontal: "▀",
1101
+ }
1102
+ : {
1103
+ ...EmptyBorder,
1104
+ horizontal: " ",
1105
+ }
1106
+ }
1107
+ />
1108
+ </box>
1109
+ <box flexDirection="row" justifyContent="space-between">
1110
+ <Show when={status().type !== "idle"} fallback={props.hint ?? <text />}>
1111
+ <box
1112
+ flexDirection="row"
1113
+ gap={1}
1114
+ flexGrow={1}
1115
+ justifyContent={status().type === "retry" ? "space-between" : "flex-start"}
1116
+ >
1117
+ <box flexShrink={0} flexDirection="row" gap={1}>
1118
+ <box marginLeft={1}>
1119
+ <Show when={kv.get("animations_enabled", true)} fallback={<text fg={theme.textMuted}>[⋯]</text>}>
1120
+ <spinner color={spinnerDef().color} frames={spinnerDef().frames} interval={40} />
1121
+ </Show>
1122
+ </box>
1123
+ <box flexDirection="row" gap={1} flexShrink={0}>
1124
+ {(() => {
1125
+ const retry = createMemo(() => {
1126
+ const s = status()
1127
+ if (s.type !== "retry") return
1128
+ return s
1129
+ })
1130
+ const message = createMemo(() => {
1131
+ const r = retry()
1132
+ if (!r) return
1133
+ if (r.message.includes("exceeded your current quota") && r.message.includes("gemini"))
1134
+ return "gemini is way too hot right now"
1135
+ if (r.message.length > 80) return r.message.slice(0, 80) + "..."
1136
+ return r.message
1137
+ })
1138
+ const isTruncated = createMemo(() => {
1139
+ const r = retry()
1140
+ if (!r) return false
1141
+ return r.message.length > 120
1142
+ })
1143
+ const [seconds, setSeconds] = createSignal(0)
1144
+ onMount(() => {
1145
+ const timer = setInterval(() => {
1146
+ const next = retry()?.next
1147
+ if (next) setSeconds(Math.round((next - Date.now()) / 1000))
1148
+ }, 1000)
1149
+
1150
+ onCleanup(() => {
1151
+ clearInterval(timer)
1152
+ })
1153
+ })
1154
+ const handleMessageClick = () => {
1155
+ const r = retry()
1156
+ if (!r) return
1157
+ if (isTruncated()) {
1158
+ DialogAlert.show(dialog, "Retry Error", r.message)
1159
+ }
1160
+ }
1161
+
1162
+ const retryText = () => {
1163
+ const r = retry()
1164
+ if (!r) return ""
1165
+ const baseMessage = message()
1166
+ const truncatedHint = isTruncated() ? " (click to expand)" : ""
1167
+ const duration = formatDuration(seconds())
1168
+ const retryInfo = ` [retrying ${duration ? `in ${duration} ` : ""}attempt #${r.attempt}]`
1169
+ return baseMessage + truncatedHint + retryInfo
1170
+ }
1171
+
1172
+ return (
1173
+ <Show when={retry()}>
1174
+ <box onMouseUp={handleMessageClick}>
1175
+ <text fg={theme.error}>{retryText()}</text>
1176
+ </box>
1177
+ </Show>
1178
+ )
1179
+ })()}
1180
+ </box>
1181
+ </box>
1182
+ <text fg={store.interrupt > 0 ? theme.primary : theme.text}>
1183
+ esc{" "}
1184
+ <span style={{ fg: store.interrupt > 0 ? theme.primary : theme.textMuted }}>
1185
+ {store.interrupt > 0 ? "again to interrupt" : "interrupt"}
1186
+ </span>
1187
+ </text>
1188
+ </box>
1189
+ </Show>
1190
+ <Show when={status().type !== "retry"}>
1191
+ <box gap={2} flexDirection="row">
1192
+ <Switch>
1193
+ <Match when={store.mode === "normal"}>
1194
+ <Switch>
1195
+ <Match when={usage()}>
1196
+ {(item) => (
1197
+ <text fg={theme.textMuted} wrapMode="none">
1198
+ {[item().context, item().cost].filter(Boolean).join(" · ")}
1199
+ </text>
1200
+ )}
1201
+ </Match>
1202
+ <Match when={true}>
1203
+ <text fg={theme.text}>
1204
+ {keybind.print("agent_cycle")} <span style={{ fg: theme.textMuted }}>agents</span>
1205
+ </text>
1206
+ </Match>
1207
+ </Switch>
1208
+ <text fg={theme.text}>
1209
+ {keybind.print("command_list")} <span style={{ fg: theme.textMuted }}>commands</span>
1210
+ </text>
1211
+ </Match>
1212
+ <Match when={store.mode === "shell"}>
1213
+ <text fg={theme.text}>
1214
+ esc <span style={{ fg: theme.textMuted }}>exit shell mode</span>
1215
+ </text>
1216
+ </Match>
1217
+ </Switch>
1218
+ </box>
1219
+ </Show>
1220
+ </box>
1221
+ </box>
1222
+ </>
1223
+ )
1224
+ }