@mty-coder/cli 0.1.1

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