glitchcode-cli 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1169) hide show
  1. package/Dockerfile +18 -0
  2. package/README.md +15 -0
  3. package/bin/glitch +180 -0
  4. package/bunfig.toml +7 -0
  5. package/drizzle.config.ts +10 -0
  6. package/git +0 -0
  7. package/migration/20260127222353_familiar_lady_ursula/migration.sql +90 -0
  8. package/migration/20260127222353_familiar_lady_ursula/snapshot.json +796 -0
  9. package/migration/20260211171708_add_project_commands/migration.sql +1 -0
  10. package/migration/20260211171708_add_project_commands/snapshot.json +806 -0
  11. package/migration/20260213144116_wakeful_the_professor/migration.sql +11 -0
  12. package/migration/20260213144116_wakeful_the_professor/snapshot.json +897 -0
  13. package/migration/20260225215848_workspace/migration.sql +7 -0
  14. package/migration/20260225215848_workspace/snapshot.json +959 -0
  15. package/migration/20260227213759_add_session_workspace_id/migration.sql +2 -0
  16. package/migration/20260227213759_add_session_workspace_id/snapshot.json +983 -0
  17. package/migration/20260228203230_blue_harpoon/migration.sql +17 -0
  18. package/migration/20260228203230_blue_harpoon/snapshot.json +1102 -0
  19. package/migration/20260303231226_add_workspace_fields/migration.sql +5 -0
  20. package/migration/20260303231226_add_workspace_fields/snapshot.json +1013 -0
  21. package/migration/20260309230000_move_org_to_state/migration.sql +3 -0
  22. package/migration/20260309230000_move_org_to_state/snapshot.json +1156 -0
  23. package/migration/20260312043431_session_message_cursor/migration.sql +4 -0
  24. package/migration/20260312043431_session_message_cursor/snapshot.json +1168 -0
  25. package/migration/20260323234822_events/migration.sql +13 -0
  26. package/migration/20260323234822_events/snapshot.json +1271 -0
  27. package/migration/20260410174513_workspace-name/migration.sql +16 -0
  28. package/migration/20260410174513_workspace-name/snapshot.json +1271 -0
  29. package/migration/20260413175956_chief_energizer/migration.sql +13 -0
  30. package/migration/20260413175956_chief_energizer/snapshot.json +1399 -0
  31. package/migration/20260422160000_context_inheritance/migration.sql +3 -0
  32. package/migration/20260422170000_task_registry/migration.sql +18 -0
  33. package/migration/20260423145421_remove_session_entry/migration.sql +4 -0
  34. package/migration/20260515000000_actor_rename/migration.sql +7 -0
  35. package/migration/20260515010000_memory_fts/migration.sql +33 -0
  36. package/migration/20260515020000_user_task/migration.sql +29 -0
  37. package/migration/20260519000000_last_checkpoint_message_id/migration.sql +1 -0
  38. package/migration/20260521000000_message_agent_id/migration.sql +2 -0
  39. package/migration/20260521000100_actor_registry_v6/migration.sql +25 -0
  40. package/migration/20260521010000_memory_fts_v6/migration.sql +33 -0
  41. package/migration/20260521020000_memory_fts_triggers/migration.sql +17 -0
  42. package/migration/20260526000000_agent_id_main/migration.sql +14 -0
  43. package/migration/20260527000000_actor_lifecycle/migration.sql +8 -0
  44. package/migration/20260527000100_inbox/migration.sql +12 -0
  45. package/migration/20260529000000_task_todo_redesign/migration.sql +16 -0
  46. package/migration/20260603000000_task_in_progress_owner/migration.sql +1 -0
  47. package/migration/20260603000000_workflow_run/migration.sql +17 -0
  48. package/migration/20260604000000_workflow_script_sha/migration.sql +1 -0
  49. package/migration/20260608000000_claude_import/migration.sql +7 -0
  50. package/migration/20260608010000_claude_import_message_ids/migration.sql +1 -0
  51. package/migration/20260609000000_history_fts/migration.sql +29 -0
  52. package/migration/20260609230000_workflow_agent_timeout/migration.sql +1 -0
  53. package/migration/20260612000000_external_import/migration.sql +16 -0
  54. package/package.json +203 -0
  55. package/parsers-config.ts +290 -0
  56. package/script/build.ts +304 -0
  57. package/script/check-migrations.ts +16 -0
  58. package/script/fix-node-pty.ts +28 -0
  59. package/script/generate.ts +23 -0
  60. package/script/postinstall.mjs +102 -0
  61. package/script/publish.ts +74 -0
  62. package/script/run-workspace-server +106 -0
  63. package/script/schema.ts +63 -0
  64. package/script/time.ts +6 -0
  65. package/script/trace-imports.ts +153 -0
  66. package/script/upgrade-opentui.ts +64 -0
  67. package/src/account/account.sql.ts +39 -0
  68. package/src/account/account.ts +456 -0
  69. package/src/account/repo.ts +166 -0
  70. package/src/account/schema.ts +99 -0
  71. package/src/account/url.ts +8 -0
  72. package/src/acp/README.md +174 -0
  73. package/src/acp/agent.ts +1783 -0
  74. package/src/acp/session.ts +116 -0
  75. package/src/acp/types.ts +24 -0
  76. package/src/actor/actor.sql.ts +38 -0
  77. package/src/actor/events.ts +67 -0
  78. package/src/actor/index.ts +2 -0
  79. package/src/actor/registry.ts +412 -0
  80. package/src/actor/return-header.ts +24 -0
  81. package/src/actor/schema.ts +47 -0
  82. package/src/actor/spawn-ref.ts +16 -0
  83. package/src/actor/spawn.ts +741 -0
  84. package/src/actor/turn.ts +49 -0
  85. package/src/actor/waiter.ts +166 -0
  86. package/src/agent/agent.ts +554 -0
  87. package/src/agent/config.ts +5 -0
  88. package/src/agent/generate.txt +75 -0
  89. package/src/agent/prompt/checkpoint-writer.txt +167 -0
  90. package/src/agent/prompt/compaction.txt +9 -0
  91. package/src/agent/prompt/distill.txt +199 -0
  92. package/src/agent/prompt/dream.txt +155 -0
  93. package/src/agent/prompt/explore.txt +18 -0
  94. package/src/agent/prompt/summary.txt +11 -0
  95. package/src/agent/prompt/title.txt +44 -0
  96. package/src/audio.d.ts +9 -0
  97. package/src/auth/index.ts +97 -0
  98. package/src/bus/bus-event.ts +33 -0
  99. package/src/bus/global.ts +12 -0
  100. package/src/bus/index.ts +193 -0
  101. package/src/cli/bootstrap.ts +33 -0
  102. package/src/cli/cmd/account.ts +258 -0
  103. package/src/cli/cmd/acp.ts +70 -0
  104. package/src/cli/cmd/agent.ts +248 -0
  105. package/src/cli/cmd/cmd.ts +7 -0
  106. package/src/cli/cmd/db.ts +120 -0
  107. package/src/cli/cmd/debug/agent.ts +192 -0
  108. package/src/cli/cmd/debug/config.ts +17 -0
  109. package/src/cli/cmd/debug/file.ts +100 -0
  110. package/src/cli/cmd/debug/index.ts +48 -0
  111. package/src/cli/cmd/debug/lsp.ts +61 -0
  112. package/src/cli/cmd/debug/ripgrep.ts +105 -0
  113. package/src/cli/cmd/debug/scrap.ts +16 -0
  114. package/src/cli/cmd/debug/skill.ts +23 -0
  115. package/src/cli/cmd/debug/snapshot.ts +53 -0
  116. package/src/cli/cmd/export.ts +306 -0
  117. package/src/cli/cmd/generate.ts +50 -0
  118. package/src/cli/cmd/github.ts +1647 -0
  119. package/src/cli/cmd/import.ts +208 -0
  120. package/src/cli/cmd/init.ts +148 -0
  121. package/src/cli/cmd/mcp.ts +812 -0
  122. package/src/cli/cmd/models.ts +88 -0
  123. package/src/cli/cmd/plug.ts +233 -0
  124. package/src/cli/cmd/pr.ts +138 -0
  125. package/src/cli/cmd/providers.ts +692 -0
  126. package/src/cli/cmd/run-completion.ts +77 -0
  127. package/src/cli/cmd/run.ts +694 -0
  128. package/src/cli/cmd/serve.ts +21 -0
  129. package/src/cli/cmd/session.ts +181 -0
  130. package/src/cli/cmd/stats.ts +413 -0
  131. package/src/cli/cmd/tui/app.tsx +1140 -0
  132. package/src/cli/cmd/tui/asset/TEN_VAD_LICENSE +12 -0
  133. package/src/cli/cmd/tui/asset/charge.wav +0 -0
  134. package/src/cli/cmd/tui/asset/pulse-a.wav +0 -0
  135. package/src/cli/cmd/tui/asset/pulse-b.wav +0 -0
  136. package/src/cli/cmd/tui/asset/pulse-c.wav +0 -0
  137. package/src/cli/cmd/tui/asset/ten_vad.wasm +0 -0
  138. package/src/cli/cmd/tui/asset/ten_vad_loader.js +30 -0
  139. package/src/cli/cmd/tui/attach.ts +84 -0
  140. package/src/cli/cmd/tui/component/background-image.tsx +150 -0
  141. package/src/cli/cmd/tui/component/bg-pulse.tsx +130 -0
  142. package/src/cli/cmd/tui/component/border.tsx +21 -0
  143. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  144. package/src/cli/cmd/tui/component/dialog-agreement.tsx +111 -0
  145. package/src/cli/cmd/tui/component/dialog-command.tsx +219 -0
  146. package/src/cli/cmd/tui/component/dialog-console-org.tsx +103 -0
  147. package/src/cli/cmd/tui/component/dialog-glitch-login.tsx +253 -0
  148. package/src/cli/cmd/tui/component/dialog-go-upsell.tsx +157 -0
  149. package/src/cli/cmd/tui/component/dialog-image-list.tsx +111 -0
  150. package/src/cli/cmd/tui/component/dialog-logo-design.tsx +37 -0
  151. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  152. package/src/cli/cmd/tui/component/dialog-mimo-login.tsx +253 -0
  153. package/src/cli/cmd/tui/component/dialog-model.tsx +252 -0
  154. package/src/cli/cmd/tui/component/dialog-provider.tsx +454 -0
  155. package/src/cli/cmd/tui/component/dialog-session-delete-failed.tsx +101 -0
  156. package/src/cli/cmd/tui/component/dialog-session-list.tsx +269 -0
  157. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  158. package/src/cli/cmd/tui/component/dialog-skill.tsx +42 -0
  159. package/src/cli/cmd/tui/component/dialog-stash.tsx +87 -0
  160. package/src/cli/cmd/tui/component/dialog-status.tsx +170 -0
  161. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  162. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  163. package/src/cli/cmd/tui/component/dialog-variant.tsx +39 -0
  164. package/src/cli/cmd/tui/component/dialog-workflows.tsx +62 -0
  165. package/src/cli/cmd/tui/component/dialog-workspace-create.tsx +289 -0
  166. package/src/cli/cmd/tui/component/dialog-workspace-unavailable.tsx +81 -0
  167. package/src/cli/cmd/tui/component/dialog-worktree.tsx +90 -0
  168. package/src/cli/cmd/tui/component/error-component.tsx +92 -0
  169. package/src/cli/cmd/tui/component/logo.tsx +961 -0
  170. package/src/cli/cmd/tui/component/plugin-route-missing.tsx +14 -0
  171. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +684 -0
  172. package/src/cli/cmd/tui/component/prompt/cwd.ts +0 -0
  173. package/src/cli/cmd/tui/component/prompt/frecency.tsx +90 -0
  174. package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
  175. package/src/cli/cmd/tui/component/prompt/index.tsx +1871 -0
  176. package/src/cli/cmd/tui/component/prompt/part.ts +16 -0
  177. package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
  178. package/src/cli/cmd/tui/component/spinner.tsx +24 -0
  179. package/src/cli/cmd/tui/component/starry-background.tsx +305 -0
  180. package/src/cli/cmd/tui/component/startup-loading.tsx +67 -0
  181. package/src/cli/cmd/tui/component/task-item.tsx +63 -0
  182. package/src/cli/cmd/tui/component/textarea-keybindings.ts +73 -0
  183. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  184. package/src/cli/cmd/tui/config/cwd.ts +5 -0
  185. package/src/cli/cmd/tui/config/tui-migrate.ts +151 -0
  186. package/src/cli/cmd/tui/config/tui-schema.ts +38 -0
  187. package/src/cli/cmd/tui/config/tui.ts +219 -0
  188. package/src/cli/cmd/tui/context/args.tsx +16 -0
  189. package/src/cli/cmd/tui/context/directory.ts +15 -0
  190. package/src/cli/cmd/tui/context/event.ts +45 -0
  191. package/src/cli/cmd/tui/context/exit.tsx +65 -0
  192. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  193. package/src/cli/cmd/tui/context/keybind.tsx +105 -0
  194. package/src/cli/cmd/tui/context/kv.tsx +86 -0
  195. package/src/cli/cmd/tui/context/language.tsx +91 -0
  196. package/src/cli/cmd/tui/context/local.tsx +455 -0
  197. package/src/cli/cmd/tui/context/plugin-keybinds.ts +41 -0
  198. package/src/cli/cmd/tui/context/project.tsx +109 -0
  199. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  200. package/src/cli/cmd/tui/context/route.tsx +61 -0
  201. package/src/cli/cmd/tui/context/sdk.tsx +150 -0
  202. package/src/cli/cmd/tui/context/sync.tsx +828 -0
  203. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  204. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  205. package/src/cli/cmd/tui/context/theme/carbonfox.json +248 -0
  206. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +230 -0
  207. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +230 -0
  208. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  209. package/src/cli/cmd/tui/context/theme/cobalt2.json +225 -0
  210. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  211. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  212. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  213. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  214. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  215. package/src/cli/cmd/tui/context/theme/glitch.json +227 -0
  216. package/src/cli/cmd/tui/context/theme/gruvbox.json +242 -0
  217. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  218. package/src/cli/cmd/tui/context/theme/lucent-orng.json +234 -0
  219. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  220. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  221. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  222. package/src/cli/cmd/tui/context/theme/mimocode.json +245 -0
  223. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  224. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  225. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  226. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  227. package/src/cli/cmd/tui/context/theme/orng.json +249 -0
  228. package/src/cli/cmd/tui/context/theme/osaka-jade.json +93 -0
  229. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  230. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  231. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  232. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  233. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  234. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  235. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  236. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  237. package/src/cli/cmd/tui/context/theme.tsx +1300 -0
  238. package/src/cli/cmd/tui/context/thinking.ts +48 -0
  239. package/src/cli/cmd/tui/context/tui-config.tsx +9 -0
  240. package/src/cli/cmd/tui/event.ts +56 -0
  241. package/src/cli/cmd/tui/feature-plugins/home/footer.tsx +93 -0
  242. package/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx +193 -0
  243. package/src/cli/cmd/tui/feature-plugins/home/tips.tsx +54 -0
  244. package/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx +114 -0
  245. package/src/cli/cmd/tui/feature-plugins/sidebar/cwd.tsx +45 -0
  246. package/src/cli/cmd/tui/feature-plugins/sidebar/files.tsx +62 -0
  247. package/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx +93 -0
  248. package/src/cli/cmd/tui/feature-plugins/sidebar/goal.tsx +84 -0
  249. package/src/cli/cmd/tui/feature-plugins/sidebar/instructions.tsx +54 -0
  250. package/src/cli/cmd/tui/feature-plugins/sidebar/lsp.tsx +66 -0
  251. package/src/cli/cmd/tui/feature-plugins/sidebar/mcp.tsx +98 -0
  252. package/src/cli/cmd/tui/feature-plugins/sidebar/task.tsx +95 -0
  253. package/src/cli/cmd/tui/feature-plugins/sidebar/todo.tsx +51 -0
  254. package/src/cli/cmd/tui/feature-plugins/sidebar/tps.ts +31 -0
  255. package/src/cli/cmd/tui/feature-plugins/system/plugins.tsx +274 -0
  256. package/src/cli/cmd/tui/i18n/en.ts +436 -0
  257. package/src/cli/cmd/tui/i18n/es.ts +478 -0
  258. package/src/cli/cmd/tui/i18n/fr.ts +485 -0
  259. package/src/cli/cmd/tui/i18n/ja.ts +437 -0
  260. package/src/cli/cmd/tui/i18n/locales.ts +82 -0
  261. package/src/cli/cmd/tui/i18n/ru.ts +497 -0
  262. package/src/cli/cmd/tui/i18n/tr.ts +398 -0
  263. package/src/cli/cmd/tui/i18n/zh.ts +429 -0
  264. package/src/cli/cmd/tui/i18n/zht.ts +405 -0
  265. package/src/cli/cmd/tui/layer.ts +6 -0
  266. package/src/cli/cmd/tui/plugin/api.tsx +402 -0
  267. package/src/cli/cmd/tui/plugin/index.ts +3 -0
  268. package/src/cli/cmd/tui/plugin/internal.ts +35 -0
  269. package/src/cli/cmd/tui/plugin/runtime.ts +1057 -0
  270. package/src/cli/cmd/tui/plugin/slots.tsx +60 -0
  271. package/src/cli/cmd/tui/routes/home.tsx +165 -0
  272. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +76 -0
  273. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +116 -0
  274. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +47 -0
  275. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  276. package/src/cli/cmd/tui/routes/session/footer.tsx +91 -0
  277. package/src/cli/cmd/tui/routes/session/index.tsx +2567 -0
  278. package/src/cli/cmd/tui/routes/session/permission.tsx +691 -0
  279. package/src/cli/cmd/tui/routes/session/question.tsx +488 -0
  280. package/src/cli/cmd/tui/routes/session/sidebar.tsx +97 -0
  281. package/src/cli/cmd/tui/routes/session/subagent-footer.tsx +142 -0
  282. package/src/cli/cmd/tui/thread.ts +323 -0
  283. package/src/cli/cmd/tui/ui/dialog-alert.tsx +61 -0
  284. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +95 -0
  285. package/src/cli/cmd/tui/ui/dialog-export-options.tsx +223 -0
  286. package/src/cli/cmd/tui/ui/dialog-help.tsx +42 -0
  287. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +123 -0
  288. package/src/cli/cmd/tui/ui/dialog-select.tsx +463 -0
  289. package/src/cli/cmd/tui/ui/dialog.tsx +207 -0
  290. package/src/cli/cmd/tui/ui/link.tsx +28 -0
  291. package/src/cli/cmd/tui/ui/spinner.ts +378 -0
  292. package/src/cli/cmd/tui/ui/toast.tsx +102 -0
  293. package/src/cli/cmd/tui/util/clipboard.ts +203 -0
  294. package/src/cli/cmd/tui/util/editor.ts +36 -0
  295. package/src/cli/cmd/tui/util/image-protocol.ts +35 -0
  296. package/src/cli/cmd/tui/util/index.ts +6 -0
  297. package/src/cli/cmd/tui/util/model.ts +23 -0
  298. package/src/cli/cmd/tui/util/pinyin.ts +20 -0
  299. package/src/cli/cmd/tui/util/provider-origin.ts +7 -0
  300. package/src/cli/cmd/tui/util/revert-diff.ts +18 -0
  301. package/src/cli/cmd/tui/util/scroll.ts +23 -0
  302. package/src/cli/cmd/tui/util/selection.ts +23 -0
  303. package/src/cli/cmd/tui/util/signal.ts +41 -0
  304. package/src/cli/cmd/tui/util/sound.ts +154 -0
  305. package/src/cli/cmd/tui/util/system-locale.ts +209 -0
  306. package/src/cli/cmd/tui/util/terminal.ts +110 -0
  307. package/src/cli/cmd/tui/util/transcript.ts +112 -0
  308. package/src/cli/cmd/tui/util/vad.ts +229 -0
  309. package/src/cli/cmd/tui/util/voice.ts +450 -0
  310. package/src/cli/cmd/tui/win32.ts +130 -0
  311. package/src/cli/cmd/tui/worker.ts +104 -0
  312. package/src/cli/cmd/uninstall.ts +351 -0
  313. package/src/cli/cmd/upgrade.ts +79 -0
  314. package/src/cli/cmd/web.ts +82 -0
  315. package/src/cli/effect/prompt.ts +25 -0
  316. package/src/cli/error.ts +82 -0
  317. package/src/cli/heap.ts +59 -0
  318. package/src/cli/i18n.ts +16 -0
  319. package/src/cli/logo.ts +53 -0
  320. package/src/cli/network.ts +62 -0
  321. package/src/cli/ui.ts +133 -0
  322. package/src/cli/upgrade.ts +41 -0
  323. package/src/command/index.ts +276 -0
  324. package/src/command/template/initialize.txt +66 -0
  325. package/src/command/template/review.txt +101 -0
  326. package/src/config/agent.ts +197 -0
  327. package/src/config/command.ts +69 -0
  328. package/src/config/config.ts +1036 -0
  329. package/src/config/console-state.ts +16 -0
  330. package/src/config/entry-name.ts +16 -0
  331. package/src/config/error.ts +21 -0
  332. package/src/config/formatter.ts +17 -0
  333. package/src/config/history.ts +21 -0
  334. package/src/config/index.ts +16 -0
  335. package/src/config/keybinds.ts +127 -0
  336. package/src/config/layout.ts +10 -0
  337. package/src/config/lsp.ts +45 -0
  338. package/src/config/managed.ts +70 -0
  339. package/src/config/markdown.ts +97 -0
  340. package/src/config/mcp.ts +172 -0
  341. package/src/config/model-id.ts +14 -0
  342. package/src/config/parse.ts +44 -0
  343. package/src/config/paths.ts +73 -0
  344. package/src/config/permission.ts +76 -0
  345. package/src/config/plugin.ts +88 -0
  346. package/src/config/provider.ts +118 -0
  347. package/src/config/server.ts +20 -0
  348. package/src/config/skills.ts +16 -0
  349. package/src/config/variable.ts +90 -0
  350. package/src/control-plane/adaptors/index.ts +52 -0
  351. package/src/control-plane/adaptors/worktree.ts +47 -0
  352. package/src/control-plane/dev/debug-workspace-plugin.ts +73 -0
  353. package/src/control-plane/schema.ts +19 -0
  354. package/src/control-plane/sse.ts +66 -0
  355. package/src/control-plane/types.ts +34 -0
  356. package/src/control-plane/util.ts +37 -0
  357. package/src/control-plane/workspace-context.ts +26 -0
  358. package/src/control-plane/workspace.sql.ts +17 -0
  359. package/src/control-plane/workspace.ts +615 -0
  360. package/src/effect/app-runtime.ts +146 -0
  361. package/src/effect/bootstrap-runtime.ts +33 -0
  362. package/src/effect/bridge.ts +48 -0
  363. package/src/effect/cross-spawn-spawner.ts +514 -0
  364. package/src/effect/index.ts +5 -0
  365. package/src/effect/instance-ref.ts +11 -0
  366. package/src/effect/instance-registry.ts +12 -0
  367. package/src/effect/instance-state.ts +81 -0
  368. package/src/effect/logger.ts +73 -0
  369. package/src/effect/memo-map.ts +3 -0
  370. package/src/effect/observability.ts +107 -0
  371. package/src/effect/run-service.ts +52 -0
  372. package/src/effect/runner.ts +210 -0
  373. package/src/effect/runtime.ts +19 -0
  374. package/src/env/index.ts +37 -0
  375. package/src/file/ignore.ts +81 -0
  376. package/src/file/index.ts +664 -0
  377. package/src/file/protected.ts +59 -0
  378. package/src/file/ripgrep.ts +485 -0
  379. package/src/file/watcher.ts +163 -0
  380. package/src/flag/flag.ts +172 -0
  381. package/src/format/formatter.ts +403 -0
  382. package/src/format/index.ts +203 -0
  383. package/src/git/index.ts +260 -0
  384. package/src/global/index.ts +54 -0
  385. package/src/history/backfill.ts +162 -0
  386. package/src/history/extract.ts +67 -0
  387. package/src/history/fts-query.ts +15 -0
  388. package/src/history/fts.sql.ts +20 -0
  389. package/src/history/index.ts +10 -0
  390. package/src/history/resolve.ts +65 -0
  391. package/src/history/service.ts +258 -0
  392. package/src/history/writer.ts +112 -0
  393. package/src/id/id.ts +87 -0
  394. package/src/ide/index.ts +73 -0
  395. package/src/inbox/inbox-ref.ts +38 -0
  396. package/src/inbox/inbox.sql.ts +26 -0
  397. package/src/inbox/inbox.ts +223 -0
  398. package/src/inbox/index.ts +3 -0
  399. package/src/inbox/render.ts +40 -0
  400. package/src/index.ts +274 -0
  401. package/src/installation/index.ts +362 -0
  402. package/src/installation/version.ts +8 -0
  403. package/src/lsp/client.ts +249 -0
  404. package/src/lsp/diagnostic.ts +29 -0
  405. package/src/lsp/index.ts +3 -0
  406. package/src/lsp/language.ts +120 -0
  407. package/src/lsp/launch.ts +21 -0
  408. package/src/lsp/lsp.ts +519 -0
  409. package/src/lsp/server.ts +1956 -0
  410. package/src/mcp/auth.ts +144 -0
  411. package/src/mcp/index.ts +944 -0
  412. package/src/mcp/oauth-callback.ts +232 -0
  413. package/src/mcp/oauth-provider.ts +214 -0
  414. package/src/memory/fts-query.ts +37 -0
  415. package/src/memory/fts.sql.ts +19 -0
  416. package/src/memory/index.ts +1 -0
  417. package/src/memory/paths.ts +116 -0
  418. package/src/memory/reconcile.ts +144 -0
  419. package/src/memory/service.ts +144 -0
  420. package/src/metrics/client.ts +40 -0
  421. package/src/metrics/event.ts +43 -0
  422. package/src/metrics/index.ts +5 -0
  423. package/src/metrics/installation.ts +18 -0
  424. package/src/metrics/subscriber.ts +58 -0
  425. package/src/metrics/util.ts +9 -0
  426. package/src/node.ts +6 -0
  427. package/src/npm/config.ts +0 -0
  428. package/src/npm/index.ts +293 -0
  429. package/src/npmcli-config.d.ts +43 -0
  430. package/src/patch/index.ts +680 -0
  431. package/src/permission/arity.ts +163 -0
  432. package/src/permission/evaluate.ts +15 -0
  433. package/src/permission/index.ts +378 -0
  434. package/src/permission/schema.ts +17 -0
  435. package/src/plugin/checkpoint-splitover.ts +60 -0
  436. package/src/plugin/cloudflare.ts +76 -0
  437. package/src/plugin/codex.ts +607 -0
  438. package/src/plugin/github-copilot/copilot.ts +368 -0
  439. package/src/plugin/github-copilot/models.ts +153 -0
  440. package/src/plugin/glitch.ts +243 -0
  441. package/src/plugin/index.ts +581 -0
  442. package/src/plugin/install.ts +439 -0
  443. package/src/plugin/loader.ts +216 -0
  444. package/src/plugin/matcher.ts +33 -0
  445. package/src/plugin/meta.ts +188 -0
  446. package/src/plugin/mimo.ts +243 -0
  447. package/src/plugin/shared.ts +323 -0
  448. package/src/plugin/subagent-progress-checker.ts +147 -0
  449. package/src/project/bootstrap.ts +59 -0
  450. package/src/project/index.ts +2 -0
  451. package/src/project/instance.ts +190 -0
  452. package/src/project/project-id.ts +48 -0
  453. package/src/project/project.sql.ts +16 -0
  454. package/src/project/project.ts +522 -0
  455. package/src/project/schema.ts +15 -0
  456. package/src/project/vcs.ts +227 -0
  457. package/src/project/workspace-trust.ts +67 -0
  458. package/src/provider/auth.ts +234 -0
  459. package/src/provider/error.ts +216 -0
  460. package/src/provider/index.ts +5 -0
  461. package/src/provider/models.ts +180 -0
  462. package/src/provider/provider.ts +1788 -0
  463. package/src/provider/schema.ts +36 -0
  464. package/src/provider/sdk/copilot/README.md +5 -0
  465. package/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts +170 -0
  466. package/src/provider/sdk/copilot/chat/get-response-metadata.ts +15 -0
  467. package/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts +19 -0
  468. package/src/provider/sdk/copilot/chat/openai-compatible-api-types.ts +64 -0
  469. package/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts +815 -0
  470. package/src/provider/sdk/copilot/chat/openai-compatible-chat-options.ts +28 -0
  471. package/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts +44 -0
  472. package/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts +83 -0
  473. package/src/provider/sdk/copilot/copilot-provider.ts +100 -0
  474. package/src/provider/sdk/copilot/index.ts +2 -0
  475. package/src/provider/sdk/copilot/openai-compatible-error.ts +27 -0
  476. package/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts +335 -0
  477. package/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts +22 -0
  478. package/src/provider/sdk/copilot/responses/openai-config.ts +18 -0
  479. package/src/provider/sdk/copilot/responses/openai-error.ts +22 -0
  480. package/src/provider/sdk/copilot/responses/openai-responses-api-types.ts +214 -0
  481. package/src/provider/sdk/copilot/responses/openai-responses-language-model.ts +1770 -0
  482. package/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts +173 -0
  483. package/src/provider/sdk/copilot/responses/openai-responses-settings.ts +1 -0
  484. package/src/provider/sdk/copilot/responses/tool/code-interpreter.ts +87 -0
  485. package/src/provider/sdk/copilot/responses/tool/file-search.ts +127 -0
  486. package/src/provider/sdk/copilot/responses/tool/image-generation.ts +114 -0
  487. package/src/provider/sdk/copilot/responses/tool/local-shell.ts +64 -0
  488. package/src/provider/sdk/copilot/responses/tool/web-search-preview.ts +103 -0
  489. package/src/provider/sdk/copilot/responses/tool/web-search.ts +102 -0
  490. package/src/provider/transform.ts +1322 -0
  491. package/src/pty/index.ts +364 -0
  492. package/src/pty/pty.bun.ts +26 -0
  493. package/src/pty/pty.node.ts +27 -0
  494. package/src/pty/pty.ts +25 -0
  495. package/src/pty/schema.ts +17 -0
  496. package/src/question/index.ts +252 -0
  497. package/src/question/schema.ts +17 -0
  498. package/src/server/adapter.bun.ts +40 -0
  499. package/src/server/adapter.node.ts +66 -0
  500. package/src/server/adapter.ts +21 -0
  501. package/src/server/auth.ts +16 -0
  502. package/src/server/error.ts +53 -0
  503. package/src/server/event.ts +7 -0
  504. package/src/server/fence.ts +81 -0
  505. package/src/server/mdns.ts +60 -0
  506. package/src/server/middleware.ts +96 -0
  507. package/src/server/projectors.ts +28 -0
  508. package/src/server/proxy.ts +171 -0
  509. package/src/server/pty-ticket.ts +42 -0
  510. package/src/server/routes/control/index.ts +160 -0
  511. package/src/server/routes/control/workspace.ts +203 -0
  512. package/src/server/routes/global.ts +367 -0
  513. package/src/server/routes/instance/bash-interactive.ts +82 -0
  514. package/src/server/routes/instance/config.ts +89 -0
  515. package/src/server/routes/instance/event.ts +108 -0
  516. package/src/server/routes/instance/experimental.ts +408 -0
  517. package/src/server/routes/instance/file.ts +190 -0
  518. package/src/server/routes/instance/httpapi/config.ts +51 -0
  519. package/src/server/routes/instance/httpapi/permission.ts +72 -0
  520. package/src/server/routes/instance/httpapi/project.ts +62 -0
  521. package/src/server/routes/instance/httpapi/provider.ts +142 -0
  522. package/src/server/routes/instance/httpapi/question.ts +121 -0
  523. package/src/server/routes/instance/httpapi/server.ts +136 -0
  524. package/src/server/routes/instance/index.ts +301 -0
  525. package/src/server/routes/instance/mcp.ts +260 -0
  526. package/src/server/routes/instance/middleware.ts +35 -0
  527. package/src/server/routes/instance/permission.ts +73 -0
  528. package/src/server/routes/instance/project.ts +122 -0
  529. package/src/server/routes/instance/provider.ts +158 -0
  530. package/src/server/routes/instance/pty.ts +302 -0
  531. package/src/server/routes/instance/question.ts +162 -0
  532. package/src/server/routes/instance/session.ts +1296 -0
  533. package/src/server/routes/instance/sync.ts +143 -0
  534. package/src/server/routes/instance/trace.ts +59 -0
  535. package/src/server/routes/instance/tui.ts +384 -0
  536. package/src/server/routes/instance/workflows.ts +72 -0
  537. package/src/server/routes/ui.ts +37 -0
  538. package/src/server/server.ts +136 -0
  539. package/src/server/workspace.ts +122 -0
  540. package/src/session/auto-dream.ts +123 -0
  541. package/src/session/boundary.ts +77 -0
  542. package/src/session/budgeted-read.ts +118 -0
  543. package/src/session/checkpoint-align.ts +29 -0
  544. package/src/session/checkpoint-context.ts +36 -0
  545. package/src/session/checkpoint-paths.ts +86 -0
  546. package/src/session/checkpoint-progress-reconcile.ts +111 -0
  547. package/src/session/checkpoint-retry.ts +192 -0
  548. package/src/session/checkpoint-templates.ts +114 -0
  549. package/src/session/checkpoint-validator.ts +259 -0
  550. package/src/session/checkpoint.ts +1478 -0
  551. package/src/session/classify.ts +92 -0
  552. package/src/session/claude-import.ts +381 -0
  553. package/src/session/codex-import.ts +416 -0
  554. package/src/session/compaction.ts +543 -0
  555. package/src/session/external-import.sql.ts +18 -0
  556. package/src/session/external-import.ts +136 -0
  557. package/src/session/goal.ts +232 -0
  558. package/src/session/index.ts +1 -0
  559. package/src/session/instruction.ts +276 -0
  560. package/src/session/last-message-info.ts +32 -0
  561. package/src/session/llm-request-prefix.ts +82 -0
  562. package/src/session/llm.ts +725 -0
  563. package/src/session/max-mode.ts +397 -0
  564. package/src/session/message-v2.ts +1136 -0
  565. package/src/session/message.ts +191 -0
  566. package/src/session/opencode-import.ts +281 -0
  567. package/src/session/overflow.ts +53 -0
  568. package/src/session/prefix-capture-ref.ts +48 -0
  569. package/src/session/processor.ts +968 -0
  570. package/src/session/projectors.ts +137 -0
  571. package/src/session/prompt/anthropic.txt +154 -0
  572. package/src/session/prompt/beast.txt +155 -0
  573. package/src/session/prompt/build-switch.txt +5 -0
  574. package/src/session/prompt/codex.txt +79 -0
  575. package/src/session/prompt/compose.txt +115 -0
  576. package/src/session/prompt/copilot-gpt-5.txt +143 -0
  577. package/src/session/prompt/default.txt +151 -0
  578. package/src/session/prompt/gemini.txt +155 -0
  579. package/src/session/prompt/gpt.txt +107 -0
  580. package/src/session/prompt/kimi.txt +95 -0
  581. package/src/session/prompt/max-steps.txt +16 -0
  582. package/src/session/prompt/text-loop-recovery.ts +40 -0
  583. package/src/session/prompt/trinity.txt +97 -0
  584. package/src/session/prompt.ts +3476 -0
  585. package/src/session/prune.ts +481 -0
  586. package/src/session/retry.ts +166 -0
  587. package/src/session/revert.ts +161 -0
  588. package/src/session/run-state.ts +135 -0
  589. package/src/session/schema.ts +36 -0
  590. package/src/session/session.sql.ts +110 -0
  591. package/src/session/session.ts +908 -0
  592. package/src/session/status.ts +89 -0
  593. package/src/session/summary.ts +163 -0
  594. package/src/session/system.ts +86 -0
  595. package/src/session/todo.ts +77 -0
  596. package/src/share/index.ts +2 -0
  597. package/src/share/session.ts +57 -0
  598. package/src/share/share-next.ts +381 -0
  599. package/src/share/share.sql.ts +13 -0
  600. package/src/shell/shell.ts +110 -0
  601. package/src/skill/compose/.bundle/ask/SKILL.md +58 -0
  602. package/src/skill/compose/.bundle/brainstorm/SKILL.md +220 -0
  603. package/src/skill/compose/.bundle/brainstorm/scripts/frame-template.html +214 -0
  604. package/src/skill/compose/.bundle/brainstorm/scripts/helper.js +88 -0
  605. package/src/skill/compose/.bundle/brainstorm/scripts/server.cjs +354 -0
  606. package/src/skill/compose/.bundle/brainstorm/scripts/start-server.sh +148 -0
  607. package/src/skill/compose/.bundle/brainstorm/scripts/stop-server.sh +56 -0
  608. package/src/skill/compose/.bundle/brainstorm/spec-document-reviewer-prompt.md +50 -0
  609. package/src/skill/compose/.bundle/brainstorm/visual-companion.md +287 -0
  610. package/src/skill/compose/.bundle/debug/CREATION-LOG.md +119 -0
  611. package/src/skill/compose/.bundle/debug/SKILL.md +297 -0
  612. package/src/skill/compose/.bundle/debug/condition-based-waiting-example.ts +158 -0
  613. package/src/skill/compose/.bundle/debug/condition-based-waiting.md +115 -0
  614. package/src/skill/compose/.bundle/debug/defense-in-depth.md +122 -0
  615. package/src/skill/compose/.bundle/debug/find-polluter.sh +63 -0
  616. package/src/skill/compose/.bundle/debug/root-cause-tracing.md +169 -0
  617. package/src/skill/compose/.bundle/debug/test-academic.md +14 -0
  618. package/src/skill/compose/.bundle/debug/test-pressure-1.md +58 -0
  619. package/src/skill/compose/.bundle/debug/test-pressure-2.md +68 -0
  620. package/src/skill/compose/.bundle/debug/test-pressure-3.md +69 -0
  621. package/src/skill/compose/.bundle/execute/SKILL.md +71 -0
  622. package/src/skill/compose/.bundle/feedback/SKILL.md +214 -0
  623. package/src/skill/compose/.bundle/merge/SKILL.md +252 -0
  624. package/src/skill/compose/.bundle/new-skill/SKILL.md +656 -0
  625. package/src/skill/compose/.bundle/new-skill/anthropic-best-practices.md +1150 -0
  626. package/src/skill/compose/.bundle/new-skill/examples/CLAUDE_MD_TESTING.md +189 -0
  627. package/src/skill/compose/.bundle/new-skill/graphviz-conventions.dot +172 -0
  628. package/src/skill/compose/.bundle/new-skill/persuasion-principles.md +187 -0
  629. package/src/skill/compose/.bundle/new-skill/render-graphs.js +168 -0
  630. package/src/skill/compose/.bundle/new-skill/testing-skills-with-subagents.md +384 -0
  631. package/src/skill/compose/.bundle/parallel/SKILL.md +182 -0
  632. package/src/skill/compose/.bundle/plan/SKILL.md +161 -0
  633. package/src/skill/compose/.bundle/plan/plan-document-reviewer-prompt.md +50 -0
  634. package/src/skill/compose/.bundle/report/SKILL.md +180 -0
  635. package/src/skill/compose/.bundle/review/SKILL.md +104 -0
  636. package/src/skill/compose/.bundle/review/code-reviewer.md +171 -0
  637. package/src/skill/compose/.bundle/self-extend/SKILL.md +131 -0
  638. package/src/skill/compose/.bundle/self-extend/reference/hook-api.md +242 -0
  639. package/src/skill/compose/.bundle/self-extend/reference/skill-api.md +114 -0
  640. package/src/skill/compose/.bundle/self-extend/reference/tool-api.md +115 -0
  641. package/src/skill/compose/.bundle/self-extend/reference/tui-api.md +258 -0
  642. package/src/skill/compose/.bundle/subagent/SKILL.md +344 -0
  643. package/src/skill/compose/.bundle/subagent/code-quality-reviewer-prompt.md +24 -0
  644. package/src/skill/compose/.bundle/subagent/implementer-prompt.md +126 -0
  645. package/src/skill/compose/.bundle/subagent/spec-reviewer-prompt.md +112 -0
  646. package/src/skill/compose/.bundle/tdd/SKILL.md +372 -0
  647. package/src/skill/compose/.bundle/tdd/testing-anti-patterns.md +299 -0
  648. package/src/skill/compose/.bundle/verify/SKILL.md +140 -0
  649. package/src/skill/compose/.bundle/worktree/SKILL.md +234 -0
  650. package/src/skill/compose/LICENSE-karpathy +28 -0
  651. package/src/skill/compose/LICENSE-superpowers +26 -0
  652. package/src/skill/compose/bundle.macro.ts +30 -0
  653. package/src/skill/compose/extract.ts +85 -0
  654. package/src/skill/discovery.ts +116 -0
  655. package/src/skill/index.ts +317 -0
  656. package/src/snapshot/index.ts +777 -0
  657. package/src/sql.d.ts +4 -0
  658. package/src/storage/db.bun.ts +8 -0
  659. package/src/storage/db.node.ts +8 -0
  660. package/src/storage/db.ts +172 -0
  661. package/src/storage/index.ts +26 -0
  662. package/src/storage/json-migration.ts +426 -0
  663. package/src/storage/read-sqlite.bun.ts +11 -0
  664. package/src/storage/read-sqlite.node.ts +13 -0
  665. package/src/storage/read-sqlite.ts +10 -0
  666. package/src/storage/schema.sql.ts +10 -0
  667. package/src/storage/schema.ts +7 -0
  668. package/src/storage/storage.ts +331 -0
  669. package/src/sync/README.md +179 -0
  670. package/src/sync/event.sql.ts +16 -0
  671. package/src/sync/index.ts +278 -0
  672. package/src/sync/schema.ts +14 -0
  673. package/src/task/events.ts +28 -0
  674. package/src/task/gate-state.ts +54 -0
  675. package/src/task/gate.ts +116 -0
  676. package/src/task/index.ts +1 -0
  677. package/src/task/registry.ts +394 -0
  678. package/src/task/schema.ts +43 -0
  679. package/src/task/task.sql.ts +50 -0
  680. package/src/team/events.ts +22 -0
  681. package/src/team/index.ts +113 -0
  682. package/src/team/schema.ts +31 -0
  683. package/src/temporary.ts +33 -0
  684. package/src/tool/actor.shell.txt +72 -0
  685. package/src/tool/actor.ts +808 -0
  686. package/src/tool/actor.txt +103 -0
  687. package/src/tool/apply_patch.ts +308 -0
  688. package/src/tool/apply_patch.txt +33 -0
  689. package/src/tool/bash-interactive.ts +183 -0
  690. package/src/tool/bash.ts +696 -0
  691. package/src/tool/bash.txt +123 -0
  692. package/src/tool/change-directory.ts +91 -0
  693. package/src/tool/codesearch.ts +63 -0
  694. package/src/tool/codesearch.txt +12 -0
  695. package/src/tool/context-compressor.ts +125 -0
  696. package/src/tool/context-compressor.txt +5 -0
  697. package/src/tool/deep-file-analysis.ts +817 -0
  698. package/src/tool/deep-file-analysis.txt +12 -0
  699. package/src/tool/edit.ts +685 -0
  700. package/src/tool/edit.txt +10 -0
  701. package/src/tool/external-directory.ts +132 -0
  702. package/src/tool/glob.ts +100 -0
  703. package/src/tool/glob.txt +6 -0
  704. package/src/tool/grep.ts +145 -0
  705. package/src/tool/grep.txt +8 -0
  706. package/src/tool/history.ts +146 -0
  707. package/src/tool/history.txt +17 -0
  708. package/src/tool/index.ts +4 -0
  709. package/src/tool/invalid.ts +20 -0
  710. package/src/tool/invocation-style.ts +17 -0
  711. package/src/tool/lsp.ts +91 -0
  712. package/src/tool/lsp.txt +19 -0
  713. package/src/tool/mcp-exa.ts +78 -0
  714. package/src/tool/memory-path-guard.ts +162 -0
  715. package/src/tool/memory.ts +81 -0
  716. package/src/tool/memory.txt +69 -0
  717. package/src/tool/model-router.ts +86 -0
  718. package/src/tool/model-router.txt +6 -0
  719. package/src/tool/multiedit.ts +61 -0
  720. package/src/tool/multiedit.txt +41 -0
  721. package/src/tool/pattern-learner.ts +179 -0
  722. package/src/tool/pattern-learner.txt +4 -0
  723. package/src/tool/plan-enter.txt +14 -0
  724. package/src/tool/plan-exit.txt +13 -0
  725. package/src/tool/plan.ts +90 -0
  726. package/src/tool/question.ts +67 -0
  727. package/src/tool/question.txt +10 -0
  728. package/src/tool/read.ts +327 -0
  729. package/src/tool/read.txt +14 -0
  730. package/src/tool/recoverable.ts +35 -0
  731. package/src/tool/registry.ts +451 -0
  732. package/src/tool/schema.ts +17 -0
  733. package/src/tool/self-healing.ts +145 -0
  734. package/src/tool/self-healing.txt +5 -0
  735. package/src/tool/self-supervision.ts +208 -0
  736. package/src/tool/self-supervision.txt +4 -0
  737. package/src/tool/session-cwd.ts +35 -0
  738. package/src/tool/shell-tokenize.ts +346 -0
  739. package/src/tool/shell-wrap.ts +190 -0
  740. package/src/tool/skill.ts +76 -0
  741. package/src/tool/skill.txt +5 -0
  742. package/src/tool/smart-predict.ts +148 -0
  743. package/src/tool/smart-predict.txt +4 -0
  744. package/src/tool/task.shell.txt +57 -0
  745. package/src/tool/task.ts +456 -0
  746. package/src/tool/task.txt +56 -0
  747. package/src/tool/tool.ts +166 -0
  748. package/src/tool/truncate.ts +201 -0
  749. package/src/tool/truncation-dir.ts +4 -0
  750. package/src/tool/webfetch.ts +199 -0
  751. package/src/tool/webfetch.txt +13 -0
  752. package/src/tool/websearch/glitch.ts +118 -0
  753. package/src/tool/websearch/index.ts +104 -0
  754. package/src/tool/websearch/mimo.ts +118 -0
  755. package/src/tool/websearch/websearch.txt +14 -0
  756. package/src/tool/workflow.ts +164 -0
  757. package/src/tool/workflow.txt +25 -0
  758. package/src/tool/write.ts +88 -0
  759. package/src/tool/write.txt +9 -0
  760. package/src/util/abort.ts +35 -0
  761. package/src/util/archive.ts +15 -0
  762. package/src/util/color.ts +17 -0
  763. package/src/util/data-url.ts +9 -0
  764. package/src/util/defer.ts +10 -0
  765. package/src/util/effect-http-client.ts +11 -0
  766. package/src/util/effect-zod.ts +367 -0
  767. package/src/util/error.ts +78 -0
  768. package/src/util/filesystem.ts +243 -0
  769. package/src/util/fn.ts +21 -0
  770. package/src/util/format.ts +20 -0
  771. package/src/util/glitch-process.ts +24 -0
  772. package/src/util/iife.ts +3 -0
  773. package/src/util/index.ts +12 -0
  774. package/src/util/keybind.ts +101 -0
  775. package/src/util/lazy.ts +18 -0
  776. package/src/util/local-context.ts +23 -0
  777. package/src/util/locale.ts +79 -0
  778. package/src/util/lock.ts +96 -0
  779. package/src/util/log.ts +234 -0
  780. package/src/util/media.ts +26 -0
  781. package/src/util/mimo-process.ts +24 -0
  782. package/src/util/network.ts +9 -0
  783. package/src/util/process.ts +174 -0
  784. package/src/util/queue.ts +60 -0
  785. package/src/util/record.ts +3 -0
  786. package/src/util/rpc.ts +64 -0
  787. package/src/util/schema.ts +53 -0
  788. package/src/util/scrap.ts +10 -0
  789. package/src/util/signal.ts +12 -0
  790. package/src/util/timeout.ts +14 -0
  791. package/src/util/token.ts +5 -0
  792. package/src/util/update-schema.ts +13 -0
  793. package/src/util/which.ts +14 -0
  794. package/src/util/wildcard.ts +57 -0
  795. package/src/workflow/builtin/deep-research.js +391 -0
  796. package/src/workflow/builtin/model-router.js +92 -0
  797. package/src/workflow/builtin/self-healing.js +94 -0
  798. package/src/workflow/builtin/swarm-mode.js +140 -0
  799. package/src/workflow/builtin.ts +65 -0
  800. package/src/workflow/events.ts +72 -0
  801. package/src/workflow/meta.ts +335 -0
  802. package/src/workflow/persistence.ts +312 -0
  803. package/src/workflow/resolve.ts +45 -0
  804. package/src/workflow/runtime-ref.ts +18 -0
  805. package/src/workflow/runtime.ts +1234 -0
  806. package/src/workflow/sandbox.ts +286 -0
  807. package/src/workflow/workflow.sql.ts +31 -0
  808. package/src/workflow/workspace.ts +69 -0
  809. package/src/worktree/index.ts +614 -0
  810. package/sst-env.d.ts +10 -0
  811. package/test/AGENTS.md +133 -0
  812. package/test/account/repo.test.ts +352 -0
  813. package/test/account/service.test.ts +456 -0
  814. package/test/acp/agent-interface.test.ts +51 -0
  815. package/test/acp/event-subscription.test.ts +725 -0
  816. package/test/actor/cancel-cascade.test.ts +432 -0
  817. package/test/actor/no-completion-listener.test.ts +41 -0
  818. package/test/actor/poststop-progress-write-permission.repro.test.ts +414 -0
  819. package/test/actor/registry-render.test.ts +113 -0
  820. package/test/actor/registry-status.test.ts +111 -0
  821. package/test/actor/registry.test.ts +619 -0
  822. package/test/actor/return-header.test.ts +40 -0
  823. package/test/actor/spawn-lifecycle.test.ts +346 -0
  824. package/test/actor/spawn-no-deadlock.test.ts +340 -0
  825. package/test/actor/spawn-notification.test.ts +393 -0
  826. package/test/actor/spawn-task-autostart.test.ts +530 -0
  827. package/test/actor/spawn.test.ts +1072 -0
  828. package/test/actor/status-event-payload.test.ts +132 -0
  829. package/test/actor/terminology.test.ts +39 -0
  830. package/test/actor/turn.test.ts +125 -0
  831. package/test/actor/waiter.test.ts +246 -0
  832. package/test/agent/agent.test.ts +861 -0
  833. package/test/agent/allowlist.test.ts +45 -0
  834. package/test/auth/auth.test.ts +86 -0
  835. package/test/bus/bus-effect.test.ts +162 -0
  836. package/test/bus/bus-integration.test.ts +87 -0
  837. package/test/bus/bus.test.ts +219 -0
  838. package/test/cli/account.test.ts +26 -0
  839. package/test/cli/cmd/tui/prompt-part.test.ts +47 -0
  840. package/test/cli/error.test.ts +18 -0
  841. package/test/cli/github-action.test.ts +198 -0
  842. package/test/cli/github-remote.test.ts +80 -0
  843. package/test/cli/import.test.ts +54 -0
  844. package/test/cli/plugin-auth-picker.test.ts +120 -0
  845. package/test/cli/run-completion.test.ts +131 -0
  846. package/test/cli/tui/keybind-plugin.test.ts +90 -0
  847. package/test/cli/tui/plugin-add.test.ts +111 -0
  848. package/test/cli/tui/plugin-install.test.ts +87 -0
  849. package/test/cli/tui/plugin-lifecycle.test.ts +224 -0
  850. package/test/cli/tui/plugin-loader-entrypoint.test.ts +484 -0
  851. package/test/cli/tui/plugin-loader-pure.test.ts +71 -0
  852. package/test/cli/tui/plugin-loader.test.ts +816 -0
  853. package/test/cli/tui/plugin-toggle.test.ts +157 -0
  854. package/test/cli/tui/revert-diff.test.ts +35 -0
  855. package/test/cli/tui/route-agent-id.test.ts +26 -0
  856. package/test/cli/tui/sidebar-tps.test.ts +63 -0
  857. package/test/cli/tui/slot-replace.test.tsx +47 -0
  858. package/test/cli/tui/sync-bucket.test.ts +29 -0
  859. package/test/cli/tui/theme-store.test.ts +51 -0
  860. package/test/cli/tui/thread.test.ts +122 -0
  861. package/test/cli/tui/transcript.test.ts +426 -0
  862. package/test/cli/tui/use-event.test.tsx +175 -0
  863. package/test/cli/tui/voice.test.ts +443 -0
  864. package/test/command/deep-research-command.test.ts +16 -0
  865. package/test/config/agent-color.test.ts +77 -0
  866. package/test/config/checkpoint-fork.test.ts +21 -0
  867. package/test/config/config.test.ts +2577 -0
  868. package/test/config/fixtures/empty-frontmatter.md +4 -0
  869. package/test/config/fixtures/frontmatter.md +28 -0
  870. package/test/config/fixtures/markdown-header.md +11 -0
  871. package/test/config/fixtures/no-frontmatter.md +1 -0
  872. package/test/config/fixtures/weird-model-id.md +13 -0
  873. package/test/config/lsp.test.ts +87 -0
  874. package/test/config/markdown.test.ts +228 -0
  875. package/test/config/plugin.test.ts +0 -0
  876. package/test/config/tui.test.ts +627 -0
  877. package/test/control-plane/adaptors.test.ts +71 -0
  878. package/test/control-plane/sse.test.ts +56 -0
  879. package/test/effect/app-runtime-logger.test.ts +92 -0
  880. package/test/effect/cross-spawn-spawner.test.ts +411 -0
  881. package/test/effect/instance-state.test.ts +482 -0
  882. package/test/effect/observability.test.ts +46 -0
  883. package/test/effect/run-service.test.ts +46 -0
  884. package/test/effect/runner-warn-log.test.ts +111 -0
  885. package/test/effect/runner.test.ts +494 -0
  886. package/test/fake/provider.ts +90 -0
  887. package/test/file/fsmonitor.test.ts +68 -0
  888. package/test/file/ignore.test.ts +10 -0
  889. package/test/file/index.test.ts +956 -0
  890. package/test/file/path-traversal.test.ts +204 -0
  891. package/test/file/ripgrep.test.ts +214 -0
  892. package/test/file/watcher.test.ts +249 -0
  893. package/test/filesystem/filesystem.test.ts +319 -0
  894. package/test/fixture/db.ts +11 -0
  895. package/test/fixture/fixture.test.ts +58 -0
  896. package/test/fixture/fixture.ts +190 -0
  897. package/test/fixture/flock-worker.ts +72 -0
  898. package/test/fixture/lsp/fake-lsp-server.js +75 -0
  899. package/test/fixture/plug-worker.ts +93 -0
  900. package/test/fixture/plugin-meta-worker.ts +19 -0
  901. package/test/fixture/skills/agents-sdk/SKILL.md +152 -0
  902. package/test/fixture/skills/agents-sdk/references/callable.md +92 -0
  903. package/test/fixture/skills/cloudflare/SKILL.md +211 -0
  904. package/test/fixture/skills/index.json +6 -0
  905. package/test/fixture/tui-plugin.ts +328 -0
  906. package/test/fixture/tui-runtime.ts +31 -0
  907. package/test/format/format.test.ts +244 -0
  908. package/test/git/git.test.ts +128 -0
  909. package/test/global/fixture/global-paths-worker.ts +17 -0
  910. package/test/global/glitchcode-home.test.ts +143 -0
  911. package/test/global/mimocode-home.test.ts +143 -0
  912. package/test/history/backfill.test.ts +149 -0
  913. package/test/history/extract.test.ts +106 -0
  914. package/test/history/fts-query.test.ts +30 -0
  915. package/test/history/resolve.test.ts +130 -0
  916. package/test/history/service.test.ts +210 -0
  917. package/test/history/writer.test.ts +163 -0
  918. package/test/ide/ide.test.ts +82 -0
  919. package/test/inbox/drain-in-loop.test.ts +230 -0
  920. package/test/inbox/fork-agent-compat.test.ts +387 -0
  921. package/test/inbox/gc-on-init.test.ts +167 -0
  922. package/test/inbox/send-no-block.test.ts +120 -0
  923. package/test/inbox/sender-cancel-independence.test.ts +160 -0
  924. package/test/inbox/wake-matrix.test.ts +141 -0
  925. package/test/installation/installation.test.ts +255 -0
  926. package/test/keybind.test.ts +421 -0
  927. package/test/lib/effect.ts +53 -0
  928. package/test/lib/filesystem.ts +10 -0
  929. package/test/lib/llm-server.ts +770 -0
  930. package/test/lib/mock-llm.ts +159 -0
  931. package/test/lib/scripted-llm-server.ts +245 -0
  932. package/test/lsp/client.test.ts +98 -0
  933. package/test/lsp/index.test.ts +109 -0
  934. package/test/lsp/launch.test.ts +22 -0
  935. package/test/lsp/lifecycle.test.ts +184 -0
  936. package/test/mcp/headers.test.ts +178 -0
  937. package/test/mcp/lifecycle.test.ts +824 -0
  938. package/test/mcp/oauth-auto-connect.test.ts +281 -0
  939. package/test/mcp/oauth-browser.test.ts +268 -0
  940. package/test/mcp/oauth-callback.test.ts +34 -0
  941. package/test/memory/abort-leak-webfetch.ts +49 -0
  942. package/test/memory/abort-leak.test.ts +127 -0
  943. package/test/memory/cc-frontmatter.test.ts +85 -0
  944. package/test/memory/cc-paths.test.ts +60 -0
  945. package/test/memory/cc-reconcile.test.ts +239 -0
  946. package/test/memory/cc-search.test.ts +151 -0
  947. package/test/memory/fts-query.test.ts +48 -0
  948. package/test/memory/fts-rowid-stability.test.ts +271 -0
  949. package/test/memory/paths.test.ts +210 -0
  950. package/test/memory/reconcile.test.ts +115 -0
  951. package/test/memory/service.test.ts +169 -0
  952. package/test/npm.test.ts +18 -0
  953. package/test/patch/patch.test.ts +348 -0
  954. package/test/permission/abort.test.ts +116 -0
  955. package/test/permission/arity.test.ts +33 -0
  956. package/test/permission/disabled.test.ts +51 -0
  957. package/test/permission/next.test.ts +1080 -0
  958. package/test/permission/non-interactive.test.ts +55 -0
  959. package/test/permission-task.test.ts +326 -0
  960. package/test/plugin/actor-hooks.test.ts +1471 -0
  961. package/test/plugin/auth-override.test.ts +79 -0
  962. package/test/plugin/checkpoint-splitover.test.ts +434 -0
  963. package/test/plugin/cloudflare.test.ts +68 -0
  964. package/test/plugin/codex.test.ts +123 -0
  965. package/test/plugin/github-copilot-models.test.ts +163 -0
  966. package/test/plugin/glitch.test.ts +257 -0
  967. package/test/plugin/install-concurrency.test.ts +140 -0
  968. package/test/plugin/install.test.ts +570 -0
  969. package/test/plugin/loader-shared.test.ts +1169 -0
  970. package/test/plugin/matcher.test.ts +97 -0
  971. package/test/plugin/meta.test.ts +137 -0
  972. package/test/plugin/mimo.test.ts +257 -0
  973. package/test/plugin/shared.test.ts +88 -0
  974. package/test/plugin/subagent-progress-checker.test.ts +227 -0
  975. package/test/plugin/trigger.test.ts +116 -0
  976. package/test/plugin/workspace-adaptor.test.ts +109 -0
  977. package/test/preload.ts +102 -0
  978. package/test/project/migrate-global.test.ts +150 -0
  979. package/test/project/project-id.test.ts +64 -0
  980. package/test/project/project.test.ts +502 -0
  981. package/test/project/vcs.test.ts +286 -0
  982. package/test/project/worktree-remove.test.ts +126 -0
  983. package/test/project/worktree.test.ts +214 -0
  984. package/test/provider/amazon-bedrock.test.ts +462 -0
  985. package/test/provider/copilot/convert-to-copilot-messages.test.ts +523 -0
  986. package/test/provider/copilot/copilot-chat-model.test.ts +592 -0
  987. package/test/provider/error.test.ts +160 -0
  988. package/test/provider/gitlab-duo.test.ts +413 -0
  989. package/test/provider/model-groups.test.ts +389 -0
  990. package/test/provider/provider-chunk-timeout.test.ts +23 -0
  991. package/test/provider/provider.test.ts +2651 -0
  992. package/test/provider/transform.test.ts +3379 -0
  993. package/test/pty/pty-output-isolation.test.ts +146 -0
  994. package/test/pty/pty-session.test.ts +102 -0
  995. package/test/pty/pty-shell.test.ts +69 -0
  996. package/test/question/question.test.ts +464 -0
  997. package/test/server/global-session-list.test.ts +105 -0
  998. package/test/server/project-init-git.test.ts +122 -0
  999. package/test/server/session-actions.test.ts +49 -0
  1000. package/test/server/session-list.test.ts +110 -0
  1001. package/test/server/session-messages.test.ts +220 -0
  1002. package/test/server/session-prompt-busy.test.ts +146 -0
  1003. package/test/server/session-select.test.ts +100 -0
  1004. package/test/server/session-task-route.test.ts +165 -0
  1005. package/test/server/summarize-route-main-slice.test.ts +99 -0
  1006. package/test/server/trace-attributes.test.ts +76 -0
  1007. package/test/server/workflows-route.test.ts +279 -0
  1008. package/test/session/bootstrap-skip-system.test.ts +121 -0
  1009. package/test/session/boundary.test.ts +33 -0
  1010. package/test/session/budgeted-read.test.ts +74 -0
  1011. package/test/session/checkpoint-align.test.ts +58 -0
  1012. package/test/session/checkpoint-boundary.test.ts +186 -0
  1013. package/test/session/checkpoint-child-session.test.ts +508 -0
  1014. package/test/session/checkpoint-context.test.ts +141 -0
  1015. package/test/session/checkpoint-drain.test.ts +188 -0
  1016. package/test/session/checkpoint-extract-titles.test.ts +58 -0
  1017. package/test/session/checkpoint-fork-mode.test.ts +576 -0
  1018. package/test/session/checkpoint-main-slice.test.ts +259 -0
  1019. package/test/session/checkpoint-paths.test.ts +78 -0
  1020. package/test/session/checkpoint-permission.test.ts +136 -0
  1021. package/test/session/checkpoint-progress-reconcile.test.ts +219 -0
  1022. package/test/session/checkpoint-rebuild-unify.test.ts +143 -0
  1023. package/test/session/checkpoint-rebuild-v3.test.ts +248 -0
  1024. package/test/session/checkpoint-render-verify.test.ts +512 -0
  1025. package/test/session/checkpoint-retry.test.ts +150 -0
  1026. package/test/session/checkpoint-splitover-integration.test.ts +533 -0
  1027. package/test/session/checkpoint-templates.test.ts +51 -0
  1028. package/test/session/checkpoint-thresholds.test.ts +120 -0
  1029. package/test/session/checkpoint-validator.test.ts +189 -0
  1030. package/test/session/classify-integration.test.ts +476 -0
  1031. package/test/session/classify.test.ts +335 -0
  1032. package/test/session/codex-import.test.ts +331 -0
  1033. package/test/session/compaction-agent-scope.test.ts +209 -0
  1034. package/test/session/context-inheritance.test.ts +46 -0
  1035. package/test/session/external-import.test.ts +17 -0
  1036. package/test/session/fork-prefix-invariant.test.ts +116 -0
  1037. package/test/session/goal.test.ts +106 -0
  1038. package/test/session/instruction.test.ts +387 -0
  1039. package/test/session/invalid-output-continuation.test.ts +150 -0
  1040. package/test/session/last-message-info.test.ts +47 -0
  1041. package/test/session/length-tool-safety.test.ts +121 -0
  1042. package/test/session/llm-request-prefix.test.ts +197 -0
  1043. package/test/session/llm-retry.test.ts +59 -0
  1044. package/test/session/llm-system-prompt.test.ts +479 -0
  1045. package/test/session/llm.test.ts +1266 -0
  1046. package/test/session/main-lifecycle.test.ts +51 -0
  1047. package/test/session/main-runloop-history-invariant.test.ts +182 -0
  1048. package/test/session/max-mode-econnreset.test.ts +229 -0
  1049. package/test/session/max-mode.test.ts +54 -0
  1050. package/test/session/message-v2-filter.test.ts +197 -0
  1051. package/test/session/message-v2.test.ts +1119 -0
  1052. package/test/session/messages-default-main.test.ts +105 -0
  1053. package/test/session/messages-pagination.test.ts +888 -0
  1054. package/test/session/overflow.test.ts +576 -0
  1055. package/test/session/processor-effect.test.ts +853 -0
  1056. package/test/session/prompt-effect.test.ts +1614 -0
  1057. package/test/session/prompt-rebuild-loop.test.ts +108 -0
  1058. package/test/session/prompt-rebuild-reset.test.ts +67 -0
  1059. package/test/session/prompt-sweep.test.ts +145 -0
  1060. package/test/session/prompt-task-gate.test.ts +127 -0
  1061. package/test/session/prompt.test.ts +703 -0
  1062. package/test/session/prune-main-slice.test.ts +272 -0
  1063. package/test/session/prune-skip-system.test.ts +346 -0
  1064. package/test/session/prune.test.ts +419 -0
  1065. package/test/session/rebuild-microcompact.test.ts +318 -0
  1066. package/test/session/recall-reminder.test.ts +37 -0
  1067. package/test/session/retry.test.ts +410 -0
  1068. package/test/session/revert-compact.test.ts +639 -0
  1069. package/test/session/run-state-tuple-key.test.ts +152 -0
  1070. package/test/session/session-create-registers-main.test.ts +70 -0
  1071. package/test/session/session.test.ts +181 -0
  1072. package/test/session/snapshot-tool-race.test.ts +301 -0
  1073. package/test/session/structured-output-integration.test.ts +264 -0
  1074. package/test/session/structured-output-retry.test.ts +127 -0
  1075. package/test/session/structured-output.test.ts +397 -0
  1076. package/test/session/summary-main-slice.test.ts +170 -0
  1077. package/test/session/system.test.ts +72 -0
  1078. package/test/session/text-loop-detection.test.ts +185 -0
  1079. package/test/session/text-loop-integration.test.ts +448 -0
  1080. package/test/share/share-next.test.ts +332 -0
  1081. package/test/shell/shell.test.ts +73 -0
  1082. package/test/skill/compose-review.test.ts +141 -0
  1083. package/test/skill/discovery.test.ts +116 -0
  1084. package/test/skill/skill.test.ts +465 -0
  1085. package/test/snapshot/snapshot.test.ts +1531 -0
  1086. package/test/storage/db.test.ts +16 -0
  1087. package/test/storage/json-migration.test.ts +831 -0
  1088. package/test/storage/storage.test.ts +293 -0
  1089. package/test/sync/index.test.ts +237 -0
  1090. package/test/task/gate-state.test.ts +66 -0
  1091. package/test/task/gate.test.ts +167 -0
  1092. package/test/task/registry.test.ts +171 -0
  1093. package/test/task/state-machine.test.ts +292 -0
  1094. package/test/team/migrate-to-inbox.test.ts +124 -0
  1095. package/test/team/team.test.ts +75 -0
  1096. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  1097. package/test/tool/actor-cancel.test.ts +206 -0
  1098. package/test/tool/actor-recover.test.ts +50 -0
  1099. package/test/tool/actor-send.test.ts +200 -0
  1100. package/test/tool/actor-status.test.ts +296 -0
  1101. package/test/tool/actor-wait.test.ts +193 -0
  1102. package/test/tool/actor.shell.test.ts +250 -0
  1103. package/test/tool/actor.test.ts +748 -0
  1104. package/test/tool/apply_patch.test.ts +626 -0
  1105. package/test/tool/bash.test.ts +1195 -0
  1106. package/test/tool/describe-workflow.test.ts +12 -0
  1107. package/test/tool/edit.test.ts +691 -0
  1108. package/test/tool/external-directory.test.ts +207 -0
  1109. package/test/tool/fixtures/large-image.png +0 -0
  1110. package/test/tool/fixtures/models-api.json +65179 -0
  1111. package/test/tool/glob.test.ts +81 -0
  1112. package/test/tool/grep.test.ts +114 -0
  1113. package/test/tool/history.test.ts +144 -0
  1114. package/test/tool/invocation-style.test.ts +30 -0
  1115. package/test/tool/memory-edit-ask-skip.test.ts +62 -0
  1116. package/test/tool/memory-path-guard.test.ts +594 -0
  1117. package/test/tool/memory.test.ts +71 -0
  1118. package/test/tool/question.test.ts +167 -0
  1119. package/test/tool/read.test.ts +483 -0
  1120. package/test/tool/recoverable.test.ts +36 -0
  1121. package/test/tool/registry-invocation-style.test.ts +121 -0
  1122. package/test/tool/registry.test.ts +164 -0
  1123. package/test/tool/shell-tokenize.test.ts +273 -0
  1124. package/test/tool/shell-wrap-missing-script.test.ts +128 -0
  1125. package/test/tool/shell-wrap.test.ts +257 -0
  1126. package/test/tool/skill.test.ts +99 -0
  1127. package/test/tool/task-recover.test.ts +36 -0
  1128. package/test/tool/task.shell.test.ts +234 -0
  1129. package/test/tool/task.test.ts +296 -0
  1130. package/test/tool/tool-def-shell-shape.test.ts +23 -0
  1131. package/test/tool/tool-define.test.ts +59 -0
  1132. package/test/tool/tool-validation-error.test.ts +25 -0
  1133. package/test/tool/truncation.test.ts +253 -0
  1134. package/test/tool/webfetch.test.ts +103 -0
  1135. package/test/tool/whitelist.test.ts +373 -0
  1136. package/test/tool/write.test.ts +244 -0
  1137. package/test/util/data-url.test.ts +14 -0
  1138. package/test/util/effect-zod.test.ts +869 -0
  1139. package/test/util/error.test.ts +38 -0
  1140. package/test/util/filesystem.test.ts +656 -0
  1141. package/test/util/format.test.ts +59 -0
  1142. package/test/util/glob.test.ts +164 -0
  1143. package/test/util/iife.test.ts +36 -0
  1144. package/test/util/lazy.test.ts +50 -0
  1145. package/test/util/lock.test.ts +72 -0
  1146. package/test/util/log.test.ts +69 -0
  1147. package/test/util/module.test.ts +59 -0
  1148. package/test/util/process.test.ts +128 -0
  1149. package/test/util/queue.test.ts +64 -0
  1150. package/test/util/timeout.test.ts +21 -0
  1151. package/test/util/which.test.ts +100 -0
  1152. package/test/util/wildcard.test.ts +90 -0
  1153. package/test/workflow/builtin.test.ts +22 -0
  1154. package/test/workflow/deep-research-cluster.test.ts +47 -0
  1155. package/test/workflow/lib.ts +243 -0
  1156. package/test/workflow/meta.test.ts +142 -0
  1157. package/test/workflow/model-routing.test.ts +68 -0
  1158. package/test/workflow/persistence.test.ts +229 -0
  1159. package/test/workflow/resolve.test.ts +37 -0
  1160. package/test/workflow/runtime-nested.test.ts +419 -0
  1161. package/test/workflow/runtime-worktree.test.ts +261 -0
  1162. package/test/workflow/runtime.test.ts +1078 -0
  1163. package/test/workflow/sandbox.test.ts +259 -0
  1164. package/test/workflow/tool.test.ts +473 -0
  1165. package/test/workflow/verify-wow.test.ts +144 -0
  1166. package/test/workflow/workspace.test.ts +88 -0
  1167. package/test/workspace/workspace-restore.test.ts +281 -0
  1168. package/test/worktree/index.test.ts +30 -0
  1169. package/tsconfig.json +24 -0
