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