@@ -0,0 +1,1871 @@
1
+ import { BoxRenderable, RGBA, TextareaRenderable, MouseEvent, PasteEvent, decodePasteBytes } from "@opentui/core"
2
+ import { createEffect, createMemo, onMount, createSignal, onCleanup, on, Show, Switch, Match } from "solid-js"
3
+ import "opentui-spinner/solid"
4
+ import path from "path"
5
+ import { fileURLToPath } from "url"
6
+ import { Filesystem } from "@/util"
7
+ import { useLocal } from "@tui/context/local"
8
+ import { tint, useTheme } from "@tui/context/theme"
9
+ import { EmptyBorder, SplitBorder } from "@tui/component/border"
10
+ import { useSDK } from "@tui/context/sdk"
11
+ import { useRoute } from "@tui/context/route"
12
+ import { useProject } from "@tui/context/project"
13
+ import { useSync } from "@tui/context/sync"
14
+ import { useEvent } from "@tui/context/event"
15
+ import { MessageID, PartID } from "@/session/schema"
16
+ import { createStore, produce, unwrap } from "solid-js/store"
17
+ import { useKeybind } from "@tui/context/keybind"
18
+ import { usePromptHistory, type PromptInfo } from "./history"
19
+ import { assign } from "./part"
20
+ import { usePromptStash } from "./stash"
21
+ import { DialogStash } from "../dialog-stash"
22
+ import { type AutocompleteRef, Autocomplete } from "./autocomplete"
23
+ import { useCommandDialog } from "../dialog-command"
24
+ import { useLanguage } from "@tui/context/language"
25
+ import { useRenderer, type JSX } from "@opentui/solid"
26
+ import * as Editor from "@tui/util/editor"
27
+ import * as Voice from "@tui/util/voice"
28
+ import { useExit } from "../../context/exit"
29
+ import * as Clipboard from "../../util/clipboard"
30
+ import type { AssistantMessage, FilePart, UserMessage } from "@mimo-ai/sdk/v2"
31
+ import { TuiEvent } from "../../event"
32
+ import { iife } from "@/util/iife"
33
+ import { Locale } from "@/util"
34
+ import { formatDuration } from "@/util/format"
35
+ import { createColors, createFrames } from "../../ui/spinner.ts"
36
+ import { useDialog } from "@tui/ui/dialog"
37
+ import { DialogProvider as DialogProviderConnect } from "../dialog-provider"
38
+ import { DialogAlert } from "../../ui/dialog-alert"
39
+ import { useToast } from "../../ui/toast"
40
+ import { useKV } from "../../context/kv"
41
+ import { createFadeIn } from "../../util/signal"
42
+ import { useTextareaKeybindings } from "../textarea-keybindings"
43
+ import { DialogSkill } from "../dialog-skill"
44
+ import { DialogWorkspaceCreate, restoreWorkspaceSession } from "../dialog-workspace-create"
45
+ import { DialogWorkspaceUnavailable } from "../dialog-workspace-unavailable"
46
+ import { DialogAgreement, FREE_AGREEMENT_KEY, FREE_MODEL_IDS } from "../dialog-agreement"
47
+ import { useArgs } from "@tui/context/args"
48
+
49
+ export type PromptProps = {
50
+ sessionID?: string
51
+ workspaceID?: string
52
+ visible?: boolean
53
+ disabled?: boolean
54
+ onSubmit?: () => void
55
+ ref?: (ref: PromptRef | undefined) => void
56
+ hint?: JSX.Element
57
+ right?: JSX.Element
58
+ showPlaceholder?: boolean
59
+ placeholders?: {
60
+ normal?: string[]
61
+ shell?: string[]
62
+ }
63
+ }
64
+
65
+ export type PromptRef = {
66
+ focused: boolean
67
+ current: PromptInfo
68
+ set(prompt: PromptInfo): void
69
+ reset(): void
70
+ blur(): void
71
+ focus(): void
72
+ submit(): void
73
+ paste(): void
74
+ }
75
+
76
+ const money = new Intl.NumberFormat("en-US", {
77
+ style: "currency",
78
+ currency: "USD",
79
+ })
80
+
81
+ function randomIndex(count: number) {
82
+ if (count <= 0) return 0
83
+ return Math.floor(Math.random() * count)
84
+ }
85
+
86
+ function fadeColor(color: RGBA, alpha: number) {
87
+ return RGBA.fromValues(color.r, color.g, color.b, color.a * alpha)
88
+ }
89
+
90
+ let stashed: { prompt: PromptInfo; cursor: number } | undefined
91
+
92
+ // Module-level voice state: survives component remounts and route changes
93
+ let activeVoice: {
94
+ handle: Voice.StreamingHandle
95
+ pending: number
96
+ appendText: (text: string) => void
97
+ setText: (text: string) => void
98
+ getPlainText: () => string
99
+ switchAgent: (name: string) => void
100
+ submit: () => Promise<unknown>
101
+ setState: (type: "listening" | "speaking" | "processing" | "finishing" | "idle") => void
102
+ showError: (msg: string) => void
103
+ } | undefined
104
+
105
+ export function Prompt(props: PromptProps) {
106
+ let input: TextareaRenderable
107
+ let anchor: BoxRenderable
108
+ let autocomplete: AutocompleteRef
109
+
110
+ const keybind = useKeybind()
111
+ const local = useLocal()
112
+ const args = useArgs()
113
+ const sdk = useSDK()
114
+ const route = useRoute()
115
+ const project = useProject()
116
+ const sync = useSync()
117
+ const dialog = useDialog()
118
+ const toast = useToast()
119
+ const status = createMemo(() => sync.data.session_status?.[props.sessionID ?? ""] ?? { type: "idle" })
120
+ const history = usePromptHistory()
121
+ const stash = usePromptStash()
122
+ const command = useCommandDialog()
123
+ const t = useLanguage().t
124
+ const renderer = useRenderer()
125
+ const { theme, syntax } = useTheme()
126
+ const kv = useKV()
127
+ const animationsEnabled = createMemo(() => kv.get("animations_enabled", true))
128
+ const voiceEnabled = createMemo(() => kv.get("voice_enabled", false))
129
+ const voiceSendEnabled = createMemo(() => kv.get("voice_send_command", false))
130
+ const voiceControlEnabled = createMemo(() => kv.get("voice_control_enabled", false))
131
+ const [voiceState, setVoiceState] = createSignal<"idle" | "listening" | "speaking" | "processing" | "finishing">(
132
+ activeVoice ? (activeVoice.pending > 0 ? "processing" : "listening") : "idle",
133
+ )
134
+ const [voiceElapsed, setVoiceElapsed] = createSignal(0)
135
+
136
+ let voiceTimer: ReturnType<typeof setInterval> | undefined
137
+ let voiceSegmentStart = 0
138
+
139
+ function voiceTimerStart() {
140
+ voiceTimerStop()
141
+ voiceSegmentStart = Date.now()
142
+ voiceTimer = setInterval(() => {
143
+ setVoiceElapsed(Math.floor((Date.now() - voiceSegmentStart) / 1000))
144
+ }, 200)
145
+ }
146
+ function voiceTimerStop() {
147
+ if (voiceTimer) {
148
+ clearInterval(voiceTimer)
149
+ voiceTimer = undefined
150
+ }
151
+ setVoiceElapsed(0)
152
+ }
153
+
154
+ function voiceAppendText(text: string) {
155
+ if (!input || input.isDestroyed) return
156
+ const current = store.prompt.input
157
+ if (current.length > 0 && /[.?!]$/.test(current) && text.length > 0 && text[0] !== " ") {
158
+ input.insertText(" " + text)
159
+ setStore("prompt", "input", current + " " + text)
160
+ } else {
161
+ input.insertText(text)
162
+ setStore("prompt", "input", current + text)
163
+ }
164
+ setTimeout(() => {
165
+ if (!input || input.isDestroyed) return
166
+ input.getLayoutNode().markDirty()
167
+ input.gotoBufferEnd()
168
+ renderer.requestRender()
169
+ }, 0)
170
+ }
171
+
172
+ function voiceSetText(text: string) {
173
+ if (!input || input.isDestroyed) return
174
+ input.clear()
175
+ input.insertText(text)
176
+ setStore("prompt", "input", text)
177
+ setTimeout(() => {
178
+ if (!input || input.isDestroyed) return
179
+ input.getLayoutNode().markDirty()
180
+ input.gotoBufferEnd()
181
+ renderer.requestRender()
182
+ }, 0)
183
+ }
184
+
185
+ function voiceGetPlainText() {
186
+ return store.prompt.input
187
+ }
188
+
189
+ function voiceSwitchAgent(name: string) {
190
+ const match = local.agent.list().find((x) => x.name.toLowerCase() === name.toLowerCase())
191
+ if (match) local.agent.set(match.name)
192
+ else toast.show({ message: t("tui.voice.error.unknown_agent", { name: name }), variant: "error", duration: 3000 })
193
+ }
194
+
195
+ function voiceSetState(type: "idle" | "listening" | "speaking" | "processing" | "finishing") {
196
+ setVoiceState(type)
197
+ if (type === "speaking") voiceTimerStart()
198
+ if (type === "idle" || type === "listening" || type === "processing") voiceTimerStop()
199
+ }
200
+
201
+ // Wire module-level callbacks to current component instance
202
+ if (activeVoice) {
203
+ activeVoice.appendText = voiceAppendText
204
+ activeVoice.setText = voiceSetText
205
+ activeVoice.getPlainText = voiceGetPlainText
206
+ activeVoice.switchAgent = voiceSwitchAgent
207
+ activeVoice.submit = () => submit()
208
+ activeVoice.setState = voiceSetState
209
+ activeVoice.showError = (msg) => toast.show({ message: msg, variant: "error", duration: 3000 })
210
+ }
211
+ onCleanup(() => {
212
+ voiceTimerStop()
213
+ })
214
+
215
+ async function voiceToggle() {
216
+ const state = voiceState()
217
+ if (state === "listening" || state === "speaking" || state === "processing") {
218
+ voiceTimerStop()
219
+ setVoiceState("finishing")
220
+ if (activeVoice) {
221
+ const handle = activeVoice.handle
222
+ const av = activeVoice
223
+ activeVoice = undefined
224
+ await Voice.stopStreaming(handle)
225
+ if (av.pending <= 0) setVoiceState("idle")
226
+ }
227
+ return
228
+ }
229
+ if (state === "finishing") return
230
+ // Start streaming — only validate the active mode's provider
231
+ const voiceConfig = sync.data.config.voice
232
+ const resolved = Voice.resolveVoiceConfig(voiceConfig)
233
+ const activeConfig = voiceControlEnabled() ? resolved.control : resolved.asr
234
+ const creds = Voice.resolveCredentials(sync.data.provider, activeConfig)
235
+ if ("error" in creds) {
236
+ const vars = { provider: creds.providerID, model: creds.model }
237
+ const msg = !voiceConfig ? t("tui.voice.error.no_auth")
238
+ : creds.error === "not_found" ? t("tui.voice.error.provider_not_found", vars)
239
+ : creds.error === "no_url" ? t("tui.voice.error.no_url", vars)
240
+ : t("tui.voice.error.no_auth_provider", vars)
241
+ toast.show({ message: msg, variant: "error" })
242
+ return
243
+ }
244
+ if (!Voice.isAvailable()) {
245
+ toast.show({ message: t("tui.voice.error.no_recorder"), variant: "error" })
246
+ return
247
+ }
248
+
249
+ const av: NonNullable<typeof activeVoice> = {
250
+ handle: undefined!,
251
+ pending: 0,
252
+ appendText: voiceAppendText,
253
+ setText: voiceSetText,
254
+ getPlainText: voiceGetPlainText,
255
+ switchAgent: voiceSwitchAgent,
256
+ submit: () => submit(),
257
+ setState: voiceSetState,
258
+ showError: (msg) => toast.show({ message: msg, variant: "error", duration: 3000 }),
259
+ }
260
+
261
+ let voiceControlChain: Promise<void> = Promise.resolve()
262
+
263
+ const handle = Voice.startStreaming({
264
+ onSegment: (segment) => {
265
+ av.pending++
266
+ av.setState("processing")
267
+
268
+ if (voiceControlEnabled()) {
269
+ voiceControlChain = voiceControlChain.then(async () => {
270
+ try {
271
+ if (!activeVoice) return
272
+ av.setState("processing")
273
+ const currentText = av.getPlainText()
274
+ const currentAgent = local.agent.current()?.name ?? ""
275
+ const availableAgents = local.agent.list().map((x) => x.name)
276
+
277
+ const ctrl = await Voice.processVoiceControl({
278
+ audio: segment.audio,
279
+ apiKey: creds.apiKey,
280
+ baseUrl: creds.baseUrl,
281
+ model: resolved.control.model,
282
+ currentText,
283
+ currentAgent,
284
+ availableAgents,
285
+ sendEnabled: voiceSendEnabled(),
286
+ })
287
+
288
+ if (ctrl) {
289
+ for (const action of ctrl.actions) {
290
+ if (action.action === "edit") av.setText(action.text)
291
+ else if (action.action === "send") {
292
+ if (voiceSendEnabled() && av.getPlainText().trim()) await av.submit()
293
+ else if (!av.getPlainText().trim()) av.showError(t("tui.voice.error.empty_send"))
294
+ } else if (action.action === "agent") {
295
+ av.switchAgent(action.agent)
296
+ }
297
+ }
298
+ } else {
299
+ av.showError(t("tui.voice.error.network"))
300
+ }
301
+ } finally {
302
+ av.pending--
303
+ if (activeVoice === av && voiceState() !== "speaking")
304
+ av.setState(av.pending > 0 ? "processing" : "listening")
305
+ if (!activeVoice && av.pending <= 0) av.setState("idle")
306
+ }
307
+ }).catch(() => {})
308
+ } else {
309
+ Voice.transcribeAudio({
310
+ audio: segment.audio,
311
+ apiKey: creds.apiKey,
312
+ baseUrl: creds.baseUrl,
313
+ model: resolved.asr.model,
314
+ }).then((text) => {
315
+ if (text) {
316
+ if (voiceSendEnabled() && Voice.SEND_RE.test(text.replace(/[\s。.!!??,,]+$/g, "").trim())) {
317
+ av.submit()
318
+ } else {
319
+ av.appendText(text.trim())
320
+ }
321
+ } else {
322
+ av.showError(t("tui.voice.error.network"))
323
+ }
324
+ av.pending--
325
+ if (activeVoice === av && voiceState() !== "speaking")
326
+ av.setState(av.pending > 0 ? "processing" : "listening")
327
+ if (!activeVoice && av.pending <= 0) av.setState("idle")
328
+ }).catch(() => {
329
+ av.pending--
330
+ if (activeVoice === av && voiceState() !== "speaking")
331
+ av.setState(av.pending > 0 ? "processing" : "listening")
332
+ if (!activeVoice && av.pending <= 0) av.setState("idle")
333
+ })
334
+ }
335
+ },
336
+ onActiveChange: (active) => {
337
+ if (active && activeVoice === av) av.setState("speaking")
338
+ },
339
+ onError: (err) => {
340
+ const msg = err.message || ""
341
+ if (msg.includes("no default audio") || msg.includes("not found") || msg.includes("Cannot open") || msg.includes("ALSA")) {
342
+ av.showError(t("tui.voice.error.no_device"))
343
+ } else {
344
+ av.showError(`${t("tui.voice.error.recorder_failed")}: ${msg}`)
345
+ }
346
+ activeVoice = undefined
347
+ av.setState("idle")
348
+ },
349
+ })
350
+ if (!handle) {
351
+ toast.show({ message: t("tui.voice.error.no_recorder"), variant: "error" })
352
+ return
353
+ }
354
+ av.handle = handle
355
+ activeVoice = av
356
+ setVoiceState("listening")
357
+ }
358
+
359
+ const list = createMemo(() => props.placeholders?.normal ?? [])
360
+ const shell = createMemo(() => props.placeholders?.shell ?? [])
361
+ const [auto, setAuto] = createSignal<AutocompleteRef>()
362
+ const [ghost, setGhost] = createSignal("")
363
+ const hasRightContent = createMemo(() => Boolean(props.right))
364
+
365
+ function promptModelWarning() {
366
+ toast.show({
367
+ variant: "warning",
368
+ message: "Connect a provider to send prompts",
369
+ duration: 3000,
370
+ })
371
+ if (sync.data.provider.length === 0) {
372
+ dialog.replace(() => <DialogProviderConnect />)
373
+ }
374
+ }
375
+
376
+ const textareaKeybindings = useTextareaKeybindings()
377
+
378
+ const fileStyleId = syntax().getStyleId("extmark.file")!
379
+ const agentStyleId = syntax().getStyleId("extmark.agent")!
380
+ const pasteStyleId = syntax().getStyleId("extmark.paste")!
381
+ let promptPartTypeId = 0
382
+ const event = useEvent()
383
+
384
+ event.on(TuiEvent.PromptAppend.type, (evt) => {
385
+ if (!input || input.isDestroyed) return
386
+ input.insertText(evt.properties.text)
387
+ setTimeout(() => {
388
+ // setTimeout is a workaround and needs to be addressed properly
389
+ if (!input || input.isDestroyed) return
390
+ input.getLayoutNode().markDirty()
391
+ input.gotoBufferEnd()
392
+ renderer.requestRender()
393
+ }, 0)
394
+ })
395
+
396
+ createEffect(() => {
397
+ if (props.disabled) input.cursorColor = theme.backgroundElement
398
+ if (!props.disabled) input.cursorColor = theme.text
399
+ })
400
+
401
+ const lastUserMessage = createMemo(() => {
402
+ if (!props.sessionID) return undefined
403
+ const messages = sync.data.message[props.sessionID]?.["main"]
404
+ if (!messages) return undefined
405
+ return messages.findLast((m): m is UserMessage => m.role === "user")
406
+ })
407
+
408
+ // After the agent finishes a turn, predict the user's likely next prompt and
409
+ // show it as ghost text in the empty input (accept with Tab). Only fires on
410
+ // an idle transition while the input is empty so it never clobbers typing.
411
+ let ghostRequest = 0
412
+ async function fetchGhost(sessionID: string) {
413
+ if (props.showPlaceholder === false) return
414
+ const token = ++ghostRequest
415
+ const userMessageID = lastUserMessage()?.id
416
+ const res = await sdk.client.session.predict({ sessionID }).catch(() => undefined)
417
+ const text = res?.data?.prediction?.trim()
418
+ if (!text) return
419
+ // Drop the result if anything that defined its context changed while the
420
+ // request was in flight: superseded by a newer fetch, session switched, a
421
+ // new run started, the conversation advanced, or the user began typing.
422
+ if (token !== ghostRequest) return
423
+ if (props.sessionID !== sessionID) return
424
+ if (status().type !== "idle") return
425
+ if (lastUserMessage()?.id !== userMessageID) return
426
+ if (!input || input.isDestroyed || input.plainText !== "") return
427
+ setGhost(text)
428
+ }
429
+ createEffect(
430
+ on(
431
+ () => status().type,
432
+ (type, prev) => {
433
+ if (type !== "idle") {
434
+ // A new run started (or the session went non-idle): invalidate any
435
+ // in-flight prediction and hide a stale suggestion.
436
+ ghostRequest++
437
+ if (ghost()) setGhost("")
438
+ return
439
+ }
440
+ if (prev === "idle") return
441
+ const sessionID = props.sessionID
442
+ if (!sessionID || !input || input.isDestroyed || input.plainText !== "") return
443
+ if (!lastUserMessage()) return
444
+ fetchGhost(sessionID)
445
+ },
446
+ ),
447
+ )
448
+ // While a ghost suggestion is showing, suspend global command keybinds so Tab
449
+ // reaches the textarea's onKeyDown (where we accept it) instead of being
450
+ // consumed by the agent-cycle keybind. Global keyboard handlers run before
451
+ // renderable handlers, so without this the suggestion can never be accepted.
452
+ // The cleanup resumes keybinds on any dismissal (typing, accept, submit,
453
+ // session change, status leaving idle).
454
+ createEffect(() => {
455
+ if (!ghost()) return
456
+ command.keybinds(false)
457
+ onCleanup(() => command.keybinds(true))
458
+ })
459
+
460
+ const usage = createMemo(() => {
461
+ if (!props.sessionID) return
462
+ const msg = sync.data.message[props.sessionID]?.["main"] ?? []
463
+ const last = msg.findLast((item): item is AssistantMessage => item.role === "assistant" && item.tokens.output > 0)
464
+ if (!last) return
465
+
466
+ const tokens =
467
+ last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write
468
+ if (tokens <= 0) return
469
+
470
+ const model = sync.data.provider.find((item) => item.id === last.providerID)?.models[last.modelID]
471
+ const pct = model?.limit.context ? `${Math.round((tokens / model.limit.context) * 100)}%` : undefined
472
+ const cost = msg.reduce((sum, item) => sum + (item.role === "assistant" ? item.cost : 0), 0)
473
+ return {
474
+ context: pct ? `${Locale.number(tokens)} (${pct})` : Locale.number(tokens),
475
+ cost: cost > 0 ? money.format(cost) : undefined,
476
+ }
477
+ })
478
+
479
+ const [store, setStore] = createStore<{
480
+ prompt: PromptInfo
481
+ mode: "normal" | "shell"
482
+ extmarkToPartIndex: Map<number, number>
483
+ interrupt: number
484
+ placeholder: number
485
+ }>({
486
+ placeholder: randomIndex(list().length),
487
+ prompt: {
488
+ input: "",
489
+ parts: [],
490
+ },
491
+ mode: "normal",
492
+ extmarkToPartIndex: new Map(),
493
+ interrupt: 0,
494
+ })
495
+
496
+ createEffect(
497
+ on(
498
+ () => props.sessionID,
499
+ () => {
500
+ setGhost("")
501
+ setStore("placeholder", randomIndex(list().length))
502
+ },
503
+ { defer: true },
504
+ ),
505
+ )
506
+
507
+ // Initialize agent/model/variant from last user message when session changes
508
+ let syncedSessionID: string | undefined
509
+ createEffect(() => {
510
+ const sessionID = props.sessionID
511
+ const msg = lastUserMessage()
512
+
513
+ if (sessionID !== syncedSessionID) {
514
+ if (!sessionID || !msg) return
515
+
516
+ syncedSessionID = sessionID
517
+
518
+ // Only set agent if it's a primary agent (not a subagent)
519
+ const isPrimaryAgent = local.agent.list().some((x) => x.name === msg.agent)
520
+ if (msg.agent && isPrimaryAgent) {
521
+ // Keep command line --agent if specified.
522
+ if (!args.agent) local.agent.set(msg.agent)
523
+ if (msg.model) {
524
+ local.model.set(msg.model)
525
+ local.model.variant.set(msg.model.variant)
526
+ }
527
+ }
528
+ }
529
+ })
530
+
531
+ command.register(() => {
532
+ return [
533
+ {
534
+ title: t("tui.command.prompt.clear.title"),
535
+ value: "prompt.clear",
536
+ category: "prompt",
537
+ hidden: true,
538
+ onSelect: (dialog) => {
539
+ input.extmarks.clear()
540
+ input.clear()
541
+ dialog.clear()
542
+ },
543
+ },
544
+ {
545
+ title: t("tui.command.prompt.submit.title"),
546
+ value: "prompt.submit",
547
+ keybind: "input_submit",
548
+ category: "prompt",
549
+ hidden: true,
550
+ onSelect: async (dialog) => {
551
+ if (!input.focused) return
552
+ const handled = await submit()
553
+ if (!handled) return
554
+
555
+ dialog.clear()
556
+ },
557
+ },
558
+ {
559
+ title: t("tui.command.prompt.paste.title"),
560
+ value: "prompt.paste",
561
+ keybind: "input_paste",
562
+ category: "prompt",
563
+ hidden: true,
564
+ onSelect: async () => {
565
+ await pasteFromClipboard()
566
+ },
567
+ },
568
+ {
569
+ title: t("tui.command.session.interrupt.title"),
570
+ value: "session.interrupt",
571
+ keybind: "session_interrupt",
572
+ category: "session",
573
+ hidden: true,
574
+ enabled: status().type !== "idle",
575
+ onSelect: (dialog) => {
576
+ if (autocomplete.visible) return
577
+ if (!input.focused) return
578
+ // TODO: this should be its own command
579
+ if (store.mode === "shell") {
580
+ setStore("mode", "normal")
581
+ return
582
+ }
583
+ if (!props.sessionID) return
584
+
585
+ setStore("interrupt", store.interrupt + 1)
586
+
587
+ setTimeout(() => {
588
+ setStore("interrupt", 0)
589
+ }, 5000)
590
+
591
+ if (store.interrupt >= 2) {
592
+ void sdk.client.session.abort({
593
+ sessionID: props.sessionID,
594
+ })
595
+ setStore("interrupt", 0)
596
+ }
597
+ dialog.clear()
598
+ },
599
+ },
600
+ {
601
+ title: t("tui.command.prompt.editor.title"),
602
+ category: "session",
603
+ keybind: "editor_open",
604
+ value: "prompt.editor",
605
+ slash: {
606
+ name: "editor",
607
+ },
608
+ onSelect: async (dialog) => {
609
+ dialog.clear()
610
+
611
+ // replace summarized text parts with the actual text
612
+ const text = store.prompt.parts
613
+ .filter((p) => p.type === "text")
614
+ .reduce((acc, p) => {
615
+ if (!p.source) return acc
616
+ return acc.replace(p.source.text.value, p.text)
617
+ }, store.prompt.input)
618
+
619
+ const nonTextParts = store.prompt.parts.filter((p) => p.type !== "text")
620
+
621
+ const value = text
622
+ const content = await Editor.open({ value, renderer })
623
+ if (!content) return
624
+
625
+ input.setText(content)
626
+
627
+ // Update positions for nonTextParts based on their location in new content
628
+ // Filter out parts whose virtual text was deleted
629
+ // this handles a case where the user edits the text in the editor
630
+ // such that the virtual text moves around or is deleted
631
+ const updatedNonTextParts = nonTextParts
632
+ .map((part) => {
633
+ let virtualText = ""
634
+ if (part.type === "file" && part.source?.text) {
635
+ virtualText = part.source.text.value
636
+ } else if (part.type === "agent" && part.source) {
637
+ virtualText = part.source.value
638
+ }
639
+
640
+ if (!virtualText) return part
641
+
642
+ const newStart = content.indexOf(virtualText)
643
+ // if the virtual text is deleted, remove the part
644
+ if (newStart === -1) return null
645
+
646
+ const newEnd = newStart + virtualText.length
647
+
648
+ if (part.type === "file" && part.source?.text) {
649
+ return {
650
+ ...part,
651
+ source: {
652
+ ...part.source,
653
+ text: {
654
+ ...part.source.text,
655
+ start: newStart,
656
+ end: newEnd,
657
+ },
658
+ },
659
+ }
660
+ }
661
+
662
+ if (part.type === "agent" && part.source) {
663
+ return {
664
+ ...part,
665
+ source: {
666
+ ...part.source,
667
+ start: newStart,
668
+ end: newEnd,
669
+ },
670
+ }
671
+ }
672
+
673
+ return part
674
+ })
675
+ .filter((part) => part !== null)
676
+
677
+ setStore("prompt", {
678
+ input: content,
679
+ // keep only the non-text parts because the text parts were
680
+ // already expanded inline
681
+ parts: updatedNonTextParts,
682
+ })
683
+ restoreExtmarksFromParts(updatedNonTextParts)
684
+ input.cursorOffset = Bun.stringWidth(content)
685
+ },
686
+ },
687
+ {
688
+ title: t("tui.command.prompt.skills.title"),
689
+ value: "prompt.skills",
690
+ category: "prompt",
691
+ slash: {
692
+ name: "skills",
693
+ },
694
+ onSelect: () => {
695
+ dialog.replace(() => (
696
+ <DialogSkill
697
+ onSelect={(skill) => {
698
+ input.setText(`/${skill} `)
699
+ setStore("prompt", {
700
+ input: `/${skill} `,
701
+ parts: [],
702
+ })
703
+ input.gotoBufferEnd()
704
+ }}
705
+ />
706
+ ))
707
+ },
708
+ },
709
+ {
710
+ title: t("tui.command.consent.revoke.title"),
711
+ value: "consent.revoke",
712
+ category: "prompt",
713
+ slash: {
714
+ name: "revoke-consent",
715
+ },
716
+ onSelect: (dialog) => {
717
+ kv.delete(FREE_AGREEMENT_KEY)
718
+ dialog.clear()
719
+ toast.show({
720
+ message: t("tui.consent.revoked"),
721
+ variant: "info",
722
+ duration: 3000,
723
+ })
724
+ },
725
+ },
726
+ {
727
+ title: voiceEnabled() ? t("tui.command.voice.toggle.title_on") : t("tui.command.voice.toggle.title_off"),
728
+ value: "voice.toggle",
729
+ category: "prompt",
730
+ slash: {
731
+ name: "voice",
732
+ },
733
+ onSelect: () => {
734
+ const next = !voiceEnabled()
735
+ kv.set("voice_enabled", next)
736
+ if (!next && activeVoice) void voiceToggle()
737
+ toast.show({
738
+ message: next ? t("tui.voice.enabled") : t("tui.voice.disabled"),
739
+ variant: "info",
740
+ duration: 3000,
741
+ })
742
+ },
743
+ },
744
+ {
745
+ title: voiceSendEnabled() ? t("tui.command.voice.send.title_on") : t("tui.command.voice.send.title_off"),
746
+ value: "voice.send",
747
+ category: "prompt",
748
+ slash: {
749
+ name: "voice-send",
750
+ },
751
+ onSelect: () => {
752
+ const next = !voiceSendEnabled()
753
+ kv.set("voice_send_command", next)
754
+ toast.show({
755
+ message: next ? t("tui.voice.send.enabled") : t("tui.voice.send.disabled"),
756
+ variant: "info",
757
+ duration: 3000,
758
+ })
759
+ },
760
+ },
761
+ {
762
+ title: voiceControlEnabled() ? t("tui.command.voice.control.title_on") : t("tui.command.voice.control.title_off"),
763
+ value: "voice.control",
764
+ category: "prompt",
765
+ slash: {
766
+ name: "voice-control",
767
+ },
768
+ onSelect: () => {
769
+ const next = !voiceControlEnabled()
770
+ kv.set("voice_control_enabled", next)
771
+ toast.show({
772
+ message: next ? t("tui.voice.control.enabled") : t("tui.voice.control.disabled"),
773
+ variant: "info",
774
+ duration: 3000,
775
+ })
776
+ },
777
+ },
778
+ ]
779
+ })
780
+
781
+ const ref: PromptRef = {
782
+ get focused() {
783
+ return input.focused
784
+ },
785
+ get current() {
786
+ return store.prompt
787
+ },
788
+ focus() {
789
+ input.focus()
790
+ },
791
+ blur() {
792
+ input.blur()
793
+ },
794
+ set(prompt) {
795
+ input.setText(prompt.input)
796
+ setStore("prompt", prompt)
797
+ restoreExtmarksFromParts(prompt.parts)
798
+ input.gotoBufferEnd()
799
+ },
800
+ reset() {
801
+ input.clear()
802
+ input.extmarks.clear()
803
+ setStore("prompt", {
804
+ input: "",
805
+ parts: [],
806
+ })
807
+ setStore("extmarkToPartIndex", new Map())
808
+ },
809
+ submit() {
810
+ void submit()
811
+ },
812
+ paste() {
813
+ void pasteFromClipboard()
814
+ },
815
+ }
816
+
817
+ onMount(() => {
818
+ const saved = stashed
819
+ stashed = undefined
820
+ if (store.prompt.input) return
821
+ if (saved && saved.prompt.input) {
822
+ input.setText(saved.prompt.input)
823
+ setStore("prompt", saved.prompt)
824
+ restoreExtmarksFromParts(saved.prompt.parts)
825
+ input.cursorOffset = saved.cursor
826
+ }
827
+ })
828
+
829
+ onCleanup(() => {
830
+ if (store.prompt.input) {
831
+ stashed = { prompt: unwrap(store.prompt), cursor: input.cursorOffset }
832
+ }
833
+ props.ref?.(undefined)
834
+ })
835
+
836
+ createEffect(() => {
837
+ if (!input || input.isDestroyed) return
838
+ if (props.visible === false || dialog.stack.length > 0) {
839
+ if (input.focused) input.blur()
840
+ return
841
+ }
842
+
843
+ // Slot/plugin updates can remount the background prompt while a dialog is open.
844
+ // Keep focus with the dialog and let the prompt reclaim it after the dialog closes.
845
+ if (!input.focused) input.focus()
846
+ })
847
+
848
+ createEffect(() => {
849
+ if (!input || input.isDestroyed) return
850
+ const capture =
851
+ store.mode === "normal"
852
+ ? auto()?.visible
853
+ ? (["escape", "navigate", "submit", "tab"] as const)
854
+ : (["tab"] as const)
855
+ : undefined
856
+ input.traits = {
857
+ capture,
858
+ suspend: !!props.disabled || store.mode === "shell",
859
+ status: store.mode === "shell" ? "SHELL" : undefined,
860
+ }
861
+ })
862
+
863
+ function restoreExtmarksFromParts(parts: PromptInfo["parts"]) {
864
+ input.extmarks.clear()
865
+ setStore("extmarkToPartIndex", new Map())
866
+
867
+ parts.forEach((part, partIndex) => {
868
+ let start = 0
869
+ let end = 0
870
+ let virtualText = ""
871
+ let styleId: number | undefined
872
+
873
+ if (part.type === "file" && part.source?.text) {
874
+ start = part.source.text.start
875
+ end = part.source.text.end
876
+ virtualText = part.source.text.value
877
+ styleId = fileStyleId
878
+ } else if (part.type === "agent" && part.source) {
879
+ start = part.source.start
880
+ end = part.source.end
881
+ virtualText = part.source.value
882
+ styleId = agentStyleId
883
+ } else if (part.type === "text" && part.source?.text) {
884
+ start = part.source.text.start
885
+ end = part.source.text.end
886
+ virtualText = part.source.text.value
887
+ styleId = pasteStyleId
888
+ }
889
+
890
+ if (virtualText) {
891
+ const extmarkId = input.extmarks.create({
892
+ start,
893
+ end,
894
+ virtual: true,
895
+ styleId,
896
+ typeId: promptPartTypeId,
897
+ })
898
+ setStore("extmarkToPartIndex", (map: Map<number, number>) => {
899
+ const newMap = new Map(map)
900
+ newMap.set(extmarkId, partIndex)
901
+ return newMap
902
+ })
903
+ }
904
+ })
905
+ }
906
+
907
+ function syncExtmarksWithPromptParts() {
908
+ const allExtmarks = input.extmarks.getAllForTypeId(promptPartTypeId)
909
+ setStore(
910
+ produce((draft) => {
911
+ const newMap = new Map<number, number>()
912
+ const newParts: typeof draft.prompt.parts = []
913
+
914
+ for (const extmark of allExtmarks) {
915
+ const partIndex = draft.extmarkToPartIndex.get(extmark.id)
916
+ if (partIndex !== undefined) {
917
+ const part = draft.prompt.parts[partIndex]
918
+ if (part) {
919
+ if (part.type === "agent" && part.source) {
920
+ part.source.start = extmark.start
921
+ part.source.end = extmark.end
922
+ } else if (part.type === "file" && part.source?.text) {
923
+ part.source.text.start = extmark.start
924
+ part.source.text.end = extmark.end
925
+ } else if (part.type === "text" && part.source?.text) {
926
+ part.source.text.start = extmark.start
927
+ part.source.text.end = extmark.end
928
+ }
929
+ newMap.set(extmark.id, newParts.length)
930
+ newParts.push(part)
931
+ }
932
+ }
933
+ }
934
+
935
+ draft.extmarkToPartIndex = newMap
936
+ draft.prompt.parts = newParts
937
+ }),
938
+ )
939
+ }
940
+
941
+ command.register(() => [
942
+ {
943
+ title: t("tui.command.prompt.stash.title"),
944
+ value: "prompt.stash",
945
+ category: "prompt",
946
+ enabled: !!store.prompt.input,
947
+ onSelect: (dialog) => {
948
+ if (!store.prompt.input) return
949
+ stash.push({
950
+ input: store.prompt.input,
951
+ parts: store.prompt.parts,
952
+ })
953
+ input.extmarks.clear()
954
+ input.clear()
955
+ setStore("prompt", { input: "", parts: [] })
956
+ setStore("extmarkToPartIndex", new Map())
957
+ dialog.clear()
958
+ },
959
+ },
960
+ {
961
+ title: t("tui.command.prompt.stash.pop.title"),
962
+ value: "prompt.stash.pop",
963
+ category: "prompt",
964
+ enabled: stash.list().length > 0,
965
+ onSelect: (dialog) => {
966
+ const entry = stash.pop()
967
+ if (entry) {
968
+ input.setText(entry.input)
969
+ setStore("prompt", { input: entry.input, parts: entry.parts })
970
+ restoreExtmarksFromParts(entry.parts)
971
+ input.gotoBufferEnd()
972
+ }
973
+ dialog.clear()
974
+ },
975
+ },
976
+ {
977
+ title: t("tui.command.prompt.stash.list.title"),
978
+ value: "prompt.stash.list",
979
+ category: "prompt",
980
+ enabled: stash.list().length > 0,
981
+ onSelect: (dialog) => {
982
+ dialog.replace(() => (
983
+ <DialogStash
984
+ onSelect={(entry) => {
985
+ input.setText(entry.input)
986
+ setStore("prompt", { input: entry.input, parts: entry.parts })
987
+ restoreExtmarksFromParts(entry.parts)
988
+ input.gotoBufferEnd()
989
+ }}
990
+ />
991
+ ))
992
+ },
993
+ },
994
+ ])
995
+
996
+ // While the free-model agreement dialog is open, ignore any further submit()
997
+ // calls. Enter triggers submit twice (the input_submit keybind plus the
998
+ // textarea's deferred onSubmit), and without this guard the deferred call can
999
+ // interleave with the post-accept re-submit and drop the user's message.
1000
+ let agreementPending = false
1001
+ async function submit() {
1002
+ if (agreementPending) return false
1003
+ setGhost("")
1004
+ // IME: double-defer may fire before onContentChange flushes the last
1005
+ // composed character (e.g. Korean hangul) to the store, so read
1006
+ // plainText directly and sync before any downstream reads.
1007
+ if (input && !input.isDestroyed && input.plainText !== store.prompt.input) {
1008
+ setStore("prompt", "input", input.plainText)
1009
+ syncExtmarksWithPromptParts()
1010
+ }
1011
+ if (props.disabled) return false
1012
+ if (autocomplete?.visible) return false
1013
+ if (!store.prompt.input) return false
1014
+ const agent = local.agent.current()
1015
+ if (!agent) return false
1016
+ const trimmed = store.prompt.input.trim()
1017
+ if (trimmed === "exit" || trimmed === "quit" || trimmed === ":q") {
1018
+ void exit()
1019
+ return true
1020
+ }
1021
+ const selectedModel = local.model.current()
1022
+ if (!selectedModel) {
1023
+ void promptModelWarning()
1024
+ return false
1025
+ }
1026
+
1027
+ // Free models require a one-time acknowledgment of the terms and privacy
1028
+ // policy. Gate submission until the user accepts; the flag is stored in KV.
1029
+ const isFreeModel = FREE_MODEL_IDS.has(selectedModel.modelID)
1030
+ if (isFreeModel && !kv.get(FREE_AGREEMENT_KEY)) {
1031
+ agreementPending = true
1032
+ DialogAgreement.show(dialog, {
1033
+ onConfirm: () => {
1034
+ kv.set(FREE_AGREEMENT_KEY, true)
1035
+ void submit()
1036
+ },
1037
+ // Fires on any dismissal (confirm, cancel, esc, click-outside). Reset
1038
+ // the guard here so submission is unblocked once the dialog is gone.
1039
+ onClose: () => {
1040
+ agreementPending = false
1041
+ },
1042
+ })
1043
+ return false
1044
+ }
1045
+
1046
+ const workspaceSession = props.sessionID ? sync.session.get(props.sessionID) : undefined
1047
+ const workspaceID = workspaceSession?.workspaceID
1048
+ const workspaceStatus = workspaceID ? (project.workspace.status(workspaceID) ?? "error") : undefined
1049
+ if (props.sessionID && workspaceID && workspaceStatus !== "connected") {
1050
+ dialog.replace(() => (
1051
+ <DialogWorkspaceUnavailable
1052
+ onRestore={() => {
1053
+ dialog.replace(() => (
1054
+ <DialogWorkspaceCreate
1055
+ onSelect={(nextWorkspaceID) =>
1056
+ restoreWorkspaceSession({
1057
+ dialog,
1058
+ sdk,
1059
+ sync,
1060
+ project,
1061
+ toast,
1062
+ workspaceID: nextWorkspaceID,
1063
+ sessionID: props.sessionID!,
1064
+ })
1065
+ }
1066
+ />
1067
+ ))
1068
+ }}
1069
+ />
1070
+ ))
1071
+ return false
1072
+ }
1073
+
1074
+ let sessionID = props.sessionID
1075
+ if (sessionID == null) {
1076
+ const res = await sdk.client.session.create({ workspace: props.workspaceID })
1077
+
1078
+ if (res.error) {
1079
+ console.log("Creating a session failed:", res.error)
1080
+
1081
+ toast.show({
1082
+ message: "Creating a session failed. Open console for more details.",
1083
+ variant: "error",
1084
+ })
1085
+
1086
+ return true
1087
+ }
1088
+
1089
+ sessionID = res.data.id
1090
+ }
1091
+
1092
+ const messageID = MessageID.ascending()
1093
+ let inputText = store.prompt.input
1094
+
1095
+ // Expand pasted text inline before submitting
1096
+ const allExtmarks = input.extmarks.getAllForTypeId(promptPartTypeId)
1097
+ const sortedExtmarks = allExtmarks.sort((a: { start: number }, b: { start: number }) => b.start - a.start)
1098
+
1099
+ for (const extmark of sortedExtmarks) {
1100
+ const partIndex = store.extmarkToPartIndex.get(extmark.id)
1101
+ if (partIndex !== undefined) {
1102
+ const part = store.prompt.parts[partIndex]
1103
+ if (part?.type === "text" && part.text) {
1104
+ const before = inputText.slice(0, extmark.start)
1105
+ const after = inputText.slice(extmark.end)
1106
+ inputText = before + part.text + after
1107
+ }
1108
+ }
1109
+ }
1110
+
1111
+ // Filter out text parts (pasted content) since they're now expanded inline
1112
+ const nonTextParts = store.prompt.parts.filter((part) => part.type !== "text")
1113
+
1114
+ // Capture mode before it gets reset
1115
+ const currentMode = store.mode
1116
+ const variant = local.model.variant.current()
1117
+
1118
+ const clientSlash = inputText.startsWith("/")
1119
+ ? command.slashes().find((s) => s.display === inputText.trim())
1120
+ : undefined
1121
+
1122
+ if (store.mode === "shell") {
1123
+ void sdk.client.session.shell({
1124
+ sessionID,
1125
+ agent: agent.name,
1126
+ model: {
1127
+ providerID: selectedModel.providerID,
1128
+ modelID: selectedModel.modelID,
1129
+ },
1130
+ command: inputText,
1131
+ })
1132
+ setStore("mode", "normal")
1133
+ } else if (clientSlash) {
1134
+ clientSlash.onSelect?.()
1135
+ } else if (
1136
+ inputText.startsWith("/") &&
1137
+ iife(() => {
1138
+ const firstLine = inputText.split("\n")[0]
1139
+ const command = firstLine.split(" ")[0].slice(1)
1140
+ return sync.data.command.some((x) => x.name === command)
1141
+ })
1142
+ ) {
1143
+ // Parse command from first line, preserve multi-line content in arguments
1144
+ const firstLineEnd = inputText.indexOf("\n")
1145
+ const firstLine = firstLineEnd === -1 ? inputText : inputText.slice(0, firstLineEnd)
1146
+ const [command, ...firstLineArgs] = firstLine.split(" ")
1147
+ const restOfInput = firstLineEnd === -1 ? "" : inputText.slice(firstLineEnd + 1)
1148
+ const args = firstLineArgs.join(" ") + (restOfInput ? "\n" + restOfInput : "")
1149
+
1150
+ void sdk.client.session.command({
1151
+ sessionID,
1152
+ command: command.slice(1),
1153
+ arguments: args,
1154
+ agent: agent.name,
1155
+ model: `${selectedModel.providerID}/${selectedModel.modelID}`,
1156
+ messageID,
1157
+ variant,
1158
+ parts: nonTextParts
1159
+ .filter((x) => x.type === "file")
1160
+ .map((x) => ({
1161
+ id: PartID.ascending(),
1162
+ ...x,
1163
+ })),
1164
+ })
1165
+ } else {
1166
+ sdk.client.session
1167
+ .promptAsync({
1168
+ sessionID,
1169
+ ...selectedModel,
1170
+ messageID,
1171
+ agent: agent.name,
1172
+ model: selectedModel,
1173
+ variant,
1174
+ parts: [
1175
+ {
1176
+ id: PartID.ascending(),
1177
+ type: "text",
1178
+ text: inputText,
1179
+ },
1180
+ ...nonTextParts.map(assign),
1181
+ ],
1182
+ })
1183
+ .catch((err) => {
1184
+ toast.show({
1185
+ message: err instanceof Error ? err.message : "Failed to send message",
1186
+ variant: "error",
1187
+ })
1188
+ })
1189
+ }
1190
+ history.append({
1191
+ ...store.prompt,
1192
+ mode: currentMode,
1193
+ })
1194
+ input.extmarks.clear()
1195
+ setStore("prompt", {
1196
+ input: "",
1197
+ parts: [],
1198
+ })
1199
+ setStore("extmarkToPartIndex", new Map())
1200
+ props.onSubmit?.()
1201
+
1202
+ // temporary hack to make sure the message is sent
1203
+ if (!props.sessionID)
1204
+ setTimeout(() => {
1205
+ route.navigate({
1206
+ type: "session",
1207
+ sessionID,
1208
+ })
1209
+ }, 50)
1210
+ input.clear()
1211
+ return true
1212
+ }
1213
+ const exit = useExit()
1214
+
1215
+ function pasteText(text: string, virtualText: string) {
1216
+ const currentOffset = input.visualCursor.offset
1217
+ const extmarkStart = currentOffset
1218
+ const extmarkEnd = extmarkStart + virtualText.length
1219
+
1220
+ input.insertText(virtualText + " ")
1221
+
1222
+ const extmarkId = input.extmarks.create({
1223
+ start: extmarkStart,
1224
+ end: extmarkEnd,
1225
+ virtual: true,
1226
+ styleId: pasteStyleId,
1227
+ typeId: promptPartTypeId,
1228
+ })
1229
+
1230
+ setStore(
1231
+ produce((draft) => {
1232
+ const partIndex = draft.prompt.parts.length
1233
+ draft.prompt.parts.push({
1234
+ type: "text" as const,
1235
+ text,
1236
+ source: {
1237
+ text: {
1238
+ start: extmarkStart,
1239
+ end: extmarkEnd,
1240
+ value: virtualText,
1241
+ },
1242
+ },
1243
+ })
1244
+ draft.extmarkToPartIndex.set(extmarkId, partIndex)
1245
+ }),
1246
+ )
1247
+ }
1248
+
1249
+ async function pastePlainText(normalizedText: string) {
1250
+ const pastedContent = normalizedText.trim()
1251
+ if (!pastedContent) return
1252
+
1253
+ const filepath = iife(() => {
1254
+ const raw = pastedContent.replace(/^['"]+|['"]+$/g, "")
1255
+ if (raw.startsWith("file://")) {
1256
+ try {
1257
+ return fileURLToPath(raw)
1258
+ } catch {}
1259
+ }
1260
+ if (process.platform === "win32") return raw
1261
+ return raw.replace(/\\(.)/g, "$1")
1262
+ })
1263
+ const isUrl = /^(https?):\/\//.test(filepath)
1264
+ if (!isUrl) {
1265
+ try {
1266
+ const mime = await Filesystem.mimeType(filepath)
1267
+ const filename = path.basename(filepath)
1268
+ // Handle SVG as raw text content, not as base64 image
1269
+ if (mime === "image/svg+xml") {
1270
+ const content = await Filesystem.readText(filepath).catch(() => {})
1271
+ if (content) {
1272
+ pasteText(content, `[SVG: ${filename ?? "image"}]`)
1273
+ return
1274
+ }
1275
+ }
1276
+ if (mime.startsWith("image/") || mime === "application/pdf") {
1277
+ const content = await Filesystem.readArrayBuffer(filepath)
1278
+ .then((buffer) => Buffer.from(buffer).toString("base64"))
1279
+ .catch(() => {})
1280
+ if (content) {
1281
+ await pasteAttachment({
1282
+ filename,
1283
+ filepath,
1284
+ mime,
1285
+ content,
1286
+ })
1287
+ return
1288
+ }
1289
+ }
1290
+ } catch {}
1291
+ }
1292
+
1293
+ const lineCount = (pastedContent.match(/\n/g)?.length ?? 0) + 1
1294
+ if ((lineCount >= 3 || pastedContent.length > 150) && !sync.data.config.experimental?.disable_paste_summary) {
1295
+ pasteText(pastedContent, `[Pasted ~${lineCount} lines]`)
1296
+ return
1297
+ }
1298
+
1299
+ input.insertText(normalizedText)
1300
+
1301
+ // Force layout update and render for the pasted content
1302
+ setTimeout(() => {
1303
+ // setTimeout is a workaround and needs to be addressed properly
1304
+ if (!input || input.isDestroyed) return
1305
+ input.getLayoutNode().markDirty()
1306
+ renderer.requestRender()
1307
+ }, 0)
1308
+ }
1309
+
1310
+ async function pasteFromClipboard() {
1311
+ if (props.disabled) return
1312
+ const content = await Clipboard.read()
1313
+ if (!content) return
1314
+ if (content.mime.startsWith("image/")) {
1315
+ await pasteAttachment({
1316
+ filename: "clipboard",
1317
+ mime: content.mime,
1318
+ content: content.data,
1319
+ })
1320
+ return
1321
+ }
1322
+ await pastePlainText(content.data.replace(/\r\n/g, "\n").replace(/\r/g, "\n"))
1323
+ }
1324
+
1325
+ async function pasteAttachment(file: { filename?: string; filepath?: string; content: string; mime: string }) {
1326
+ const currentOffset = input.visualCursor.offset
1327
+ const extmarkStart = currentOffset
1328
+ const pdf = file.mime === "application/pdf"
1329
+ const count = store.prompt.parts.filter((x) => {
1330
+ if (x.type !== "file") return false
1331
+ if (pdf) return x.mime === "application/pdf"
1332
+ return x.mime.startsWith("image/")
1333
+ }).length
1334
+ const virtualText = pdf ? `[PDF ${count + 1}]` : `[Image ${count + 1}]`
1335
+ const extmarkEnd = extmarkStart + virtualText.length
1336
+ const textToInsert = virtualText + " "
1337
+
1338
+ input.insertText(textToInsert)
1339
+
1340
+ const extmarkId = input.extmarks.create({
1341
+ start: extmarkStart,
1342
+ end: extmarkEnd,
1343
+ virtual: true,
1344
+ styleId: pasteStyleId,
1345
+ typeId: promptPartTypeId,
1346
+ })
1347
+
1348
+ const part: Omit<FilePart, "id" | "messageID" | "sessionID"> = {
1349
+ type: "file" as const,
1350
+ mime: file.mime,
1351
+ filename: file.filename,
1352
+ url: `data:${file.mime};base64,${file.content}`,
1353
+ source: {
1354
+ type: "file",
1355
+ path: file.filepath ?? file.filename ?? "",
1356
+ text: {
1357
+ start: extmarkStart,
1358
+ end: extmarkEnd,
1359
+ value: virtualText,
1360
+ },
1361
+ },
1362
+ }
1363
+ setStore(
1364
+ produce((draft) => {
1365
+ const partIndex = draft.prompt.parts.length
1366
+ draft.prompt.parts.push(part)
1367
+ draft.extmarkToPartIndex.set(extmarkId, partIndex)
1368
+ }),
1369
+ )
1370
+ return
1371
+ }
1372
+
1373
+ const highlight = createMemo(() => {
1374
+ if (keybind.leader) return theme.border
1375
+ if (store.mode === "shell") return theme.primary
1376
+ const agent = local.agent.current()
1377
+ if (!agent) return theme.border
1378
+ return local.agent.color(agent.name)
1379
+ })
1380
+
1381
+ const showVariant = createMemo(() => {
1382
+ const variants = local.model.variant.list()
1383
+ if (variants.length === 0) return false
1384
+ const current = local.model.variant.current()
1385
+ return !!current
1386
+ })
1387
+
1388
+ const agentMetaAlpha = createFadeIn(() => !!local.agent.current(), animationsEnabled)
1389
+ const modelMetaAlpha = createFadeIn(() => !!local.agent.current() && store.mode === "normal", animationsEnabled)
1390
+ const variantMetaAlpha = createFadeIn(
1391
+ () => !!local.agent.current() && store.mode === "normal" && showVariant(),
1392
+ animationsEnabled,
1393
+ )
1394
+ const borderHighlight = createMemo(() => tint(theme.border, highlight(), agentMetaAlpha()))
1395
+
1396
+ const placeholderText = createMemo(() => {
1397
+ if (props.showPlaceholder === false) return undefined
1398
+ if (store.mode === "normal" && ghost()) return t("tui.prompt.ghost", { prediction: ghost() })
1399
+ if (store.mode === "shell") {
1400
+ if (!shell().length) return undefined
1401
+ return t("tui.prompt.placeholder.shell", { example: shell()[store.placeholder % shell().length] })
1402
+ }
1403
+ if (!list().length) return undefined
1404
+ return t("tui.prompt.placeholder.normal", { example: list()[store.placeholder % list().length] })
1405
+ })
1406
+
1407
+ const spinnerDef = createMemo(() => {
1408
+ const agent = local.agent.current()
1409
+ const color = agent ? local.agent.color(agent.name) : theme.border
1410
+ return {
1411
+ frames: createFrames({
1412
+ color,
1413
+ style: "plane",
1414
+ width: 14,
1415
+ holdStart: 8,
1416
+ holdEnd: 8,
1417
+ inactiveFactor: 0.6,
1418
+ // enableFading: false,
1419
+ minAlpha: 0.3,
1420
+ }),
1421
+ color: createColors({
1422
+ color,
1423
+ style: "plane",
1424
+ holdStart: 8,
1425
+ holdEnd: 8,
1426
+ inactiveFactor: 0.6,
1427
+ // enableFading: false,
1428
+ minAlpha: 0.3,
1429
+ }),
1430
+ }
1431
+ })
1432
+
1433
+ return (
1434
+ <>
1435
+ <Autocomplete
1436
+ sessionID={props.sessionID}
1437
+ ref={(r) => {
1438
+ autocomplete = r
1439
+ setAuto(() => r)
1440
+ }}
1441
+ anchor={() => anchor}
1442
+ input={() => input}
1443
+ setPrompt={(cb) => {
1444
+ setStore("prompt", produce(cb))
1445
+ }}
1446
+ setExtmark={(partIndex, extmarkId) => {
1447
+ setStore("extmarkToPartIndex", (map: Map<number, number>) => {
1448
+ const newMap = new Map(map)
1449
+ newMap.set(extmarkId, partIndex)
1450
+ return newMap
1451
+ })
1452
+ }}
1453
+ value={store.prompt.input}
1454
+ fileStyleId={fileStyleId}
1455
+ agentStyleId={agentStyleId}
1456
+ promptPartTypeId={() => promptPartTypeId}
1457
+ />
1458
+ <box ref={(r) => (anchor = r)} visible={props.visible !== false}>
1459
+ <box
1460
+ border={["left"]}
1461
+ borderColor={borderHighlight()}
1462
+ customBorderChars={{
1463
+ ...SplitBorder.customBorderChars,
1464
+ bottomLeft: "╹",
1465
+ }}
1466
+ >
1467
+ <box
1468
+ paddingLeft={2}
1469
+ paddingRight={2}
1470
+ paddingTop={1}
1471
+ flexShrink={0}
1472
+ backgroundColor={theme.backgroundElement}
1473
+ flexGrow={1}
1474
+ >
1475
+ <textarea
1476
+ placeholder={placeholderText()}
1477
+ placeholderColor={theme.textMuted}
1478
+ textColor={keybind.leader ? theme.textMuted : theme.text}
1479
+ focusedTextColor={keybind.leader ? theme.textMuted : theme.text}
1480
+ minHeight={1}
1481
+ maxHeight={6}
1482
+ onContentChange={() => {
1483
+ const value = input.plainText
1484
+ if (value !== "" && ghost()) setGhost("")
1485
+ setStore("prompt", "input", value)
1486
+ autocomplete.onInput(value)
1487
+ syncExtmarksWithPromptParts()
1488
+ }}
1489
+ keyBindings={textareaKeybindings()}
1490
+ onKeyDown={async (e) => {
1491
+ if (props.disabled) {
1492
+ e.preventDefault()
1493
+ return
1494
+ }
1495
+ // Check clipboard for images before terminal-handled paste runs.
1496
+ // This helps terminals that forward Ctrl+V to the app; Windows
1497
+ // Terminal 1.25+ usually handles Ctrl+V before this path.
1498
+ if (keybind.match("input_paste", e)) {
1499
+ const content = await Clipboard.read()
1500
+ if (content?.mime.startsWith("image/")) {
1501
+ e.preventDefault()
1502
+ await pasteAttachment({
1503
+ filename: "clipboard",
1504
+ mime: content.mime,
1505
+ content: content.data,
1506
+ })
1507
+ return
1508
+ }
1509
+ // If no image, let the default paste behavior continue
1510
+ }
1511
+ if (keybind.match("input_clear", e) && store.prompt.input !== "") {
1512
+ input.clear()
1513
+ input.extmarks.clear()
1514
+ setStore("prompt", {
1515
+ input: "",
1516
+ parts: [],
1517
+ })
1518
+ setStore("extmarkToPartIndex", new Map())
1519
+ return
1520
+ }
1521
+ if (keybind.match("app_exit", e)) {
1522
+ if (store.prompt.input === "") {
1523
+ if (props.sessionID && status().type !== "idle") {
1524
+ void sdk.client.session.abort({ sessionID: props.sessionID })
1525
+ e.preventDefault()
1526
+ return
1527
+ }
1528
+ await exit()
1529
+ // Don't preventDefault - let textarea potentially handle the event
1530
+ e.preventDefault()
1531
+ return
1532
+ }
1533
+ }
1534
+ if (e.name === "!" && input.visualCursor.offset === 0) {
1535
+ setStore("placeholder", randomIndex(shell().length))
1536
+ setStore("mode", "shell")
1537
+ e.preventDefault()
1538
+ return
1539
+ }
1540
+ if (store.mode === "shell") {
1541
+ if ((e.name === "backspace" && input.visualCursor.offset === 0) || e.name === "escape") {
1542
+ setStore("mode", "normal")
1543
+ e.preventDefault()
1544
+ return
1545
+ }
1546
+ }
1547
+ if (ghost() && store.mode === "normal" && !autocomplete.visible && input.plainText === "") {
1548
+ if (e.name === "tab") {
1549
+ const text = ghost()
1550
+ setGhost("")
1551
+ input.setText(text)
1552
+ setStore("prompt", "input", text)
1553
+ input.gotoBufferEnd()
1554
+ e.preventDefault()
1555
+ return
1556
+ }
1557
+ if (e.name === "escape") {
1558
+ setGhost("")
1559
+ e.preventDefault()
1560
+ return
1561
+ }
1562
+ }
1563
+ if (store.mode === "normal") autocomplete.onKeyDown(e)
1564
+ if (!autocomplete.visible) {
1565
+ if (
1566
+ (keybind.match("history_previous", e) && input.cursorOffset === 0) ||
1567
+ (keybind.match("history_next", e) && input.cursorOffset === input.plainText.length)
1568
+ ) {
1569
+ const direction = keybind.match("history_previous", e) ? -1 : 1
1570
+ const item = history.move(direction, input.plainText)
1571
+
1572
+ if (item) {
1573
+ input.setText(item.input)
1574
+ setStore("prompt", item)
1575
+ setStore("mode", item.mode ?? "normal")
1576
+ restoreExtmarksFromParts(item.parts)
1577
+ e.preventDefault()
1578
+ if (direction === -1) input.cursorOffset = 0
1579
+ if (direction === 1) input.cursorOffset = input.plainText.length
1580
+ }
1581
+ return
1582
+ }
1583
+
1584
+ if (keybind.match("history_previous", e) && input.visualCursor.visualRow === 0) input.cursorOffset = 0
1585
+ if (keybind.match("history_next", e) && input.visualCursor.visualRow === input.height - 1)
1586
+ input.cursorOffset = input.plainText.length
1587
+ }
1588
+ }}
1589
+ onSubmit={() => {
1590
+ // IME: double-defer so the last composed character (e.g. Korean
1591
+ // hangul) is flushed to plainText before we read it for submission.
1592
+ setTimeout(() => setTimeout(() => submit(), 0), 0)
1593
+ }}
1594
+ onPaste={async (event: PasteEvent) => {
1595
+ if (props.disabled) {
1596
+ event.preventDefault()
1597
+ return
1598
+ }
1599
+
1600
+ // Normalize line endings at the boundary
1601
+ // Windows ConPTY/Terminal often sends CR-only newlines in bracketed paste
1602
+ // Replace CRLF first, then any remaining CR
1603
+ const normalizedText = decodePasteBytes(event.bytes).replace(/\r\n/g, "\n").replace(/\r/g, "\n")
1604
+
1605
+ // Windows Terminal <1.25 can surface image-only clipboard as an
1606
+ // empty bracketed paste. Windows Terminal 1.25+ does not.
1607
+ if (!normalizedText.trim()) {
1608
+ command.trigger("prompt.paste")
1609
+ return
1610
+ }
1611
+
1612
+ // Once we cross an async boundary below, the terminal may perform its
1613
+ // default paste unless we suppress it first and handle insertion ourselves.
1614
+ event.preventDefault()
1615
+ await pastePlainText(normalizedText)
1616
+ }}
1617
+ ref={(r: TextareaRenderable) => {
1618
+ input = r
1619
+ if (promptPartTypeId === 0) {
1620
+ promptPartTypeId = input.extmarks.registerType("prompt-part")
1621
+ }
1622
+ props.ref?.(ref)
1623
+ setTimeout(() => {
1624
+ // setTimeout is a workaround and needs to be addressed properly
1625
+ if (!input || input.isDestroyed) return
1626
+ input.cursorColor = theme.text
1627
+ }, 0)
1628
+ }}
1629
+ onMouseDown={(r: MouseEvent) => r.target?.focus()}
1630
+ focusedBackgroundColor={theme.backgroundElement}
1631
+ cursorColor={theme.text}
1632
+ syntaxStyle={syntax()}
1633
+ />
1634
+ <box flexDirection="row" flexShrink={0} paddingTop={1} gap={1} justifyContent="space-between">
1635
+ <box flexDirection="row" gap={1}>
1636
+ <Show when={local.agent.current()} fallback={<box height={1} />}>
1637
+ {(agent) => (
1638
+ <>
1639
+ <text fg={fadeColor(highlight(), agentMetaAlpha())}>
1640
+ {store.mode === "shell" ? "Shell" : Locale.titlecase(agent().name)}
1641
+ </text>
1642
+ <Show when={store.mode === "normal"}>
1643
+ <box flexDirection="row" gap={1}>
1644
+ <text fg={fadeColor(theme.textMuted, modelMetaAlpha())}>·</text>
1645
+ <text
1646
+ flexShrink={0}
1647
+ fg={fadeColor(keybind.leader ? theme.textMuted : theme.text, modelMetaAlpha())}
1648
+ >
1649
+ {local.model.parsed().model}
1650
+ </text>
1651
+ <Show when={showVariant()}>
1652
+ <text fg={fadeColor(theme.textMuted, variantMetaAlpha())}>·</text>
1653
+ <text>
1654
+ <span style={{ fg: fadeColor(theme.warning, variantMetaAlpha()), bold: true }}>
1655
+ {local.model.variant.current()}
1656
+ </span>
1657
+ </text>
1658
+ </Show>
1659
+ </box>
1660
+ </Show>
1661
+ </>
1662
+ )}
1663
+ </Show>
1664
+ <Show when={local.neverAsk.current()}>
1665
+ <text>
1666
+ <span style={{ fg: theme.error, bold: true }}>«never-ask»</span>
1667
+ </text>
1668
+ </Show>
1669
+ </box>
1670
+ <box flexDirection="row" gap={1} alignItems="center">
1671
+ <Show when={hasRightContent()}>
1672
+ {props.right}
1673
+ </Show>
1674
+ <Show when={voiceEnabled()}>
1675
+ <Switch>
1676
+ <Match when={voiceState() === "idle"}>
1677
+ <text fg={theme.textMuted} selectable={false} onMouseUp={() => voiceToggle()}>
1678
+ {"[ 🎙 Voice ]"}
1679
+ </text>
1680
+ </Match>
1681
+ <Match when={voiceState() === "listening"}>
1682
+ <text fg={theme.primary} selectable={false} onMouseUp={() => voiceToggle()}>
1683
+ {"[ 🎙 -:-- ]"}
1684
+ </text>
1685
+ </Match>
1686
+ <Match when={voiceState() === "speaking"}>
1687
+ <text fg={theme.primary} selectable={false} onMouseUp={() => voiceToggle()}>
1688
+ {`[ 🎙 ${Math.floor(voiceElapsed() / 60)}:${String(voiceElapsed() % 60).padStart(2, "0")} ]`}
1689
+ </text>
1690
+ </Match>
1691
+ <Match when={voiceState() === "processing"}>
1692
+ <text fg={theme.primary} selectable={false} onMouseUp={() => voiceToggle()}>
1693
+ {"[ 🎙 .... ]"}
1694
+ </text>
1695
+ </Match>
1696
+ <Match when={voiceState() === "finishing"}>
1697
+ <text fg={theme.textMuted} selectable={false}>{"[ 🎙 .... ]"}</text>
1698
+ </Match>
1699
+ </Switch>
1700
+ </Show>
1701
+ </box>
1702
+ </box>
1703
+ </box>
1704
+ </box>
1705
+ <box
1706
+ height={1}
1707
+ border={["left"]}
1708
+ borderColor={borderHighlight()}
1709
+ customBorderChars={{
1710
+ ...EmptyBorder,
1711
+ vertical: theme.backgroundElement.a !== 0 ? "╹" : " ",
1712
+ }}
1713
+ >
1714
+ <box
1715
+ height={1}
1716
+ border={["bottom"]}
1717
+ borderColor={theme.backgroundElement}
1718
+ customBorderChars={
1719
+ theme.backgroundElement.a !== 0
1720
+ ? {
1721
+ ...EmptyBorder,
1722
+ horizontal: "▀",
1723
+ }
1724
+ : {
1725
+ ...EmptyBorder,
1726
+ horizontal: " ",
1727
+ }
1728
+ }
1729
+ />
1730
+ </box>
1731
+ <box width="100%" flexDirection="row" justifyContent="space-between">
1732
+ <Show when={status().type !== "idle"} fallback={props.hint ?? <text />}>
1733
+ <box
1734
+ flexDirection="row"
1735
+ gap={1}
1736
+ flexGrow={1}
1737
+ justifyContent={status().type === "retry" ? "space-between" : "flex-start"}
1738
+ >
1739
+ <box flexShrink={0} flexDirection="row" gap={1}>
1740
+ <box marginLeft={1}>
1741
+ <Show when={kv.get("animations_enabled", true)} fallback={<text fg={theme.textMuted}>[⋯]</text>}>
1742
+ <spinner color={spinnerDef().color} frames={spinnerDef().frames} interval={40} />
1743
+ </Show>
1744
+ </box>
1745
+ {(() => {
1746
+ const busyMessage = createMemo(() => {
1747
+ const s = status()
1748
+ return s.type === "busy" ? s.message : undefined
1749
+ })
1750
+ return (
1751
+ <Show when={busyMessage()}>
1752
+ <text fg={theme.textMuted}>{busyMessage()}</text>
1753
+ </Show>
1754
+ )
1755
+ })()}
1756
+ <box flexDirection="row" gap={1} flexShrink={0}>
1757
+ {(() => {
1758
+ const retry = createMemo(() => {
1759
+ const s = status()
1760
+ if (s.type !== "retry") return
1761
+ return s
1762
+ })
1763
+ const message = createMemo(() => {
1764
+ const r = retry()
1765
+ if (!r) return
1766
+ if (r.message.includes("exceeded your current quota") && r.message.includes("gemini"))
1767
+ return "gemini is way too hot right now"
1768
+ if (r.message.length > 80) return r.message.slice(0, 80) + "..."
1769
+ return r.message
1770
+ })
1771
+ const isTruncated = createMemo(() => {
1772
+ const r = retry()
1773
+ if (!r) return false
1774
+ return r.message.length > 120
1775
+ })
1776
+ const [seconds, setSeconds] = createSignal(0)
1777
+ onMount(() => {
1778
+ const timer = setInterval(() => {
1779
+ const next = retry()?.next
1780
+ if (next) setSeconds(Math.round((next - Date.now()) / 1000))
1781
+ }, 1000)
1782
+
1783
+ onCleanup(() => {
1784
+ clearInterval(timer)
1785
+ })
1786
+ })
1787
+ const handleMessageClick = () => {
1788
+ const r = retry()
1789
+ if (!r) return
1790
+ if (isTruncated()) {
1791
+ void DialogAlert.show(dialog, "Retry Error", r.message)
1792
+ }
1793
+ }
1794
+
1795
+ const retryText = () => {
1796
+ const r = retry()
1797
+ if (!r) return ""
1798
+ const baseMessage = message()
1799
+ const truncatedHint = isTruncated() ? " (click to expand)" : ""
1800
+ const duration = formatDuration(seconds())
1801
+ const retryInfo = ` [retrying ${duration ? `in ${duration} ` : ""}attempt #${r.attempt}]`
1802
+ return baseMessage + truncatedHint + retryInfo
1803
+ }
1804
+
1805
+ return (
1806
+ <Show when={retry()}>
1807
+ <box onMouseUp={handleMessageClick}>
1808
+ <text fg={theme.error}>{retryText()}</text>
1809
+ </box>
1810
+ </Show>
1811
+ )
1812
+ })()}
1813
+ </box>
1814
+ </box>
1815
+ <text fg={store.interrupt > 0 ? theme.primary : theme.text}>
1816
+ esc{" "}
1817
+ <span style={{ fg: store.interrupt > 0 ? theme.primary : theme.textMuted }}>
1818
+ {store.interrupt > 0 ? "again to interrupt" : "interrupt"}
1819
+ </span>
1820
+ </text>
1821
+ </box>
1822
+ </Show>
1823
+ <Show when={status().type !== "retry"}>
1824
+ <box gap={2} flexGrow={1} flexDirection="row" justifyContent="space-between">
1825
+ <Switch>
1826
+ <Match when={store.mode === "normal"}>
1827
+ <box gap={2} flexDirection="row">
1828
+ <Show when={usage()}>
1829
+ {(item) => (
1830
+ <text fg={theme.textMuted} wrapMode="none">
1831
+ {[item().context, item().cost].filter(Boolean).join(" · ")}
1832
+ </text>
1833
+ )}
1834
+ </Show>
1835
+ <text fg={theme.text}>
1836
+ {keybind.print("agent_cycle")} <span style={{ fg: theme.textMuted }}>{t("tui.prompt.hint.switch_mode")}</span>
1837
+ </text>
1838
+ <text fg={theme.text}>
1839
+ {keybind.print("command_list")}{" "}
1840
+ <span style={{ fg: theme.textMuted }}>{t("tui.prompt.hint.settings")}</span>
1841
+ </text>
1842
+ </box>
1843
+ <Show when={status().type === "idle"}>
1844
+ <box gap={2} flexDirection="row">
1845
+ <text fg={theme.text}>
1846
+ @ <span style={{ fg: theme.textMuted }}>{t("tui.prompt.hint.attach_file")}</span>
1847
+ </text>
1848
+ <text fg={theme.text}>
1849
+ $ <span style={{ fg: theme.textMuted }}>{t("tui.prompt.hint.subagent")}</span>
1850
+ </text>
1851
+ <text fg={theme.text}>
1852
+ / <span style={{ fg: theme.textMuted }}>{t("tui.prompt.hint.commands")}</span>
1853
+ </text>
1854
+ </box>
1855
+ </Show>
1856
+ </Match>
1857
+ <Match when={store.mode === "shell"}>
1858
+ <box flexGrow={1} flexDirection="row" justifyContent="flex-end">
1859
+ <text fg={theme.text}>
1860
+ esc <span style={{ fg: theme.textMuted }}>exit shell mode</span>
1861
+ </text>
1862
+ </box>
1863
+ </Match>
1864
+ </Switch>
1865
+ </box>
1866
+ </Show>
1867
+ </box>
1868
+ </box>
1869
+ </>
1870
+ )
1871
+ }