@sleepy-ai/cli 0.1.16 → 0.1.17

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 (805) hide show
  1. package/{src/skill/compose/LICENSE-superpowers → LICENSE} +3 -6
  2. package/README.md +139 -6
  3. package/package.json +35 -199
  4. package/postinstall.mjs +102 -0
  5. package/.sleepycode-test-fixtures-50158/sleepycode-test-eqsxv84ythu/.watcher-b67v64k2mnv +0 -1
  6. package/.sleepycode-test-fixtures-66172/sleepycode-test-x1ymo4kksaa/sleepycode.json +0 -1
  7. package/Dockerfile +0 -18
  8. package/drizzle.config.ts +0 -10
  9. package/git +0 -0
  10. package/migration/20260127222353_familiar_lady_ursula/migration.sql +0 -90
  11. package/migration/20260127222353_familiar_lady_ursula/snapshot.json +0 -796
  12. package/migration/20260211171708_add_project_commands/migration.sql +0 -1
  13. package/migration/20260211171708_add_project_commands/snapshot.json +0 -806
  14. package/migration/20260213144116_wakeful_the_professor/migration.sql +0 -11
  15. package/migration/20260213144116_wakeful_the_professor/snapshot.json +0 -897
  16. package/migration/20260225215848_workspace/migration.sql +0 -7
  17. package/migration/20260225215848_workspace/snapshot.json +0 -959
  18. package/migration/20260227213759_add_session_workspace_id/migration.sql +0 -2
  19. package/migration/20260227213759_add_session_workspace_id/snapshot.json +0 -983
  20. package/migration/20260228203230_blue_harpoon/migration.sql +0 -17
  21. package/migration/20260228203230_blue_harpoon/snapshot.json +0 -1102
  22. package/migration/20260303231226_add_workspace_fields/migration.sql +0 -5
  23. package/migration/20260303231226_add_workspace_fields/snapshot.json +0 -1013
  24. package/migration/20260309230000_move_org_to_state/migration.sql +0 -3
  25. package/migration/20260309230000_move_org_to_state/snapshot.json +0 -1156
  26. package/migration/20260312043431_session_message_cursor/migration.sql +0 -4
  27. package/migration/20260312043431_session_message_cursor/snapshot.json +0 -1168
  28. package/migration/20260323234822_events/migration.sql +0 -13
  29. package/migration/20260323234822_events/snapshot.json +0 -1271
  30. package/migration/20260410174513_workspace-name/migration.sql +0 -16
  31. package/migration/20260410174513_workspace-name/snapshot.json +0 -1271
  32. package/migration/20260413175956_chief_energizer/migration.sql +0 -13
  33. package/migration/20260413175956_chief_energizer/snapshot.json +0 -1399
  34. package/migration/20260422160000_context_inheritance/migration.sql +0 -3
  35. package/migration/20260422170000_task_registry/migration.sql +0 -18
  36. package/migration/20260423145421_remove_session_entry/migration.sql +0 -4
  37. package/migration/20260515000000_actor_rename/migration.sql +0 -7
  38. package/migration/20260515010000_memory_fts/migration.sql +0 -33
  39. package/migration/20260515020000_user_task/migration.sql +0 -29
  40. package/migration/20260519000000_last_checkpoint_message_id/migration.sql +0 -1
  41. package/migration/20260521000000_message_agent_id/migration.sql +0 -2
  42. package/migration/20260521000100_actor_registry_v6/migration.sql +0 -25
  43. package/migration/20260521010000_memory_fts_v6/migration.sql +0 -33
  44. package/migration/20260521020000_memory_fts_triggers/migration.sql +0 -17
  45. package/migration/20260526000000_agent_id_main/migration.sql +0 -14
  46. package/migration/20260527000000_actor_lifecycle/migration.sql +0 -8
  47. package/migration/20260527000100_inbox/migration.sql +0 -12
  48. package/migration/20260529000000_task_todo_redesign/migration.sql +0 -16
  49. package/migration/20260603000000_task_in_progress_owner/migration.sql +0 -1
  50. package/migration/20260603000000_workflow_run/migration.sql +0 -17
  51. package/migration/20260604000000_workflow_script_sha/migration.sql +0 -1
  52. package/migration/20260608000000_claude_import/migration.sql +0 -7
  53. package/migration/20260608010000_claude_import_message_ids/migration.sql +0 -1
  54. package/migration/20260609000000_history_fts/migration.sql +0 -29
  55. package/migration/20260609230000_workflow_agent_timeout/migration.sql +0 -1
  56. package/migration/20260612000000_external_import/migration.sql +0 -16
  57. package/parsers-config.ts +0 -290
  58. package/src/account/account.sql.ts +0 -39
  59. package/src/account/account.ts +0 -456
  60. package/src/account/repo.ts +0 -166
  61. package/src/account/schema.ts +0 -99
  62. package/src/account/url.ts +0 -8
  63. package/src/acp/README.md +0 -174
  64. package/src/acp/agent.ts +0 -1787
  65. package/src/acp/session.ts +0 -116
  66. package/src/acp/types.ts +0 -24
  67. package/src/actor/actor.sql.ts +0 -38
  68. package/src/actor/events.ts +0 -67
  69. package/src/actor/index.ts +0 -2
  70. package/src/actor/registry.ts +0 -412
  71. package/src/actor/return-header.ts +0 -24
  72. package/src/actor/schema.ts +0 -47
  73. package/src/actor/spawn-ref.ts +0 -16
  74. package/src/actor/spawn.ts +0 -756
  75. package/src/actor/turn.ts +0 -49
  76. package/src/actor/waiter.ts +0 -166
  77. package/src/agent/agent.ts +0 -645
  78. package/src/agent/config.ts +0 -5
  79. package/src/agent/generate.txt +0 -75
  80. package/src/agent/prompt/apex.txt +0 -363
  81. package/src/agent/prompt/checkpoint-writer.txt +0 -167
  82. package/src/agent/prompt/compaction.txt +0 -9
  83. package/src/agent/prompt/distill.txt +0 -199
  84. package/src/agent/prompt/dream.txt +0 -155
  85. package/src/agent/prompt/explore.txt +0 -18
  86. package/src/agent/prompt/forge.txt +0 -491
  87. package/src/agent/prompt/phantom.txt +0 -491
  88. package/src/agent/prompt/pivot.txt +0 -460
  89. package/src/agent/prompt/stack.txt +0 -472
  90. package/src/agent/prompt/summary.txt +0 -11
  91. package/src/agent/prompt/title.txt +0 -44
  92. package/src/audio.d.ts +0 -9
  93. package/src/auth/index.ts +0 -97
  94. package/src/bus/bus-event.ts +0 -33
  95. package/src/bus/global.ts +0 -12
  96. package/src/bus/index.ts +0 -193
  97. package/src/cli/bootstrap.ts +0 -33
  98. package/src/cli/cmd/account.ts +0 -258
  99. package/src/cli/cmd/acp.ts +0 -70
  100. package/src/cli/cmd/agent.ts +0 -248
  101. package/src/cli/cmd/cmd.ts +0 -7
  102. package/src/cli/cmd/db.ts +0 -120
  103. package/src/cli/cmd/debug/agent.ts +0 -192
  104. package/src/cli/cmd/debug/config.ts +0 -17
  105. package/src/cli/cmd/debug/file.ts +0 -100
  106. package/src/cli/cmd/debug/index.ts +0 -48
  107. package/src/cli/cmd/debug/lsp.ts +0 -61
  108. package/src/cli/cmd/debug/ripgrep.ts +0 -105
  109. package/src/cli/cmd/debug/scrap.ts +0 -16
  110. package/src/cli/cmd/debug/skill.ts +0 -23
  111. package/src/cli/cmd/debug/snapshot.ts +0 -53
  112. package/src/cli/cmd/doctor.ts +0 -116
  113. package/src/cli/cmd/export.ts +0 -306
  114. package/src/cli/cmd/generate.ts +0 -50
  115. package/src/cli/cmd/github.ts +0 -1647
  116. package/src/cli/cmd/import.ts +0 -208
  117. package/src/cli/cmd/init.ts +0 -97
  118. package/src/cli/cmd/login.ts +0 -402
  119. package/src/cli/cmd/mcp.ts +0 -812
  120. package/src/cli/cmd/models.ts +0 -88
  121. package/src/cli/cmd/plug.ts +0 -233
  122. package/src/cli/cmd/pr.ts +0 -138
  123. package/src/cli/cmd/providers.ts +0 -713
  124. package/src/cli/cmd/run-completion.ts +0 -77
  125. package/src/cli/cmd/run.ts +0 -694
  126. package/src/cli/cmd/serve.ts +0 -30
  127. package/src/cli/cmd/session.ts +0 -181
  128. package/src/cli/cmd/stats.ts +0 -413
  129. package/src/cli/cmd/tui/app.tsx +0 -1161
  130. package/src/cli/cmd/tui/asset/TEN_VAD_LICENSE +0 -12
  131. package/src/cli/cmd/tui/asset/charge.wav +0 -0
  132. package/src/cli/cmd/tui/asset/pulse-a.wav +0 -0
  133. package/src/cli/cmd/tui/asset/pulse-b.wav +0 -0
  134. package/src/cli/cmd/tui/asset/pulse-c.wav +0 -0
  135. package/src/cli/cmd/tui/asset/ten_vad.wasm +0 -0
  136. package/src/cli/cmd/tui/asset/ten_vad_loader.js +0 -30
  137. package/src/cli/cmd/tui/attach.ts +0 -84
  138. package/src/cli/cmd/tui/component/background-image.tsx +0 -150
  139. package/src/cli/cmd/tui/component/banner-session-expired.tsx +0 -48
  140. package/src/cli/cmd/tui/component/bg-pulse.tsx +0 -130
  141. package/src/cli/cmd/tui/component/border.tsx +0 -21
  142. package/src/cli/cmd/tui/component/dialog-agent.tsx +0 -31
  143. package/src/cli/cmd/tui/component/dialog-agreement.tsx +0 -111
  144. package/src/cli/cmd/tui/component/dialog-command.tsx +0 -219
  145. package/src/cli/cmd/tui/component/dialog-console-org.tsx +0 -103
  146. package/src/cli/cmd/tui/component/dialog-go-upsell.tsx +0 -150
  147. package/src/cli/cmd/tui/component/dialog-image-list.tsx +0 -111
  148. package/src/cli/cmd/tui/component/dialog-logo-design.tsx +0 -37
  149. package/src/cli/cmd/tui/component/dialog-mcp.tsx +0 -86
  150. package/src/cli/cmd/tui/component/dialog-model.tsx +0 -299
  151. package/src/cli/cmd/tui/component/dialog-provider.tsx +0 -449
  152. package/src/cli/cmd/tui/component/dialog-session-delete-failed.tsx +0 -101
  153. package/src/cli/cmd/tui/component/dialog-session-list.tsx +0 -269
  154. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +0 -31
  155. package/src/cli/cmd/tui/component/dialog-skill.tsx +0 -42
  156. package/src/cli/cmd/tui/component/dialog-sleepy-login.tsx +0 -167
  157. package/src/cli/cmd/tui/component/dialog-stash.tsx +0 -87
  158. package/src/cli/cmd/tui/component/dialog-status.tsx +0 -170
  159. package/src/cli/cmd/tui/component/dialog-tag.tsx +0 -44
  160. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +0 -50
  161. package/src/cli/cmd/tui/component/dialog-token-plan.tsx +0 -77
  162. package/src/cli/cmd/tui/component/dialog-variant.tsx +0 -39
  163. package/src/cli/cmd/tui/component/dialog-workflows.tsx +0 -51
  164. package/src/cli/cmd/tui/component/dialog-workspace-create.tsx +0 -289
  165. package/src/cli/cmd/tui/component/dialog-workspace-unavailable.tsx +0 -81
  166. package/src/cli/cmd/tui/component/dialog-worktree.tsx +0 -90
  167. package/src/cli/cmd/tui/component/error-component.tsx +0 -92
  168. package/src/cli/cmd/tui/component/logo.tsx +0 -961
  169. package/src/cli/cmd/tui/component/plugin-route-missing.tsx +0 -14
  170. package/src/cli/cmd/tui/component/prompt/autocomplete-detect.ts +0 -34
  171. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +0 -668
  172. package/src/cli/cmd/tui/component/prompt/cwd.ts +0 -0
  173. package/src/cli/cmd/tui/component/prompt/frecency.tsx +0 -90
  174. package/src/cli/cmd/tui/component/prompt/history.tsx +0 -108
  175. package/src/cli/cmd/tui/component/prompt/index.tsx +0 -1869
  176. package/src/cli/cmd/tui/component/prompt/offset.ts +0 -45
  177. package/src/cli/cmd/tui/component/prompt/part.ts +0 -36
  178. package/src/cli/cmd/tui/component/prompt/stash.tsx +0 -101
  179. package/src/cli/cmd/tui/component/spinner.tsx +0 -24
  180. package/src/cli/cmd/tui/component/starry-background.tsx +0 -305
  181. package/src/cli/cmd/tui/component/startup-loading.tsx +0 -67
  182. package/src/cli/cmd/tui/component/task-item.tsx +0 -63
  183. package/src/cli/cmd/tui/component/textarea-keybindings.ts +0 -73
  184. package/src/cli/cmd/tui/component/todo-item.tsx +0 -32
  185. package/src/cli/cmd/tui/component/workflow-tree.tsx +0 -177
  186. package/src/cli/cmd/tui/config/cwd.ts +0 -5
  187. package/src/cli/cmd/tui/config/tui-migrate.ts +0 -151
  188. package/src/cli/cmd/tui/config/tui-schema.ts +0 -38
  189. package/src/cli/cmd/tui/config/tui.ts +0 -219
  190. package/src/cli/cmd/tui/context/args.tsx +0 -16
  191. package/src/cli/cmd/tui/context/directory.ts +0 -15
  192. package/src/cli/cmd/tui/context/event.ts +0 -45
  193. package/src/cli/cmd/tui/context/exit.tsx +0 -65
  194. package/src/cli/cmd/tui/context/helper.tsx +0 -25
  195. package/src/cli/cmd/tui/context/keybind.tsx +0 -105
  196. package/src/cli/cmd/tui/context/kv.tsx +0 -86
  197. package/src/cli/cmd/tui/context/language.tsx +0 -91
  198. package/src/cli/cmd/tui/context/local.tsx +0 -458
  199. package/src/cli/cmd/tui/context/plugin-keybinds.ts +0 -41
  200. package/src/cli/cmd/tui/context/project.tsx +0 -109
  201. package/src/cli/cmd/tui/context/prompt.tsx +0 -18
  202. package/src/cli/cmd/tui/context/route.tsx +0 -68
  203. package/src/cli/cmd/tui/context/sdk.tsx +0 -150
  204. package/src/cli/cmd/tui/context/sync.tsx +0 -884
  205. package/src/cli/cmd/tui/context/theme/aura.json +0 -69
  206. package/src/cli/cmd/tui/context/theme/ayu.json +0 -80
  207. package/src/cli/cmd/tui/context/theme/carbonfox.json +0 -248
  208. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +0 -230
  209. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +0 -230
  210. package/src/cli/cmd/tui/context/theme/catppuccin.json +0 -112
  211. package/src/cli/cmd/tui/context/theme/cobalt2.json +0 -225
  212. package/src/cli/cmd/tui/context/theme/cursor.json +0 -249
  213. package/src/cli/cmd/tui/context/theme/dracula.json +0 -219
  214. package/src/cli/cmd/tui/context/theme/everforest.json +0 -241
  215. package/src/cli/cmd/tui/context/theme/flexoki.json +0 -237
  216. package/src/cli/cmd/tui/context/theme/github.json +0 -233
  217. package/src/cli/cmd/tui/context/theme/gruvbox.json +0 -242
  218. package/src/cli/cmd/tui/context/theme/kanagawa.json +0 -77
  219. package/src/cli/cmd/tui/context/theme/lucent-orng.json +0 -234
  220. package/src/cli/cmd/tui/context/theme/material.json +0 -235
  221. package/src/cli/cmd/tui/context/theme/matrix.json +0 -77
  222. package/src/cli/cmd/tui/context/theme/mercury.json +0 -252
  223. package/src/cli/cmd/tui/context/theme/monokai.json +0 -221
  224. package/src/cli/cmd/tui/context/theme/nightowl.json +0 -221
  225. package/src/cli/cmd/tui/context/theme/nord.json +0 -223
  226. package/src/cli/cmd/tui/context/theme/one-dark.json +0 -84
  227. package/src/cli/cmd/tui/context/theme/orng.json +0 -249
  228. package/src/cli/cmd/tui/context/theme/osaka-jade.json +0 -93
  229. package/src/cli/cmd/tui/context/theme/palenight.json +0 -222
  230. package/src/cli/cmd/tui/context/theme/rosepine.json +0 -234
  231. package/src/cli/cmd/tui/context/theme/sleepycode.json +0 -245
  232. package/src/cli/cmd/tui/context/theme/solarized.json +0 -223
  233. package/src/cli/cmd/tui/context/theme/synthwave84.json +0 -226
  234. package/src/cli/cmd/tui/context/theme/tokyonight.json +0 -243
  235. package/src/cli/cmd/tui/context/theme/vercel.json +0 -245
  236. package/src/cli/cmd/tui/context/theme/vesper.json +0 -218
  237. package/src/cli/cmd/tui/context/theme/zenburn.json +0 -223
  238. package/src/cli/cmd/tui/context/theme.tsx +0 -1298
  239. package/src/cli/cmd/tui/context/thinking.ts +0 -48
  240. package/src/cli/cmd/tui/context/tui-config.tsx +0 -9
  241. package/src/cli/cmd/tui/event.ts +0 -62
  242. package/src/cli/cmd/tui/feature-plugins/home/footer.tsx +0 -93
  243. package/src/cli/cmd/tui/feature-plugins/home/tips-view.tsx +0 -193
  244. package/src/cli/cmd/tui/feature-plugins/home/tips.tsx +0 -54
  245. package/src/cli/cmd/tui/feature-plugins/sidebar/context.tsx +0 -287
  246. package/src/cli/cmd/tui/feature-plugins/sidebar/cwd.tsx +0 -45
  247. package/src/cli/cmd/tui/feature-plugins/sidebar/files.tsx +0 -62
  248. package/src/cli/cmd/tui/feature-plugins/sidebar/footer.tsx +0 -93
  249. package/src/cli/cmd/tui/feature-plugins/sidebar/goal.tsx +0 -84
  250. package/src/cli/cmd/tui/feature-plugins/sidebar/instructions.tsx +0 -54
  251. package/src/cli/cmd/tui/feature-plugins/sidebar/lsp.tsx +0 -66
  252. package/src/cli/cmd/tui/feature-plugins/sidebar/mascot.tsx +0 -69
  253. package/src/cli/cmd/tui/feature-plugins/sidebar/mcp.tsx +0 -98
  254. package/src/cli/cmd/tui/feature-plugins/sidebar/task.tsx +0 -95
  255. package/src/cli/cmd/tui/feature-plugins/sidebar/todo.tsx +0 -51
  256. package/src/cli/cmd/tui/feature-plugins/sidebar/tps.ts +0 -31
  257. package/src/cli/cmd/tui/feature-plugins/system/plugins.tsx +0 -274
  258. package/src/cli/cmd/tui/i18n/en.ts +0 -454
  259. package/src/cli/cmd/tui/i18n/es.ts +0 -508
  260. package/src/cli/cmd/tui/i18n/fr.ts +0 -515
  261. package/src/cli/cmd/tui/i18n/ja.ts +0 -463
  262. package/src/cli/cmd/tui/i18n/locales.ts +0 -82
  263. package/src/cli/cmd/tui/i18n/ru.ts +0 -527
  264. package/src/cli/cmd/tui/i18n/slash-command.ts +0 -11
  265. package/src/cli/cmd/tui/i18n/zh.ts +0 -454
  266. package/src/cli/cmd/tui/i18n/zht.ts +0 -430
  267. package/src/cli/cmd/tui/layer.ts +0 -6
  268. package/src/cli/cmd/tui/plugin/api.tsx +0 -402
  269. package/src/cli/cmd/tui/plugin/index.ts +0 -3
  270. package/src/cli/cmd/tui/plugin/internal.ts +0 -37
  271. package/src/cli/cmd/tui/plugin/runtime.ts +0 -1057
  272. package/src/cli/cmd/tui/plugin/slots.tsx +0 -60
  273. package/src/cli/cmd/tui/routes/home.tsx +0 -223
  274. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +0 -76
  275. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +0 -116
  276. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +0 -47
  277. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +0 -47
  278. package/src/cli/cmd/tui/routes/session/footer.tsx +0 -91
  279. package/src/cli/cmd/tui/routes/session/index.tsx +0 -3068
  280. package/src/cli/cmd/tui/routes/session/permission.tsx +0 -697
  281. package/src/cli/cmd/tui/routes/session/question.tsx +0 -488
  282. package/src/cli/cmd/tui/routes/session/sidebar.tsx +0 -97
  283. package/src/cli/cmd/tui/routes/session/subagent-footer.tsx +0 -143
  284. package/src/cli/cmd/tui/thread.ts +0 -324
  285. package/src/cli/cmd/tui/ui/dialog-alert.tsx +0 -61
  286. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +0 -95
  287. package/src/cli/cmd/tui/ui/dialog-export-options.tsx +0 -223
  288. package/src/cli/cmd/tui/ui/dialog-help.tsx +0 -42
  289. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +0 -123
  290. package/src/cli/cmd/tui/ui/dialog-select.tsx +0 -467
  291. package/src/cli/cmd/tui/ui/dialog.tsx +0 -207
  292. package/src/cli/cmd/tui/ui/link.tsx +0 -28
  293. package/src/cli/cmd/tui/ui/spinner.ts +0 -378
  294. package/src/cli/cmd/tui/ui/toast.tsx +0 -102
  295. package/src/cli/cmd/tui/util/clipboard.ts +0 -197
  296. package/src/cli/cmd/tui/util/editor.ts +0 -41
  297. package/src/cli/cmd/tui/util/image-protocol.ts +0 -35
  298. package/src/cli/cmd/tui/util/index.ts +0 -6
  299. package/src/cli/cmd/tui/util/model.ts +0 -23
  300. package/src/cli/cmd/tui/util/pinyin.ts +0 -20
  301. package/src/cli/cmd/tui/util/provider-origin.ts +0 -7
  302. package/src/cli/cmd/tui/util/revert-diff.ts +0 -18
  303. package/src/cli/cmd/tui/util/scroll.ts +0 -23
  304. package/src/cli/cmd/tui/util/selection.ts +0 -23
  305. package/src/cli/cmd/tui/util/signal.ts +0 -41
  306. package/src/cli/cmd/tui/util/sound.ts +0 -154
  307. package/src/cli/cmd/tui/util/system-locale.ts +0 -209
  308. package/src/cli/cmd/tui/util/terminal.ts +0 -114
  309. package/src/cli/cmd/tui/util/transcript.ts +0 -112
  310. package/src/cli/cmd/tui/util/vad.ts +0 -229
  311. package/src/cli/cmd/tui/util/voice.ts +0 -450
  312. package/src/cli/cmd/tui/win32.ts +0 -130
  313. package/src/cli/cmd/tui/worker.ts +0 -104
  314. package/src/cli/cmd/uninstall.ts +0 -351
  315. package/src/cli/cmd/upgrade.ts +0 -79
  316. package/src/cli/cmd/web.ts +0 -96
  317. package/src/cli/effect/prompt.ts +0 -25
  318. package/src/cli/error.ts +0 -82
  319. package/src/cli/heap.ts +0 -59
  320. package/src/cli/i18n.ts +0 -15
  321. package/src/cli/logo.ts +0 -53
  322. package/src/cli/network.ts +0 -68
  323. package/src/cli/ui.ts +0 -133
  324. package/src/cli/upgrade.ts +0 -41
  325. package/src/command/index.ts +0 -276
  326. package/src/command/template/initialize.txt +0 -66
  327. package/src/command/template/review.txt +0 -101
  328. package/src/config/agent.ts +0 -197
  329. package/src/config/command.ts +0 -69
  330. package/src/config/compose.ts +0 -26
  331. package/src/config/config.ts +0 -1047
  332. package/src/config/console-state.ts +0 -16
  333. package/src/config/entry-name.ts +0 -16
  334. package/src/config/error.ts +0 -21
  335. package/src/config/formatter.ts +0 -17
  336. package/src/config/history.ts +0 -21
  337. package/src/config/index.ts +0 -17
  338. package/src/config/keybinds.ts +0 -127
  339. package/src/config/layout.ts +0 -10
  340. package/src/config/lsp.ts +0 -45
  341. package/src/config/managed.ts +0 -70
  342. package/src/config/markdown.ts +0 -97
  343. package/src/config/mcp.ts +0 -172
  344. package/src/config/model-id.ts +0 -14
  345. package/src/config/parse.ts +0 -44
  346. package/src/config/paths.ts +0 -85
  347. package/src/config/permission.ts +0 -76
  348. package/src/config/plugin.ts +0 -88
  349. package/src/config/provider.ts +0 -118
  350. package/src/config/server.ts +0 -20
  351. package/src/config/skills.ts +0 -16
  352. package/src/config/variable.ts +0 -90
  353. package/src/control-plane/adaptors/index.ts +0 -52
  354. package/src/control-plane/adaptors/worktree.ts +0 -47
  355. package/src/control-plane/dev/debug-workspace-plugin.ts +0 -73
  356. package/src/control-plane/schema.ts +0 -19
  357. package/src/control-plane/sse.ts +0 -66
  358. package/src/control-plane/types.ts +0 -34
  359. package/src/control-plane/util.ts +0 -37
  360. package/src/control-plane/workspace-context.ts +0 -26
  361. package/src/control-plane/workspace.sql.ts +0 -17
  362. package/src/control-plane/workspace.ts +0 -615
  363. package/src/effect/app-runtime.ts +0 -146
  364. package/src/effect/bootstrap-runtime.ts +0 -33
  365. package/src/effect/bridge.ts +0 -48
  366. package/src/effect/cross-spawn-spawner.ts +0 -514
  367. package/src/effect/index.ts +0 -5
  368. package/src/effect/instance-ref.ts +0 -11
  369. package/src/effect/instance-registry.ts +0 -12
  370. package/src/effect/instance-state.ts +0 -81
  371. package/src/effect/logger.ts +0 -73
  372. package/src/effect/memo-map.ts +0 -3
  373. package/src/effect/observability.ts +0 -107
  374. package/src/effect/run-service.ts +0 -52
  375. package/src/effect/runner.ts +0 -210
  376. package/src/effect/runtime.ts +0 -19
  377. package/src/env/index.ts +0 -37
  378. package/src/file/ignore.ts +0 -81
  379. package/src/file/index.ts +0 -664
  380. package/src/file/protected.ts +0 -59
  381. package/src/file/ripgrep.ts +0 -485
  382. package/src/file/watcher.ts +0 -163
  383. package/src/flag/flag.ts +0 -185
  384. package/src/format/formatter.ts +0 -403
  385. package/src/format/index.ts +0 -203
  386. package/src/git/index.ts +0 -260
  387. package/src/global/index.ts +0 -54
  388. package/src/history/backfill.ts +0 -162
  389. package/src/history/extract.ts +0 -67
  390. package/src/history/fts-query.ts +0 -15
  391. package/src/history/fts.sql.ts +0 -20
  392. package/src/history/index.ts +0 -10
  393. package/src/history/resolve.ts +0 -65
  394. package/src/history/service.ts +0 -258
  395. package/src/history/writer.ts +0 -112
  396. package/src/id/id.ts +0 -87
  397. package/src/ide/index.ts +0 -73
  398. package/src/inbox/inbox-ref.ts +0 -38
  399. package/src/inbox/inbox.sql.ts +0 -26
  400. package/src/inbox/inbox.ts +0 -223
  401. package/src/inbox/index.ts +0 -3
  402. package/src/inbox/render.ts +0 -40
  403. package/src/index.ts +0 -268
  404. package/src/installation/index.ts +0 -362
  405. package/src/installation/version.ts +0 -8
  406. package/src/lsp/client.ts +0 -249
  407. package/src/lsp/diagnostic.ts +0 -29
  408. package/src/lsp/index.ts +0 -3
  409. package/src/lsp/language.ts +0 -120
  410. package/src/lsp/launch.ts +0 -21
  411. package/src/lsp/lsp.ts +0 -519
  412. package/src/lsp/server.ts +0 -1956
  413. package/src/mcp/auth.ts +0 -144
  414. package/src/mcp/index.ts +0 -939
  415. package/src/mcp/oauth-callback.ts +0 -236
  416. package/src/mcp/oauth-provider.ts +0 -214
  417. package/src/memory/fts-query.ts +0 -37
  418. package/src/memory/fts.sql.ts +0 -19
  419. package/src/memory/index.ts +0 -1
  420. package/src/memory/paths.ts +0 -116
  421. package/src/memory/reconcile.ts +0 -144
  422. package/src/memory/service.ts +0 -144
  423. package/src/metrics/client.ts +0 -40
  424. package/src/metrics/event.ts +0 -43
  425. package/src/metrics/index.ts +0 -5
  426. package/src/metrics/installation.ts +0 -18
  427. package/src/metrics/subscriber.ts +0 -58
  428. package/src/metrics/util.ts +0 -9
  429. package/src/node.ts +0 -6
  430. package/src/npm/config.ts +0 -0
  431. package/src/npm/index.ts +0 -293
  432. package/src/npmcli-config.d.ts +0 -43
  433. package/src/patch/index.ts +0 -680
  434. package/src/permission/arity.ts +0 -163
  435. package/src/permission/evaluate.ts +0 -15
  436. package/src/permission/index.ts +0 -382
  437. package/src/permission/schema.ts +0 -17
  438. package/src/plugin/checkpoint-splitover.ts +0 -60
  439. package/src/plugin/cloudflare.ts +0 -76
  440. package/src/plugin/codex.ts +0 -611
  441. package/src/plugin/github-copilot/copilot.ts +0 -368
  442. package/src/plugin/github-copilot/models.ts +0 -153
  443. package/src/plugin/index.ts +0 -637
  444. package/src/plugin/install.ts +0 -439
  445. package/src/plugin/loader.ts +0 -216
  446. package/src/plugin/matcher.ts +0 -33
  447. package/src/plugin/meta.ts +0 -188
  448. package/src/plugin/shared.ts +0 -323
  449. package/src/plugin/sleepy.ts +0 -232
  450. package/src/plugin/subagent-progress-checker.ts +0 -147
  451. package/src/project/bootstrap.ts +0 -59
  452. package/src/project/index.ts +0 -2
  453. package/src/project/instance.ts +0 -215
  454. package/src/project/project-id.ts +0 -48
  455. package/src/project/project.sql.ts +0 -16
  456. package/src/project/project.ts +0 -522
  457. package/src/project/schema.ts +0 -15
  458. package/src/project/vcs.ts +0 -227
  459. package/src/project/workspace-trust.ts +0 -67
  460. package/src/provider/auth.ts +0 -234
  461. package/src/provider/error.ts +0 -216
  462. package/src/provider/index.ts +0 -5
  463. package/src/provider/models-snapshot.d.ts +0 -2
  464. package/src/provider/models-snapshot.js +0 -3
  465. package/src/provider/models.ts +0 -180
  466. package/src/provider/provider.ts +0 -2030
  467. package/src/provider/schema.ts +0 -36
  468. package/src/provider/sdk/copilot/README.md +0 -5
  469. package/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts +0 -170
  470. package/src/provider/sdk/copilot/chat/get-response-metadata.ts +0 -15
  471. package/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts +0 -19
  472. package/src/provider/sdk/copilot/chat/openai-compatible-api-types.ts +0 -64
  473. package/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts +0 -815
  474. package/src/provider/sdk/copilot/chat/openai-compatible-chat-options.ts +0 -28
  475. package/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts +0 -44
  476. package/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts +0 -83
  477. package/src/provider/sdk/copilot/copilot-provider.ts +0 -100
  478. package/src/provider/sdk/copilot/index.ts +0 -2
  479. package/src/provider/sdk/copilot/openai-compatible-error.ts +0 -27
  480. package/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts +0 -335
  481. package/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts +0 -22
  482. package/src/provider/sdk/copilot/responses/openai-config.ts +0 -18
  483. package/src/provider/sdk/copilot/responses/openai-error.ts +0 -22
  484. package/src/provider/sdk/copilot/responses/openai-responses-api-types.ts +0 -214
  485. package/src/provider/sdk/copilot/responses/openai-responses-language-model.ts +0 -1770
  486. package/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts +0 -173
  487. package/src/provider/sdk/copilot/responses/openai-responses-settings.ts +0 -1
  488. package/src/provider/sdk/copilot/responses/tool/code-interpreter.ts +0 -87
  489. package/src/provider/sdk/copilot/responses/tool/file-search.ts +0 -127
  490. package/src/provider/sdk/copilot/responses/tool/image-generation.ts +0 -114
  491. package/src/provider/sdk/copilot/responses/tool/local-shell.ts +0 -64
  492. package/src/provider/sdk/copilot/responses/tool/web-search-preview.ts +0 -103
  493. package/src/provider/sdk/copilot/responses/tool/web-search.ts +0 -102
  494. package/src/provider/session-check.ts +0 -178
  495. package/src/provider/transform.ts +0 -1393
  496. package/src/pty/index.ts +0 -364
  497. package/src/pty/pty.bun.ts +0 -26
  498. package/src/pty/pty.node.ts +0 -27
  499. package/src/pty/pty.ts +0 -25
  500. package/src/pty/schema.ts +0 -17
  501. package/src/question/index.ts +0 -252
  502. package/src/question/schema.ts +0 -17
  503. package/src/server/adapter.bun.ts +0 -40
  504. package/src/server/adapter.node.ts +0 -66
  505. package/src/server/adapter.ts +0 -21
  506. package/src/server/auth.ts +0 -16
  507. package/src/server/error.ts +0 -53
  508. package/src/server/event.ts +0 -7
  509. package/src/server/fence.ts +0 -81
  510. package/src/server/mdns.ts +0 -60
  511. package/src/server/middleware.ts +0 -94
  512. package/src/server/projectors.ts +0 -28
  513. package/src/server/proxy.ts +0 -171
  514. package/src/server/pty-ticket.ts +0 -42
  515. package/src/server/rate-limit.ts +0 -38
  516. package/src/server/routes/control/index.ts +0 -160
  517. package/src/server/routes/control/workspace.ts +0 -203
  518. package/src/server/routes/global.ts +0 -367
  519. package/src/server/routes/instance/bash-interactive.ts +0 -82
  520. package/src/server/routes/instance/config.ts +0 -89
  521. package/src/server/routes/instance/event.ts +0 -108
  522. package/src/server/routes/instance/experimental.ts +0 -408
  523. package/src/server/routes/instance/file.ts +0 -190
  524. package/src/server/routes/instance/httpapi/config.ts +0 -51
  525. package/src/server/routes/instance/httpapi/permission.ts +0 -72
  526. package/src/server/routes/instance/httpapi/project.ts +0 -62
  527. package/src/server/routes/instance/httpapi/provider.ts +0 -142
  528. package/src/server/routes/instance/httpapi/question.ts +0 -121
  529. package/src/server/routes/instance/httpapi/server.ts +0 -153
  530. package/src/server/routes/instance/index.ts +0 -301
  531. package/src/server/routes/instance/mcp.ts +0 -260
  532. package/src/server/routes/instance/middleware.ts +0 -44
  533. package/src/server/routes/instance/permission.ts +0 -73
  534. package/src/server/routes/instance/project.ts +0 -122
  535. package/src/server/routes/instance/provider.ts +0 -158
  536. package/src/server/routes/instance/pty.ts +0 -302
  537. package/src/server/routes/instance/question.ts +0 -162
  538. package/src/server/routes/instance/session.ts +0 -1328
  539. package/src/server/routes/instance/sync.ts +0 -143
  540. package/src/server/routes/instance/trace.ts +0 -59
  541. package/src/server/routes/instance/tui.ts +0 -384
  542. package/src/server/routes/instance/workflows.ts +0 -142
  543. package/src/server/routes/ui.ts +0 -37
  544. package/src/server/server.ts +0 -146
  545. package/src/server/workspace.ts +0 -122
  546. package/src/session/auto-dream.ts +0 -123
  547. package/src/session/boundary.ts +0 -77
  548. package/src/session/budgeted-read.ts +0 -118
  549. package/src/session/checkpoint-align.ts +0 -29
  550. package/src/session/checkpoint-context.ts +0 -36
  551. package/src/session/checkpoint-paths.ts +0 -86
  552. package/src/session/checkpoint-progress-reconcile.ts +0 -111
  553. package/src/session/checkpoint-retry.ts +0 -192
  554. package/src/session/checkpoint-templates.ts +0 -114
  555. package/src/session/checkpoint-validator.ts +0 -259
  556. package/src/session/checkpoint.ts +0 -1560
  557. package/src/session/classify.ts +0 -120
  558. package/src/session/claude-import.ts +0 -381
  559. package/src/session/codex-import.ts +0 -416
  560. package/src/session/compaction.ts +0 -545
  561. package/src/session/external-import.sql.ts +0 -18
  562. package/src/session/external-import.ts +0 -136
  563. package/src/session/goal.ts +0 -232
  564. package/src/session/index.ts +0 -1
  565. package/src/session/instruction.ts +0 -276
  566. package/src/session/last-message-info.ts +0 -32
  567. package/src/session/llm-request-prefix.ts +0 -82
  568. package/src/session/llm.ts +0 -739
  569. package/src/session/max-mode.ts +0 -410
  570. package/src/session/message-v2.ts +0 -1146
  571. package/src/session/message.ts +0 -191
  572. package/src/session/opencode-import.ts +0 -281
  573. package/src/session/overflow.ts +0 -53
  574. package/src/session/prefix-capture-ref.ts +0 -48
  575. package/src/session/processor.ts +0 -983
  576. package/src/session/projectors.ts +0 -137
  577. package/src/session/prompt/anthropic.txt +0 -154
  578. package/src/session/prompt/beast.txt +0 -155
  579. package/src/session/prompt/build-switch.txt +0 -5
  580. package/src/session/prompt/codex.txt +0 -79
  581. package/src/session/prompt/compose.txt +0 -119
  582. package/src/session/prompt/copilot-gpt-5.txt +0 -143
  583. package/src/session/prompt/deepseek.txt +0 -131
  584. package/src/session/prompt/default.old.txt +0 -151
  585. package/src/session/prompt/default.txt +0 -172
  586. package/src/session/prompt/gemini.txt +0 -155
  587. package/src/session/prompt/glm.txt +0 -51
  588. package/src/session/prompt/gpt.txt +0 -107
  589. package/src/session/prompt/kimi.txt +0 -95
  590. package/src/session/prompt/max-steps.txt +0 -16
  591. package/src/session/prompt/minimax.txt +0 -140
  592. package/src/session/prompt/text-loop-recovery.ts +0 -40
  593. package/src/session/prompt/text-ngram-detection.ts +0 -87
  594. package/src/session/prompt/trinity.txt +0 -97
  595. package/src/session/prompt.ts +0 -3879
  596. package/src/session/prune.ts +0 -481
  597. package/src/session/retry.ts +0 -178
  598. package/src/session/revert.ts +0 -161
  599. package/src/session/run-state.ts +0 -135
  600. package/src/session/schema.ts +0 -36
  601. package/src/session/session.sql.ts +0 -110
  602. package/src/session/session.ts +0 -908
  603. package/src/session/status.ts +0 -89
  604. package/src/session/summary.ts +0 -163
  605. package/src/session/system.ts +0 -96
  606. package/src/session/todo.ts +0 -77
  607. package/src/session/trajectory.ts +0 -98
  608. package/src/share/index.ts +0 -2
  609. package/src/share/session.ts +0 -57
  610. package/src/share/share-next.ts +0 -381
  611. package/src/share/share.sql.ts +0 -13
  612. package/src/shell/shell.ts +0 -124
  613. package/src/skill/builtin/.bundle/self-extend/SKILL.md +0 -131
  614. package/src/skill/builtin/.bundle/self-extend/reference/hook-api.md +0 -242
  615. package/src/skill/builtin/.bundle/self-extend/reference/skill-api.md +0 -114
  616. package/src/skill/builtin/.bundle/self-extend/reference/tool-api.md +0 -115
  617. package/src/skill/builtin/.bundle/self-extend/reference/tui-api.md +0 -258
  618. package/src/skill/builtin/bundle.macro.ts +0 -30
  619. package/src/skill/builtin/extract.ts +0 -41
  620. package/src/skill/compose/.bundle/ask/SKILL.md +0 -58
  621. package/src/skill/compose/.bundle/brainstorm/SKILL.md +0 -200
  622. package/src/skill/compose/.bundle/brainstorm/scripts/frame-template.html +0 -214
  623. package/src/skill/compose/.bundle/brainstorm/scripts/helper.js +0 -88
  624. package/src/skill/compose/.bundle/brainstorm/scripts/server.cjs +0 -354
  625. package/src/skill/compose/.bundle/brainstorm/scripts/start-server.sh +0 -148
  626. package/src/skill/compose/.bundle/brainstorm/scripts/stop-server.sh +0 -56
  627. package/src/skill/compose/.bundle/brainstorm/spec-document-reviewer-prompt.md +0 -50
  628. package/src/skill/compose/.bundle/brainstorm/visual-companion.md +0 -258
  629. package/src/skill/compose/.bundle/debug/CREATION-LOG.md +0 -119
  630. package/src/skill/compose/.bundle/debug/SKILL.md +0 -297
  631. package/src/skill/compose/.bundle/debug/condition-based-waiting-example.ts +0 -158
  632. package/src/skill/compose/.bundle/debug/condition-based-waiting.md +0 -106
  633. package/src/skill/compose/.bundle/debug/defense-in-depth.md +0 -122
  634. package/src/skill/compose/.bundle/debug/find-polluter.sh +0 -63
  635. package/src/skill/compose/.bundle/debug/root-cause-tracing.md +0 -144
  636. package/src/skill/compose/.bundle/debug/test-academic.md +0 -14
  637. package/src/skill/compose/.bundle/debug/test-pressure-1.md +0 -58
  638. package/src/skill/compose/.bundle/debug/test-pressure-2.md +0 -68
  639. package/src/skill/compose/.bundle/debug/test-pressure-3.md +0 -69
  640. package/src/skill/compose/.bundle/execute/SKILL.md +0 -71
  641. package/src/skill/compose/.bundle/feedback/SKILL.md +0 -214
  642. package/src/skill/compose/.bundle/merge/SKILL.md +0 -252
  643. package/src/skill/compose/.bundle/new-skill/SKILL.md +0 -211
  644. package/src/skill/compose/.bundle/parallel/SKILL.md +0 -168
  645. package/src/skill/compose/.bundle/plan/SKILL.md +0 -183
  646. package/src/skill/compose/.bundle/plan/plan-document-reviewer-prompt.md +0 -50
  647. package/src/skill/compose/.bundle/report/SKILL.md +0 -180
  648. package/src/skill/compose/.bundle/review/SKILL.md +0 -104
  649. package/src/skill/compose/.bundle/review/code-reviewer.md +0 -178
  650. package/src/skill/compose/.bundle/subagent/SKILL.md +0 -325
  651. package/src/skill/compose/.bundle/subagent/code-quality-reviewer-prompt.md +0 -26
  652. package/src/skill/compose/.bundle/subagent/implementer-prompt.md +0 -128
  653. package/src/skill/compose/.bundle/subagent/spec-reviewer-prompt.md +0 -118
  654. package/src/skill/compose/.bundle/tdd/SKILL.md +0 -360
  655. package/src/skill/compose/.bundle/tdd/testing-anti-patterns.md +0 -299
  656. package/src/skill/compose/.bundle/verify/SKILL.md +0 -140
  657. package/src/skill/compose/.bundle/worktree/SKILL.md +0 -234
  658. package/src/skill/compose/LICENSE-karpathy +0 -28
  659. package/src/skill/compose/bundle.macro.ts +0 -30
  660. package/src/skill/compose/extract.ts +0 -85
  661. package/src/skill/discovery.ts +0 -118
  662. package/src/skill/index.ts +0 -328
  663. package/src/snapshot/index.ts +0 -777
  664. package/src/sql.d.ts +0 -4
  665. package/src/storage/db.bun.ts +0 -8
  666. package/src/storage/db.node.ts +0 -8
  667. package/src/storage/db.ts +0 -172
  668. package/src/storage/index.ts +0 -26
  669. package/src/storage/json-migration.ts +0 -426
  670. package/src/storage/read-sqlite.bun.ts +0 -11
  671. package/src/storage/read-sqlite.node.ts +0 -13
  672. package/src/storage/read-sqlite.ts +0 -10
  673. package/src/storage/schema.sql.ts +0 -10
  674. package/src/storage/schema.ts +0 -7
  675. package/src/storage/storage.ts +0 -331
  676. package/src/sync/README.md +0 -179
  677. package/src/sync/event.sql.ts +0 -16
  678. package/src/sync/index.ts +0 -278
  679. package/src/sync/schema.ts +0 -14
  680. package/src/task/events.ts +0 -28
  681. package/src/task/gate-state.ts +0 -54
  682. package/src/task/gate.ts +0 -116
  683. package/src/task/index.ts +0 -1
  684. package/src/task/registry.ts +0 -394
  685. package/src/task/schema.ts +0 -43
  686. package/src/task/task.sql.ts +0 -50
  687. package/src/team/events.ts +0 -22
  688. package/src/team/index.ts +0 -113
  689. package/src/team/schema.ts +0 -31
  690. package/src/temporary.ts +0 -33
  691. package/src/tool/actor.shell.txt +0 -72
  692. package/src/tool/actor.ts +0 -804
  693. package/src/tool/actor.txt +0 -103
  694. package/src/tool/apply_patch.ts +0 -308
  695. package/src/tool/apply_patch.txt +0 -33
  696. package/src/tool/bash-interactive.ts +0 -183
  697. package/src/tool/bash.ts +0 -704
  698. package/src/tool/bash.txt +0 -123
  699. package/src/tool/change-directory.ts +0 -91
  700. package/src/tool/codesearch.ts +0 -63
  701. package/src/tool/codesearch.txt +0 -12
  702. package/src/tool/edit.ts +0 -693
  703. package/src/tool/edit.txt +0 -10
  704. package/src/tool/external-directory.ts +0 -132
  705. package/src/tool/glob.ts +0 -100
  706. package/src/tool/glob.txt +0 -6
  707. package/src/tool/grep.ts +0 -145
  708. package/src/tool/grep.txt +0 -8
  709. package/src/tool/history.ts +0 -146
  710. package/src/tool/history.txt +0 -17
  711. package/src/tool/index.ts +0 -4
  712. package/src/tool/invalid.ts +0 -20
  713. package/src/tool/invocation-style.ts +0 -17
  714. package/src/tool/lsp.ts +0 -91
  715. package/src/tool/lsp.txt +0 -19
  716. package/src/tool/mcp-exa.ts +0 -78
  717. package/src/tool/memory-path-guard.ts +0 -162
  718. package/src/tool/memory.ts +0 -81
  719. package/src/tool/memory.txt +0 -69
  720. package/src/tool/multiedit.ts +0 -54
  721. package/src/tool/multiedit.txt +0 -41
  722. package/src/tool/notebook-edit.ts +0 -225
  723. package/src/tool/notebook-edit.txt +0 -10
  724. package/src/tool/plan-enter.txt +0 -16
  725. package/src/tool/plan-exit.txt +0 -14
  726. package/src/tool/plan.ts +0 -194
  727. package/src/tool/question.ts +0 -67
  728. package/src/tool/question.txt +0 -10
  729. package/src/tool/read-state.ts +0 -44
  730. package/src/tool/read.ts +0 -327
  731. package/src/tool/read.txt +0 -14
  732. package/src/tool/recoverable.ts +0 -35
  733. package/src/tool/registry.ts +0 -429
  734. package/src/tool/schema.ts +0 -17
  735. package/src/tool/session-cwd.ts +0 -35
  736. package/src/tool/shell-tokenize.ts +0 -374
  737. package/src/tool/shell-wrap.ts +0 -235
  738. package/src/tool/skill.ts +0 -76
  739. package/src/tool/skill.txt +0 -5
  740. package/src/tool/task.shell.txt +0 -57
  741. package/src/tool/task.ts +0 -456
  742. package/src/tool/task.txt +0 -56
  743. package/src/tool/tool.ts +0 -166
  744. package/src/tool/truncate.ts +0 -201
  745. package/src/tool/truncation-dir.ts +0 -4
  746. package/src/tool/webfetch.ts +0 -208
  747. package/src/tool/webfetch.txt +0 -13
  748. package/src/tool/websearch/index.ts +0 -104
  749. package/src/tool/websearch/sleepy.ts +0 -118
  750. package/src/tool/websearch/websearch.txt +0 -14
  751. package/src/tool/workflow.ts +0 -357
  752. package/src/tool/workflow.txt +0 -25
  753. package/src/tool/write.ts +0 -88
  754. package/src/tool/write.txt +0 -10
  755. package/src/util/abort.ts +0 -35
  756. package/src/util/archive.ts +0 -15
  757. package/src/util/color.ts +0 -17
  758. package/src/util/data-url.ts +0 -9
  759. package/src/util/defer.ts +0 -10
  760. package/src/util/effect-http-client.ts +0 -11
  761. package/src/util/effect-zod.ts +0 -367
  762. package/src/util/env-info.ts +0 -62
  763. package/src/util/error.ts +0 -78
  764. package/src/util/filesystem.ts +0 -243
  765. package/src/util/fn.ts +0 -21
  766. package/src/util/format.ts +0 -20
  767. package/src/util/iife.ts +0 -3
  768. package/src/util/index.ts +0 -14
  769. package/src/util/keybind.ts +0 -101
  770. package/src/util/lazy.ts +0 -18
  771. package/src/util/local-context.ts +0 -23
  772. package/src/util/locale.ts +0 -79
  773. package/src/util/lock.ts +0 -96
  774. package/src/util/log.ts +0 -234
  775. package/src/util/media.ts +0 -26
  776. package/src/util/network.ts +0 -9
  777. package/src/util/process.ts +0 -174
  778. package/src/util/provider-priority.ts +0 -48
  779. package/src/util/queue.ts +0 -60
  780. package/src/util/record.ts +0 -3
  781. package/src/util/rpc.ts +0 -64
  782. package/src/util/schema.ts +0 -53
  783. package/src/util/scrap.ts +0 -10
  784. package/src/util/signal.ts +0 -12
  785. package/src/util/sleepy-process.ts +0 -24
  786. package/src/util/ssrf.ts +0 -116
  787. package/src/util/timeout.ts +0 -14
  788. package/src/util/token.ts +0 -5
  789. package/src/util/tool-compat.ts +0 -144
  790. package/src/util/update-schema.ts +0 -13
  791. package/src/util/which.ts +0 -14
  792. package/src/util/wildcard.ts +0 -57
  793. package/src/workflow/builtin/compose.js +0 -749
  794. package/src/workflow/builtin/deep-research.js +0 -397
  795. package/src/workflow/builtin.ts +0 -59
  796. package/src/workflow/events.ts +0 -72
  797. package/src/workflow/meta.ts +0 -335
  798. package/src/workflow/persistence.ts +0 -312
  799. package/src/workflow/resolve.ts +0 -45
  800. package/src/workflow/runtime-ref.ts +0 -18
  801. package/src/workflow/runtime.ts +0 -1447
  802. package/src/workflow/sandbox.ts +0 -286
  803. package/src/workflow/workflow.sql.ts +0 -31
  804. package/src/workflow/workspace.ts +0 -69
  805. package/src/worktree/index.ts +0 -629
@@ -1,3068 +0,0 @@
1
- import {
2
- batch,
3
- createContext,
4
- createEffect,
5
- createMemo,
6
- createSignal,
7
- For,
8
- Match,
9
- on,
10
- onCleanup,
11
- Show,
12
- Switch,
13
- useContext,
14
- } from "solid-js"
15
- import { Dynamic } from "solid-js/web"
16
- import path from "path"
17
- import { useCurrentAgentID, useRoute, useRouteData } from "@tui/context/route"
18
- import { useProject } from "@tui/context/project"
19
- import { useSync } from "@tui/context/sync"
20
- import { useEvent } from "@tui/context/event"
21
- import { SplitBorder } from "@tui/component/border"
22
- import { Spinner } from "@tui/component/spinner"
23
- import { selectedForeground, useTheme } from "@tui/context/theme"
24
- import { BoxRenderable, ScrollBoxRenderable, addDefaultParsers, TextAttributes, RGBA, MouseEvent } from "@opentui/core"
25
- import { Prompt, type PromptRef } from "@tui/component/prompt"
26
- import type {
27
- AssistantMessage,
28
- Part,
29
- Provider,
30
- ToolPart,
31
- UserMessage,
32
- TextPart,
33
- ReasoningPart,
34
- } from "@sleepy-ai/sdk/v2"
35
- import { useLocal } from "@tui/context/local"
36
- import { Locale } from "@/util"
37
- import type { Tool } from "@/tool"
38
- import type { ReadTool } from "@/tool/read"
39
- import type { WriteTool } from "@/tool/write"
40
- import { BashTool } from "@/tool/bash"
41
- import type { GlobTool } from "@/tool/glob"
42
- import type { GrepTool } from "@/tool/grep"
43
- import type { EditTool } from "@/tool/edit"
44
- import type { ApplyPatchTool } from "@/tool/apply_patch"
45
- import type { WebFetchTool } from "@/tool/webfetch"
46
- import type { CodeSearchTool } from "@/tool/codesearch"
47
- import type { WebSearchTool } from "@/tool/websearch"
48
- import type { ActorTool } from "@/tool/actor"
49
- import type { TaskTool } from "@/tool/task"
50
- import type { QuestionTool } from "@/tool/question"
51
- import type { SkillTool } from "@/tool/skill"
52
- import type { WorkflowTool } from "@/tool/workflow"
53
- import { useKeyboard, useRenderer, useTerminalDimensions, type JSX } from "@opentui/solid"
54
- import { useSDK } from "@tui/context/sdk"
55
- import { useCommandDialog } from "@tui/component/dialog-command"
56
- import { useLanguage } from "@tui/context/language"
57
- import type { DialogContext } from "@tui/ui/dialog"
58
- import { useKeybind } from "@tui/context/keybind"
59
- import { useDialog } from "../../ui/dialog"
60
- import { DialogMessage } from "./dialog-message"
61
- import type { PromptInfo } from "../../component/prompt/history"
62
- import { DialogConfirm } from "@tui/ui/dialog-confirm"
63
- import { DialogTimeline } from "./dialog-timeline"
64
- import { DialogForkFromTimeline } from "./dialog-fork-from-timeline"
65
- import { DialogSessionRename } from "../../component/dialog-session-rename"
66
- import { Sidebar } from "./sidebar"
67
- import { WorkflowTree } from "@tui/component/workflow-tree"
68
- import { SubagentFooter } from "./subagent-footer.tsx"
69
- import { DialogSubagent } from "./dialog-subagent.tsx"
70
- import { Flag } from "@/flag/flag"
71
- import { LANGUAGE_EXTENSIONS } from "@/lsp/language"
72
- import parsers from "../../../../../../parsers-config.ts"
73
- import * as Clipboard from "../../util/clipboard"
74
- import { Toast, useToast } from "../../ui/toast"
75
- import { useKV } from "../../context/kv.tsx"
76
- import * as Editor from "../../util/editor"
77
- import stripAnsi from "strip-ansi"
78
- import { usePromptRef } from "../../context/prompt"
79
- import { useExit } from "../../context/exit"
80
- import { Filesystem } from "@/util"
81
- import { Global } from "@/global"
82
- import { PermissionPrompt } from "./permission"
83
- import { QuestionPrompt } from "./question"
84
- import { DialogExportOptions } from "../../ui/dialog-export-options"
85
- import * as Model from "../../util/model"
86
- import { formatTranscript } from "../../util/transcript"
87
- import { UI } from "@/cli/ui.ts"
88
- import { useTuiConfig } from "../../context/tui-config"
89
- import { getScrollAcceleration } from "../../util/scroll"
90
- import { nextThinkingMode, reasoningSummary, useThinkingMode, type ThinkingMode } from "../../context/thinking"
91
- import { TuiPluginRuntime } from "../../plugin"
92
- import { DialogGoUpsell } from "../../component/dialog-go-upsell"
93
- import { DialogTokenPlan } from "../../component/dialog-token-plan"
94
- import { BannerSessionExpired } from "../../component/banner-session-expired"
95
- import { SessionRetry } from "@/session/retry"
96
- import { getRevertDiffFiles } from "../../util/revert-diff"
97
-
98
- addDefaultParsers(parsers.parsers)
99
-
100
- const GO_UPSELL_LAST_SEEN_AT = "go_upsell_last_seen_at"
101
- const GO_UPSELL_DONT_SHOW = "go_upsell_dont_show"
102
- const GO_UPSELL_WINDOW = 86_400_000 // 24 hrs
103
-
104
- const QUEUE_TOKEN_PLAN_LAST_SEEN_AT = "queue_token_plan_last_seen_at"
105
- const QUEUE_TOKEN_PLAN_WINDOW = 86_400_000 // 24 hrs
106
-
107
- const context = createContext<{
108
- width: number
109
- sessionID: string
110
- conceal: () => boolean
111
- thinkingMode: () => ThinkingMode
112
- showThinking: () => boolean
113
- showTimestamps: () => boolean
114
- showDetails: () => boolean
115
- showGenericToolOutput: () => boolean
116
- diffWrapMode: () => "word" | "none"
117
- providers: () => ReadonlyMap<string, Provider>
118
- sync: ReturnType<typeof useSync>
119
- tui: ReturnType<typeof useTuiConfig>
120
- }>()
121
-
122
- function use() {
123
- const ctx = useContext(context)
124
- if (!ctx) throw new Error("useContext must be used within a Session component")
125
- return ctx
126
- }
127
-
128
- export function Session() {
129
- const route = useRouteData("session")
130
- const fullRoute = useRoute()
131
- const navigate = fullRoute.navigate
132
- const sync = useSync()
133
- const event = useEvent()
134
- const project = useProject()
135
- const tuiConfig = useTuiConfig()
136
- const kv = useKV()
137
- const { theme } = useTheme()
138
- const promptRef = usePromptRef()
139
- const session = createMemo(() => sync.session.get(route.sessionID))
140
- const currentAgentID = useCurrentAgentID()
141
- const actors = createMemo(() => sync.data.actor[route.sessionID] ?? [])
142
- const messages = createMemo(() => sync.data.message[route.sessionID]?.[currentAgentID()] ?? [])
143
- const permissions = createMemo(() => sync.data.permission[route.sessionID] ?? [])
144
- const questions = createMemo(() => sync.data.question[route.sessionID] ?? [])
145
- const visible = createMemo(
146
- () =>
147
- !session()?.parentID &&
148
- currentAgentID() === "main" &&
149
- permissions().length === 0 &&
150
- questions().length === 0,
151
- )
152
- const loading = createMemo(() => !session() && (route.sessionID ?? "").length > 0)
153
- const disabled = createMemo(() => permissions().length > 0 || questions().length > 0)
154
- const [sessionExpired, setSessionExpired] = createSignal<{ expired: boolean; message: string }>({
155
- expired: false,
156
- message: "",
157
- })
158
-
159
- const pending = createMemo(() => {
160
- return messages().findLast((x) => x.role === "assistant" && !x.time.completed)?.id
161
- })
162
-
163
- const lastAssistant = createMemo(() => {
164
- return messages().findLast((x) => x.role === "assistant")
165
- })
166
-
167
- const dimensions = useTerminalDimensions()
168
- const [sidebar, setSidebar] = kv.signal<"auto" | "hide">("sidebar", "auto")
169
- const [sidebarOpen, setSidebarOpen] = createSignal(false)
170
- const [conceal, setConceal] = createSignal(true)
171
- const thinking = useThinkingMode()
172
- const thinkingMode = thinking.mode
173
- const showThinking = createMemo(() => true)
174
- const [timestamps, setTimestamps] = kv.signal<"hide" | "show">("timestamps", "hide")
175
- const [showDetails, setShowDetails] = kv.signal("tool_details_visibility", true)
176
- const [showAssistantMetadata, _setShowAssistantMetadata] = kv.signal("assistant_metadata_visibility", true)
177
- const [showScrollbar, setShowScrollbar] = kv.signal("scrollbar_visible", false)
178
- const [scrolling, setScrolling] = createSignal(false)
179
- let scrollHideTimer: ReturnType<typeof setTimeout> | undefined
180
- const scrollbarVisible = createMemo(() => showScrollbar() || scrolling())
181
- const onWheel = (evt: MouseEvent) => {
182
- if (evt.type !== "scroll") return
183
- setScrolling(true)
184
- if (scrollHideTimer) clearTimeout(scrollHideTimer)
185
- scrollHideTimer = setTimeout(() => setScrolling(false), 1000)
186
- }
187
- onCleanup(() => {
188
- if (scrollHideTimer) clearTimeout(scrollHideTimer)
189
- })
190
- const [diffWrapMode] = kv.signal<"word" | "none">("diff_wrap_mode", "word")
191
- const [_animationsEnabled, _setAnimationsEnabled] = kv.signal("animations_enabled", true)
192
- const [showGenericToolOutput, setShowGenericToolOutput] = kv.signal("generic_tool_output_visibility", false)
193
-
194
- // The workflow run shown as a FULL-SCREEN page (replacing the message stream),
195
- // mirroring how agentID renders a subagent's conversation. Driven by the route so
196
- // it's a real navigable view, not a side panel.
197
- const workflowRunID = createMemo(() => route.workflowRunID)
198
- const fromWorkflowRunID = createMemo(() => route.fromWorkflowRunID)
199
-
200
- const wide = createMemo(() => dimensions().width > 120)
201
- const sidebarVisible = createMemo(() => {
202
- if (session()?.parentID) return false
203
- if (currentAgentID() !== "main") return false
204
- if (sidebarOpen()) return true
205
- if (sidebar() === "auto" && wide()) return true
206
- return false
207
- })
208
- const showTimestamps = createMemo(() => timestamps() === "show")
209
- const contentWidth = createMemo(() => dimensions().width - (sidebarVisible() ? 42 : 0) - 4)
210
- const providers = createMemo(() => Model.index(sync.data.provider))
211
-
212
- const scrollAcceleration = createMemo(() => getScrollAcceleration(tuiConfig))
213
- const toast = useToast()
214
- const sdk = useSDK()
215
-
216
- createEffect(async () => {
217
- const previousWorkspace = project.workspace.current()
218
- const result = await sdk.client.session.get({ sessionID: route.sessionID }, { throwOnError: true })
219
- if (!result.data) {
220
- toast.show({
221
- message: `Session not found: ${route.sessionID}`,
222
- variant: "error",
223
- })
224
- navigate({ type: "home" })
225
- return
226
- }
227
-
228
- if (result.data.workspaceID !== previousWorkspace) {
229
- project.workspace.set(result.data.workspaceID)
230
-
231
- // Sync all the data for this workspace. Note that this
232
- // workspace may not exist anymore which is why this is not
233
- // fatal. If it doesn't we still want to show the session
234
- // (which will be non-interactive)
235
- try {
236
- await sync.bootstrap({ fatal: false })
237
- } catch (e) {}
238
- }
239
- await sync.session.sync(route.sessionID)
240
- if (scroll) scroll.scrollBy(100_000)
241
- })
242
-
243
- let lastSwitch: string | undefined = undefined
244
- event.on("message.part.updated", (evt) => {
245
- const part = evt.properties.part
246
- if (part.type !== "tool") return
247
- if (part.sessionID !== route.sessionID) return
248
- if (part.state.status !== "completed") return
249
- if (part.id === lastSwitch) return
250
-
251
- if (part.tool === "plan_exit" && part.state.metadata?.switched) {
252
- local.agent.set("build")
253
- lastSwitch = part.id
254
- } else if (part.tool === "plan_enter") {
255
- local.agent.set("plan")
256
- lastSwitch = part.id
257
- }
258
- })
259
-
260
- let seeded = false
261
- let scroll: ScrollBoxRenderable
262
- let prompt: PromptRef | undefined
263
- const bind = (r: PromptRef | undefined) => {
264
- prompt = r
265
- promptRef.set(r)
266
- if (seeded || !route.prompt || !r) return
267
- seeded = true
268
- r.set(route.prompt)
269
- }
270
- const keybind = useKeybind()
271
- const dialog = useDialog()
272
- const renderer = useRenderer()
273
-
274
- event.on("session.status", (evt) => {
275
- if (evt.properties.sessionID !== route.sessionID) return
276
- if (evt.properties.status.type !== "retry") return
277
- if (evt.properties.status.message !== SessionRetry.GO_UPSELL_MESSAGE) return
278
- if (dialog.stack.length > 0) return
279
-
280
- const seen = kv.get(GO_UPSELL_LAST_SEEN_AT)
281
- if (typeof seen === "number" && Date.now() - seen < GO_UPSELL_WINDOW) return
282
-
283
- if (kv.get(GO_UPSELL_DONT_SHOW)) return
284
-
285
- void DialogGoUpsell.show(dialog).then((dontShowAgain) => {
286
- if (dontShowAgain) kv.set(GO_UPSELL_DONT_SHOW, true)
287
- kv.set(GO_UPSELL_LAST_SEEN_AT, Date.now())
288
- })
289
- })
290
-
291
- // Allow exit when in child session (prompt is hidden)
292
- const exit = useExit()
293
-
294
- createEffect(() => {
295
- const title = Locale.truncate(session()?.title ?? "", 50)
296
- const pad = (text: string) => text.padEnd(10, " ")
297
- const weak = (text: string) => UI.Style.TEXT_DIM + pad(text) + UI.Style.TEXT_NORMAL
298
- const logo = UI.logo(" ").split(/\r?\n/)
299
- return exit.message.set(
300
- [
301
- ...logo,
302
- ``,
303
- ` ${weak("Session")}${UI.Style.TEXT_NORMAL_BOLD}${title}${UI.Style.TEXT_NORMAL}`,
304
- ` ${weak("Continue")}${UI.Style.TEXT_NORMAL_BOLD}sleepy -s ${session()?.id}${UI.Style.TEXT_NORMAL}`,
305
- ``,
306
- ].join("\n"),
307
- )
308
- })
309
-
310
- useKeyboard((evt) => {
311
- if (!session()?.parentID && currentAgentID() === "main") return
312
- if (keybind.match("app_exit", evt)) {
313
- const status = sync.data.session_status?.[route.sessionID]
314
- if (status && status.type !== "idle") {
315
- void sdk.client.session.abort({ sessionID: route.sessionID }).catch(() => {})
316
- return
317
- }
318
- void exit()
319
- }
320
- })
321
-
322
- // Helper: Find next visible message boundary in direction
323
- const findNextVisibleMessage = (direction: "next" | "prev"): string | null => {
324
- const children = scroll.getChildren()
325
- const messagesList = messages()
326
- const scrollTop = scroll.y
327
-
328
- // Get visible messages sorted by position, filtering for valid non-synthetic, non-ignored content
329
- const visibleMessages = children
330
- .filter((c) => {
331
- if (!c.id) return false
332
- const message = messagesList.find((m) => m.id === c.id)
333
- if (!message) return false
334
-
335
- // Check if message has valid non-synthetic, non-ignored text parts
336
- const parts = sync.data.part[message.id]
337
- if (!parts || !Array.isArray(parts)) return false
338
-
339
- return parts.some((part) => part && part.type === "text" && !part.synthetic && !part.ignored)
340
- })
341
- .sort((a, b) => a.y - b.y)
342
-
343
- if (visibleMessages.length === 0) return null
344
-
345
- if (direction === "next") {
346
- // Find first message below current position
347
- return visibleMessages.find((c) => c.y > scrollTop + 10)?.id ?? null
348
- }
349
- // Find last message above current position
350
- return [...visibleMessages].reverse().find((c) => c.y < scrollTop - 10)?.id ?? null
351
- }
352
-
353
- // Helper: Scroll to message in direction or fallback to page scroll
354
- const scrollToMessage = (direction: "next" | "prev", dialog: ReturnType<typeof useDialog>) => {
355
- const targetID = findNextVisibleMessage(direction)
356
-
357
- if (!targetID) {
358
- scroll.scrollBy(direction === "next" ? scroll.height : -scroll.height)
359
- dialog.clear()
360
- return
361
- }
362
-
363
- const child = scroll.getChildren().find((c) => c.id === targetID)
364
- if (child) scroll.scrollBy(child.y - scroll.y - 1)
365
- dialog.clear()
366
- }
367
-
368
- function toBottom() {
369
- setTimeout(() => {
370
- if (!scroll || scroll.isDestroyed) return
371
- scroll.scrollTo(scroll.scrollHeight)
372
- }, 50)
373
- }
374
-
375
- const local = useLocal()
376
-
377
- // On a rate-limit / queue ("too many requests"),
378
- // nudge the user toward a Token Plan — at most once per 24h.
379
- event.on("tui.session.expired", (evt) => {
380
- setSessionExpired({ expired: true, message: evt.properties.message })
381
- })
382
-
383
- event.on("session.status", (evt) => {
384
- if (evt.properties.sessionID !== route.sessionID) return
385
- if (evt.properties.status.type !== "retry") return
386
- if (!SessionRetry.isRateLimitMessage(evt.properties.status.message)) return
387
- if (dialog.stack.length > 0) return
388
-
389
- const seen = kv.get(QUEUE_TOKEN_PLAN_LAST_SEEN_AT)
390
- if (typeof seen === "number" && Date.now() - seen < QUEUE_TOKEN_PLAN_WINDOW) return
391
-
392
- // Record the 24h cooldown only after the user dismisses, so a show() that
393
- // fails (or never reaches the user) doesn't silently burn the whole day.
394
- void DialogTokenPlan.show(dialog).then(() => {
395
- kv.set(QUEUE_TOKEN_PLAN_LAST_SEEN_AT, Date.now())
396
- })
397
- })
398
-
399
- function moveFirstChild() {
400
- const list = actors().filter((a) => a.mode === "subagent")
401
- if (list.length === 0) {
402
- dialog.replace(() => <DialogSubagent sessionID={route.sessionID} />)
403
- return
404
- }
405
- if (fullRoute.data.type !== "session") return
406
- navigate({ ...fullRoute.data, agentID: list[0].actor_id, fromWorkflowRunID: undefined })
407
- }
408
-
409
- function moveChild(direction: 1 | -1) {
410
- const list = actors().filter((a) => a.mode === "subagent")
411
- if (list.length === 0) return
412
- if (fullRoute.data.type !== "session") return
413
- const cur = currentAgentID()
414
- const idx = list.findIndex((a) => a.actor_id === cur)
415
- const next =
416
- idx === -1
417
- ? direction === 1
418
- ? 0
419
- : list.length - 1
420
- : (idx + direction + list.length) % list.length
421
- navigate({ ...fullRoute.data, agentID: list[next].actor_id, fromWorkflowRunID: undefined })
422
- }
423
-
424
- const command = useCommandDialog()
425
- const t = useLanguage().t
426
- command.register(() => [
427
- {
428
- title: t(session()?.share?.url ? "tui.command.session.share.copy_link" : "tui.command.session.share.title"),
429
- value: "session.share",
430
- suggested: route.type === "session",
431
- keybind: "session_share",
432
- category: "session",
433
- enabled: sync.data.config.share !== "disabled",
434
- slash: {
435
- name: "share",
436
- },
437
- onSelect: async (dialog) => {
438
- const copy = (url: string) =>
439
- Clipboard.copy(url)
440
- .then(() => toast.show({ message: "Share URL copied to clipboard!", variant: "success" }))
441
- .catch(() => toast.show({ message: "Failed to copy URL to clipboard", variant: "error" }))
442
- const url = session()?.share?.url
443
- if (url) {
444
- await copy(url)
445
- dialog.clear()
446
- return
447
- }
448
- if (!kv.get("share_consent", false)) {
449
- const ok = await DialogConfirm.show(dialog, "Share Session", "Are you sure you want to share it?")
450
- if (ok !== true) return
451
- kv.set("share_consent", true)
452
- }
453
- await sdk.client.session
454
- .share({
455
- sessionID: route.sessionID,
456
- })
457
- .then((res) => copy(res.data!.share!.url))
458
- .catch((error) => {
459
- toast.show({
460
- message: error instanceof Error ? error.message : "Failed to share session",
461
- variant: "error",
462
- })
463
- })
464
- dialog.clear()
465
- },
466
- },
467
- {
468
- title: t("tui.command.session.rename.title"),
469
- value: "session.rename",
470
- keybind: "session_rename",
471
- category: "session",
472
- slash: {
473
- name: "rename",
474
- },
475
- onSelect: (dialog) => {
476
- dialog.replace(() => <DialogSessionRename session={route.sessionID} />)
477
- },
478
- },
479
- {
480
- title: t("tui.command.session.timeline.title"),
481
- value: "session.timeline",
482
- keybind: "session_timeline",
483
- category: "session",
484
- slash: {
485
- name: "timeline",
486
- },
487
- onSelect: (dialog) => {
488
- dialog.replace(() => (
489
- <DialogTimeline
490
- onMove={(messageID) => {
491
- const child = scroll.getChildren().find((child) => {
492
- return child.id === messageID
493
- })
494
- if (child) scroll.scrollBy(child.y - scroll.y - 1)
495
- }}
496
- sessionID={route.sessionID}
497
- setPrompt={(promptInfo) => prompt?.set(promptInfo)}
498
- />
499
- ))
500
- },
501
- },
502
- {
503
- title: t("tui.command.session.fork.title"),
504
- value: "session.fork",
505
- keybind: "session_fork",
506
- category: "session",
507
- slash: {
508
- name: "fork",
509
- },
510
- onSelect: (dialog) => {
511
- dialog.replace(() => (
512
- <DialogForkFromTimeline
513
- onMove={(messageID) => {
514
- if (!messageID) return
515
- const child = scroll.getChildren().find((child) => {
516
- return child.id === messageID
517
- })
518
- if (child) scroll.scrollBy(child.y - scroll.y - 1)
519
- }}
520
- sessionID={route.sessionID}
521
- />
522
- ))
523
- },
524
- },
525
- {
526
- title: t("tui.command.session.compact.title"),
527
- value: "session.compact",
528
- keybind: "session_compact",
529
- category: "session",
530
- slash: {
531
- name: "compact",
532
- aliases: ["summarize"],
533
- },
534
- onSelect: (dialog) => {
535
- const selectedModel = local.model.current()
536
- if (!selectedModel) {
537
- toast.show({
538
- variant: "warning",
539
- message: "Connect a provider to summarize this session",
540
- duration: 3000,
541
- })
542
- return
543
- }
544
- void sdk.client.session.summarize({
545
- sessionID: route.sessionID,
546
- modelID: selectedModel.modelID,
547
- providerID: selectedModel.providerID,
548
- })
549
- dialog.clear()
550
- },
551
- },
552
- {
553
- title: t("tui.command.session.unshare.title"),
554
- value: "session.unshare",
555
- keybind: "session_unshare",
556
- category: "session",
557
- enabled: !!session()?.share?.url,
558
- slash: {
559
- name: "unshare",
560
- },
561
- onSelect: async (dialog) => {
562
- await sdk.client.session
563
- .unshare({
564
- sessionID: route.sessionID,
565
- })
566
- .then(() => toast.show({ message: "Session unshared successfully", variant: "success" }))
567
- .catch((error) => {
568
- toast.show({
569
- message: error instanceof Error ? error.message : "Failed to unshare session",
570
- variant: "error",
571
- })
572
- })
573
- dialog.clear()
574
- },
575
- },
576
- {
577
- title: t("tui.command.session.undo.title"),
578
- value: "session.undo",
579
- keybind: "messages_undo",
580
- category: "session",
581
- slash: {
582
- name: "undo",
583
- },
584
- onSelect: async (dialog) => {
585
- const status = sync.data.session_status?.[route.sessionID]
586
- if (status?.type !== "idle") await sdk.client.session.abort({ sessionID: route.sessionID }).catch(() => {})
587
- const revert = session()?.revert?.messageID
588
- const message = messages().findLast((x) => (!revert || x.id < revert) && x.role === "user")
589
- if (!message) return
590
- void sdk.client.session
591
- .revert({
592
- sessionID: route.sessionID,
593
- messageID: message.id,
594
- })
595
- .then(() => {
596
- toBottom()
597
- })
598
- const parts = sync.data.part[message.id]
599
- prompt?.set(
600
- parts.reduce(
601
- (agg, part) => {
602
- if (part.type === "text") {
603
- if (!part.synthetic) agg.input += part.text
604
- }
605
- if (part.type === "file") agg.parts.push(part)
606
- return agg
607
- },
608
- { input: "", parts: [] as PromptInfo["parts"] },
609
- ),
610
- )
611
- dialog.clear()
612
- },
613
- },
614
- {
615
- title: t("tui.command.session.redo.title"),
616
- value: "session.redo",
617
- keybind: "messages_redo",
618
- category: "session",
619
- enabled: !!session()?.revert?.messageID,
620
- slash: {
621
- name: "redo",
622
- },
623
- onSelect: (dialog) => {
624
- dialog.clear()
625
- const messageID = session()?.revert?.messageID
626
- if (!messageID) return
627
- const message = messages().find((x) => x.role === "user" && x.id > messageID)
628
- if (!message) {
629
- void sdk.client.session.unrevert({
630
- sessionID: route.sessionID,
631
- })
632
- prompt?.set({ input: "", parts: [] })
633
- return
634
- }
635
- void sdk.client.session.revert({
636
- sessionID: route.sessionID,
637
- messageID: message.id,
638
- })
639
- },
640
- },
641
- {
642
- title: t(sidebarVisible() ? "tui.command.session.sidebar.hide" : "tui.command.session.sidebar.show"),
643
- value: "session.sidebar.toggle",
644
- keybind: "sidebar_toggle",
645
- category: "session",
646
- onSelect: (dialog) => {
647
- batch(() => {
648
- const isVisible = sidebarVisible()
649
- setSidebar(() => (isVisible ? "hide" : "auto"))
650
- setSidebarOpen(!isVisible)
651
- })
652
- dialog.clear()
653
- },
654
- },
655
- {
656
- title: t(conceal() ? "tui.command.session.conceal.disable" : "tui.command.session.conceal.enable"),
657
- value: "session.toggle.conceal",
658
- keybind: "messages_toggle_conceal",
659
- category: "session",
660
- onSelect: (dialog) => {
661
- setConceal((prev) => !prev)
662
- dialog.clear()
663
- },
664
- },
665
- {
666
- title: t(showTimestamps() ? "tui.command.session.timestamps.hide" : "tui.command.session.timestamps.show"),
667
- value: "session.toggle.timestamps",
668
- category: "session",
669
- slash: {
670
- name: "timestamps",
671
- aliases: ["toggle-timestamps"],
672
- },
673
- onSelect: (dialog) => {
674
- setTimestamps((prev) => (prev === "show" ? "hide" : "show"))
675
- dialog.clear()
676
- },
677
- },
678
- {
679
- title: t(
680
- nextThinkingMode(thinkingMode()) === "hide"
681
- ? "tui.command.session.thinking.collapse"
682
- : "tui.command.session.thinking.expand",
683
- ),
684
- value: "session.toggle.thinking",
685
- keybind: "display_thinking",
686
- category: "session",
687
- slash: {
688
- name: "thinking",
689
- aliases: ["toggle-thinking"],
690
- },
691
- onSelect: (dialog) => {
692
- thinking.set(nextThinkingMode(thinkingMode()))
693
- dialog.clear()
694
- },
695
- },
696
- {
697
- title: t(showDetails() ? "tui.command.session.tool_details.hide" : "tui.command.session.tool_details.show"),
698
- value: "session.toggle.actions",
699
- keybind: "tool_details",
700
- category: "session",
701
- onSelect: (dialog) => {
702
- setShowDetails((prev) => !prev)
703
- dialog.clear()
704
- },
705
- },
706
- {
707
- title: t("tui.command.session.scrollbar.toggle"),
708
- value: "session.toggle.scrollbar",
709
- keybind: "scrollbar_toggle",
710
- category: "session",
711
- onSelect: (dialog) => {
712
- setShowScrollbar((prev) => !prev)
713
- dialog.clear()
714
- },
715
- },
716
- {
717
- title: t(
718
- showGenericToolOutput()
719
- ? "tui.command.session.generic_tool_output.hide"
720
- : "tui.command.session.generic_tool_output.show",
721
- ),
722
- value: "session.toggle.generic_tool_output",
723
- category: "session",
724
- onSelect: (dialog) => {
725
- setShowGenericToolOutput((prev) => !prev)
726
- dialog.clear()
727
- },
728
- },
729
- {
730
- title: t("tui.command.session.page_up.title"),
731
- value: "session.page.up",
732
- keybind: "messages_page_up",
733
- category: "session",
734
- hidden: true,
735
- onSelect: (dialog) => {
736
- scroll.scrollBy(-scroll.height / 2)
737
- dialog.clear()
738
- },
739
- },
740
- {
741
- title: t("tui.command.session.page_down.title"),
742
- value: "session.page.down",
743
- keybind: "messages_page_down",
744
- category: "session",
745
- hidden: true,
746
- onSelect: (dialog) => {
747
- scroll.scrollBy(scroll.height / 2)
748
- dialog.clear()
749
- },
750
- },
751
- {
752
- title: t("tui.command.session.line_up.title"),
753
- value: "session.line.up",
754
- keybind: "messages_line_up",
755
- category: "session",
756
- disabled: true,
757
- onSelect: (dialog) => {
758
- scroll.scrollBy(-1)
759
- dialog.clear()
760
- },
761
- },
762
- {
763
- title: t("tui.command.session.line_down.title"),
764
- value: "session.line.down",
765
- keybind: "messages_line_down",
766
- category: "session",
767
- disabled: true,
768
- onSelect: (dialog) => {
769
- scroll.scrollBy(1)
770
- dialog.clear()
771
- },
772
- },
773
- {
774
- title: t("tui.command.session.half_page_up.title"),
775
- value: "session.half.page.up",
776
- keybind: "messages_half_page_up",
777
- category: "session",
778
- hidden: true,
779
- onSelect: (dialog) => {
780
- scroll.scrollBy(-scroll.height / 4)
781
- dialog.clear()
782
- },
783
- },
784
- {
785
- title: t("tui.command.session.half_page_down.title"),
786
- value: "session.half.page.down",
787
- keybind: "messages_half_page_down",
788
- category: "session",
789
- hidden: true,
790
- onSelect: (dialog) => {
791
- scroll.scrollBy(scroll.height / 4)
792
- dialog.clear()
793
- },
794
- },
795
- {
796
- title: t("tui.command.session.first.title"),
797
- value: "session.first",
798
- keybind: "messages_first",
799
- category: "session",
800
- hidden: true,
801
- onSelect: (dialog) => {
802
- scroll.scrollTo(0)
803
- dialog.clear()
804
- },
805
- },
806
- {
807
- title: t("tui.command.session.last.title"),
808
- value: "session.last",
809
- keybind: "messages_last",
810
- category: "session",
811
- hidden: true,
812
- onSelect: (dialog) => {
813
- scroll.scrollTo(scroll.scrollHeight)
814
- dialog.clear()
815
- },
816
- },
817
- {
818
- title: t("tui.command.session.last_user.title"),
819
- value: "session.messages_last_user",
820
- keybind: "messages_last_user",
821
- category: "session",
822
- hidden: true,
823
- onSelect: () => {
824
- const msgs = messages()
825
- if (!msgs || !msgs.length) return
826
-
827
- // Find the most recent user message with non-ignored, non-synthetic text parts
828
- for (let i = msgs.length - 1; i >= 0; i--) {
829
- const message = msgs[i]
830
- if (!message || message.role !== "user") continue
831
-
832
- const parts = sync.data.part[message.id]
833
- if (!parts || !Array.isArray(parts)) continue
834
-
835
- const hasValidTextPart = parts.some(
836
- (part) => part && part.type === "text" && !part.synthetic && !part.ignored,
837
- )
838
-
839
- if (hasValidTextPart) {
840
- const child = scroll.getChildren().find((child) => {
841
- return child.id === message.id
842
- })
843
- if (child) scroll.scrollBy(child.y - scroll.y - 1)
844
- break
845
- }
846
- }
847
- },
848
- },
849
- {
850
- title: t("tui.command.session.message_next.title"),
851
- value: "session.message.next",
852
- keybind: "messages_next",
853
- category: "session",
854
- hidden: true,
855
- onSelect: (dialog) => scrollToMessage("next", dialog),
856
- },
857
- {
858
- title: t("tui.command.session.message_previous.title"),
859
- value: "session.message.previous",
860
- keybind: "messages_previous",
861
- category: "session",
862
- hidden: true,
863
- onSelect: (dialog) => scrollToMessage("prev", dialog),
864
- },
865
- {
866
- title: t("tui.command.messages.copy.title"),
867
- value: "messages.copy",
868
- keybind: "messages_copy",
869
- category: "session",
870
- onSelect: (dialog) => {
871
- const revertID = session()?.revert?.messageID
872
- const lastAssistantMessage = messages().findLast(
873
- (msg) => msg.role === "assistant" && (!revertID || msg.id < revertID),
874
- )
875
- if (!lastAssistantMessage) {
876
- toast.show({ message: "No assistant messages found", variant: "error" })
877
- dialog.clear()
878
- return
879
- }
880
-
881
- const parts = sync.data.part[lastAssistantMessage.id] ?? []
882
- const textParts = parts.filter((part) => part.type === "text")
883
- if (textParts.length === 0) {
884
- toast.show({ message: "No text parts found in last assistant message", variant: "error" })
885
- dialog.clear()
886
- return
887
- }
888
-
889
- const text = textParts
890
- .map((part) => part.text)
891
- .join("\n")
892
- .trim()
893
- if (!text) {
894
- toast.show({
895
- message: "No text content found in last assistant message",
896
- variant: "error",
897
- })
898
- dialog.clear()
899
- return
900
- }
901
-
902
- Clipboard.copy(text)
903
- .then(() => toast.show({ message: "Message copied to clipboard!", variant: "success" }))
904
- .catch(() => toast.show({ message: "Failed to copy to clipboard", variant: "error" }))
905
- dialog.clear()
906
- },
907
- },
908
- {
909
- title: t("tui.command.session.copy.title"),
910
- value: "session.copy",
911
- category: "session",
912
- slash: {
913
- name: "copy",
914
- },
915
- onSelect: async (dialog) => {
916
- try {
917
- const sessionData = session()
918
- if (!sessionData) return
919
- const sessionMessages = messages()
920
- const transcript = formatTranscript(
921
- sessionData,
922
- sessionMessages.map((msg) => ({ info: msg, parts: sync.data.part[msg.id] ?? [] })),
923
- {
924
- thinking: showThinking(),
925
- toolDetails: showDetails(),
926
- assistantMetadata: showAssistantMetadata(),
927
- providers: sync.data.provider,
928
- },
929
- )
930
- await Clipboard.copy(transcript)
931
- toast.show({ message: "Session transcript copied to clipboard!", variant: "success" })
932
- } catch {
933
- toast.show({ message: "Failed to copy session transcript", variant: "error" })
934
- }
935
- dialog.clear()
936
- },
937
- },
938
- {
939
- title: t("tui.command.session.export.title"),
940
- value: "session.export",
941
- keybind: "session_export",
942
- category: "session",
943
- slash: {
944
- name: "export",
945
- },
946
- onSelect: async (dialog) => {
947
- try {
948
- const sessionData = session()
949
- if (!sessionData) return
950
- const sessionMessages = messages()
951
-
952
- const defaultFilename = `session-${sessionData.id.slice(0, 8)}.md`
953
-
954
- const options = await DialogExportOptions.show(
955
- dialog,
956
- defaultFilename,
957
- showThinking(),
958
- showDetails(),
959
- showAssistantMetadata(),
960
- false,
961
- )
962
-
963
- if (options === null) return
964
-
965
- const transcript = formatTranscript(
966
- sessionData,
967
- sessionMessages.map((msg) => ({ info: msg, parts: sync.data.part[msg.id] ?? [] })),
968
- {
969
- thinking: options.thinking,
970
- toolDetails: options.toolDetails,
971
- assistantMetadata: options.assistantMetadata,
972
- providers: sync.data.provider,
973
- },
974
- )
975
-
976
- if (options.openWithoutSaving) {
977
- // Just open in editor without saving
978
- await Editor.open({ value: transcript, renderer })
979
- } else {
980
- const exportDir = process.cwd()
981
- const filename = options.filename.trim()
982
- const filepath = path.join(exportDir, filename)
983
-
984
- await Filesystem.write(filepath, transcript)
985
-
986
- // Open with EDITOR if available
987
- const result = await Editor.open({ value: transcript, renderer })
988
- if (result !== undefined) {
989
- await Filesystem.write(filepath, result)
990
- }
991
-
992
- toast.show({ message: `Session exported to ${filename}`, variant: "success" })
993
- }
994
- } catch {
995
- toast.show({ message: "Failed to export session", variant: "error" })
996
- }
997
- dialog.clear()
998
- },
999
- },
1000
- {
1001
- title: t("tui.command.session.child_first.title"),
1002
- value: "session.child.first",
1003
- keybind: "session_child_first",
1004
- category: "session",
1005
- hidden: true,
1006
- onSelect: (dialog) => {
1007
- moveFirstChild()
1008
- dialog.clear()
1009
- },
1010
- },
1011
- {
1012
- title: t("tui.command.session.parent.title"),
1013
- value: "session.parent",
1014
- keybind: "session_parent",
1015
- category: "session",
1016
- hidden: true,
1017
- enabled: currentAgentID() !== "main" || !!workflowRunID() || !!session()?.parentID,
1018
- onSelect: (dialog) => {
1019
- // Workflow page → back to the conversation (parallels agentID → main).
1020
- if (fullRoute.data.type === "session" && workflowRunID()) {
1021
- navigate({ ...fullRoute.data, workflowRunID: undefined })
1022
- dialog.clear()
1023
- return
1024
- }
1025
- // Agent opened FROM a workflow page → back returns to that workflow.
1026
- if (fullRoute.data.type === "session" && currentAgentID() !== "main" && fromWorkflowRunID()) {
1027
- navigate({ ...fullRoute.data, agentID: undefined, fromWorkflowRunID: undefined, workflowRunID: fromWorkflowRunID() })
1028
- dialog.clear()
1029
- return
1030
- }
1031
- if (fullRoute.data.type === "session" && currentAgentID() !== "main") {
1032
- navigate({ ...fullRoute.data, agentID: undefined })
1033
- dialog.clear()
1034
- return
1035
- }
1036
- const parentID = session()?.parentID
1037
- if (parentID) {
1038
- navigate({
1039
- type: "session",
1040
- sessionID: parentID,
1041
- })
1042
- }
1043
- dialog.clear()
1044
- },
1045
- },
1046
- {
1047
- title: t("tui.command.session.child_next.title"),
1048
- value: "session.child.next",
1049
- keybind: "session_child_cycle",
1050
- category: "session",
1051
- hidden: true,
1052
- onSelect: (dialog) => {
1053
- moveChild(1)
1054
- dialog.clear()
1055
- },
1056
- },
1057
- {
1058
- title: t("tui.command.session.child_previous.title"),
1059
- value: "session.child.previous",
1060
- keybind: "session_child_cycle_reverse",
1061
- category: "session",
1062
- hidden: true,
1063
- onSelect: (dialog) => {
1064
- moveChild(-1)
1065
- dialog.clear()
1066
- },
1067
- },
1068
- ])
1069
-
1070
- const revertInfo = createMemo(() => session()?.revert)
1071
- const revertMessageID = createMemo(() => revertInfo()?.messageID)
1072
-
1073
- const revertDiffFiles = createMemo(() => getRevertDiffFiles(revertInfo()?.diff ?? ""))
1074
-
1075
- const revertRevertedMessages = createMemo(() => {
1076
- const messageID = revertMessageID()
1077
- if (!messageID) return []
1078
- return messages().filter((x) => x.id >= messageID && x.role === "user")
1079
- })
1080
-
1081
- const revert = createMemo(() => {
1082
- const info = revertInfo()
1083
- if (!info) return
1084
- if (!info.messageID) return
1085
- return {
1086
- messageID: info.messageID,
1087
- reverted: revertRevertedMessages(),
1088
- diff: info.diff,
1089
- diffFiles: revertDiffFiles(),
1090
- }
1091
- })
1092
-
1093
- // snap to bottom when session changes
1094
- createEffect(on(() => route.sessionID, toBottom))
1095
-
1096
- return (
1097
- <context.Provider
1098
- value={{
1099
- get width() {
1100
- return contentWidth()
1101
- },
1102
- sessionID: route.sessionID,
1103
- conceal,
1104
- thinkingMode,
1105
- showThinking,
1106
- showTimestamps,
1107
- showDetails,
1108
- showGenericToolOutput,
1109
- diffWrapMode,
1110
- providers,
1111
- sync,
1112
- tui: tuiConfig,
1113
- }}
1114
- >
1115
- <box flexDirection="row">
1116
- <box flexGrow={1} paddingBottom={1} paddingLeft={2} paddingRight={2} gap={1} onMouse={onWheel}>
1117
- <Show
1118
- when={!workflowRunID()}
1119
- fallback={
1120
- <WorkflowPage
1121
- runID={workflowRunID()!}
1122
- onBack={() => navigate({ ...route, workflowRunID: undefined })}
1123
- onOpenAgent={(actorID) =>
1124
- navigate({ ...route, workflowRunID: undefined, agentID: actorID, fromWorkflowRunID: workflowRunID() })
1125
- }
1126
- onOpenChild={(childRunID) => navigate({ ...route, workflowRunID: childRunID })}
1127
- />
1128
- }
1129
- >
1130
- <Show when={session()} fallback={loading() ? <Spinner /> : undefined}>
1131
- <scrollbox
1132
- ref={(r) => (scroll = r)}
1133
- viewportOptions={{
1134
- paddingRight: 1,
1135
- }}
1136
- verticalScrollbarOptions={{
1137
- paddingLeft: 1,
1138
- visible: true,
1139
- trackOptions: {
1140
- backgroundColor: scrollbarVisible() ? theme.backgroundElement : theme.background,
1141
- foregroundColor: scrollbarVisible() ? theme.border : theme.background,
1142
- },
1143
- }}
1144
- stickyScroll={true}
1145
- stickyStart="bottom"
1146
- flexGrow={1}
1147
- scrollAcceleration={scrollAcceleration()}
1148
- >
1149
- <box height={1} />
1150
- <For each={messages()}>
1151
- {(message, index) => (
1152
- <Switch>
1153
- <Match when={message.id === revert()?.messageID}>
1154
- {(function () {
1155
- const command = useCommandDialog()
1156
- const [hover, setHover] = createSignal(false)
1157
- const dialog = useDialog()
1158
-
1159
- const handleUnrevert = async () => {
1160
- const confirmed = await DialogConfirm.show(
1161
- dialog,
1162
- "Confirm Redo",
1163
- "Are you sure you want to restore the reverted messages?",
1164
- )
1165
- if (confirmed) {
1166
- command.trigger("session.redo")
1167
- }
1168
- }
1169
-
1170
- return (
1171
- <box
1172
- onMouseOver={() => setHover(true)}
1173
- onMouseOut={() => setHover(false)}
1174
- onMouseUp={handleUnrevert}
1175
- marginTop={1}
1176
- flexShrink={0}
1177
- border={["left"]}
1178
- customBorderChars={SplitBorder.customBorderChars}
1179
- borderColor={theme.backgroundPanel}
1180
- >
1181
- <box
1182
- paddingTop={1}
1183
- paddingBottom={1}
1184
- paddingLeft={2}
1185
- backgroundColor={hover() ? theme.backgroundElement : theme.backgroundPanel}
1186
- >
1187
- <text fg={theme.textMuted}>{revert()!.reverted.length} message reverted</text>
1188
- <text fg={theme.textMuted}>
1189
- <span style={{ fg: theme.text }}>{keybind.print("messages_redo")}</span> or /redo to
1190
- restore
1191
- </text>
1192
- <Show when={revert()!.diffFiles?.length}>
1193
- <box marginTop={1}>
1194
- <For each={revert()!.diffFiles}>
1195
- {(file) => (
1196
- <text fg={theme.text}>
1197
- {file.filename}
1198
- <Show when={file.additions > 0}>
1199
- <span style={{ fg: theme.diffAdded }}> +{file.additions}</span>
1200
- </Show>
1201
- <Show when={file.deletions > 0}>
1202
- <span style={{ fg: theme.diffRemoved }}> -{file.deletions}</span>
1203
- </Show>
1204
- </text>
1205
- )}
1206
- </For>
1207
- </box>
1208
- </Show>
1209
- </box>
1210
- </box>
1211
- )
1212
- })()}
1213
- </Match>
1214
- <Match when={revert()?.messageID && message.id >= revert()!.messageID}>
1215
- <></>
1216
- </Match>
1217
- <Match when={message.role === "user"}>
1218
- <UserMessage
1219
- index={index()}
1220
- onMouseUp={() => {
1221
- if (renderer.getSelection()?.getSelectedText()) return
1222
- dialog.replace(() => (
1223
- <DialogMessage
1224
- messageID={message.id}
1225
- sessionID={route.sessionID}
1226
- setPrompt={(promptInfo) => prompt?.set(promptInfo)}
1227
- />
1228
- ))
1229
- }}
1230
- message={message as UserMessage}
1231
- parts={sync.data.part[message.id] ?? []}
1232
- pending={pending()}
1233
- />
1234
- </Match>
1235
- <Match when={message.role === "assistant"}>
1236
- <AssistantMessage
1237
- last={lastAssistant()?.id === message.id}
1238
- message={message as AssistantMessage}
1239
- parts={sync.data.part[message.id] ?? []}
1240
- />
1241
- </Match>
1242
- </Switch>
1243
- )}
1244
- </For>
1245
- </scrollbox>
1246
- <box flexShrink={0}>
1247
- <Show when={permissions().length > 0}>
1248
- <PermissionPrompt request={permissions()[0]} />
1249
- </Show>
1250
- <Show when={permissions().length === 0 && questions().length > 0}>
1251
- <QuestionPrompt request={questions()[0]} />
1252
- </Show>
1253
- <Show when={session()?.parentID || currentAgentID() !== "main"}>
1254
- <SubagentFooter />
1255
- </Show>
1256
- <Show when={sessionExpired().expired}>
1257
- <BannerSessionExpired message={sessionExpired().message} />
1258
- </Show>
1259
- <Show when={visible()}>
1260
- <TuiPluginRuntime.Slot
1261
- name="session_prompt"
1262
- mode="replace"
1263
- session_id={route.sessionID}
1264
- visible={visible()}
1265
- disabled={disabled() || sessionExpired().expired}
1266
- on_submit={toBottom}
1267
- ref={bind}
1268
- >
1269
- <Prompt
1270
- visible={visible()}
1271
- ref={bind}
1272
- disabled={disabled() || sessionExpired().expired}
1273
- onSubmit={() => {
1274
- toBottom()
1275
- }}
1276
- sessionID={route.sessionID}
1277
- right={<TuiPluginRuntime.Slot name="session_prompt_right" session_id={route.sessionID} />}
1278
- />
1279
- </TuiPluginRuntime.Slot>
1280
- </Show>
1281
- </box>
1282
- </Show>
1283
- </Show>
1284
- <Toast />
1285
- </box>
1286
- <Show when={sidebarVisible()}>
1287
- <Switch>
1288
- <Match when={wide()}>
1289
- <Sidebar sessionID={route.sessionID} />
1290
- </Match>
1291
- <Match when={!wide()}>
1292
- <box
1293
- position="absolute"
1294
- top={0}
1295
- left={0}
1296
- right={0}
1297
- bottom={0}
1298
- alignItems="flex-end"
1299
- backgroundColor={RGBA.fromInts(0, 0, 0, 70)}
1300
- >
1301
- <Sidebar sessionID={route.sessionID} />
1302
- </box>
1303
- </Match>
1304
- </Switch>
1305
- </Show>
1306
- </box>
1307
- </context.Provider>
1308
- )
1309
- }
1310
-
1311
- const MIME_BADGE: Record<string, string> = {
1312
- "text/plain": "txt",
1313
- "image/png": "img",
1314
- "image/jpeg": "img",
1315
- "image/gif": "img",
1316
- "image/webp": "img",
1317
- "application/pdf": "pdf",
1318
- "application/x-directory": "dir",
1319
- }
1320
-
1321
- function UserMessage(props: {
1322
- message: UserMessage
1323
- parts: Part[]
1324
- onMouseUp: () => void
1325
- index: number
1326
- pending?: string
1327
- }) {
1328
- const ctx = use()
1329
- const local = useLocal()
1330
- const text = createMemo(() => props.parts.flatMap((x) => (x.type === "text" && !x.synthetic ? [x] : []))[0])
1331
- const files = createMemo(() => props.parts.flatMap((x) => (x.type === "file" ? [x] : [])))
1332
- const { theme } = useTheme()
1333
- const [hover, setHover] = createSignal(false)
1334
- const queued = createMemo(() => props.pending && props.message.id > props.pending)
1335
- const color = createMemo(() => local.agent.color(props.message.agent))
1336
- const queuedFg = createMemo(() => selectedForeground(theme, color()))
1337
- const metadataVisible = createMemo(() => queued() || ctx.showTimestamps())
1338
-
1339
- return (
1340
- <>
1341
- <Show when={text()}>
1342
- <box
1343
- id={props.message.id}
1344
- border={["left"]}
1345
- borderColor={color()}
1346
- customBorderChars={SplitBorder.customBorderChars}
1347
- marginTop={props.index === 0 ? 0 : 1}
1348
- >
1349
- <box
1350
- onMouseOver={() => {
1351
- setHover(true)
1352
- }}
1353
- onMouseOut={() => {
1354
- setHover(false)
1355
- }}
1356
- onMouseUp={props.onMouseUp}
1357
- paddingTop={1}
1358
- paddingBottom={1}
1359
- paddingLeft={2}
1360
- backgroundColor={hover() ? theme.backgroundElement : theme.backgroundPanel}
1361
- flexShrink={0}
1362
- >
1363
- <text fg={theme.text}>{text()?.text}</text>
1364
- <Show when={files().length}>
1365
- <box flexDirection="row" paddingBottom={metadataVisible() ? 1 : 0} paddingTop={1} gap={1} flexWrap="wrap">
1366
- <For each={files()}>
1367
- {(file) => {
1368
- const bg = createMemo(() => {
1369
- if (file.mime.startsWith("image/")) return theme.accent
1370
- if (file.mime === "application/pdf") return theme.primary
1371
- return theme.secondary
1372
- })
1373
- return (
1374
- <text fg={theme.text}>
1375
- <span style={{ bg: bg(), fg: theme.background }}> {MIME_BADGE[file.mime] ?? file.mime} </span>
1376
- <span style={{ bg: theme.backgroundElement, fg: theme.textMuted }}> {file.filename} </span>
1377
- </text>
1378
- )
1379
- }}
1380
- </For>
1381
- </box>
1382
- </Show>
1383
- <Show
1384
- when={queued()}
1385
- fallback={
1386
- <Show when={ctx.showTimestamps()}>
1387
- <text fg={theme.textMuted}>
1388
- <span style={{ fg: theme.textMuted }}>
1389
- {Locale.todayTimeOrDateTime(props.message.time.created)}
1390
- </span>
1391
- </text>
1392
- </Show>
1393
- }
1394
- >
1395
- <text fg={theme.textMuted}>
1396
- <span style={{ bg: color(), fg: queuedFg(), bold: true }}> QUEUED </span>
1397
- </text>
1398
- </Show>
1399
- </box>
1400
- </box>
1401
- </Show>
1402
- </>
1403
- )
1404
- }
1405
-
1406
- function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; last: boolean }) {
1407
- const ctx = use()
1408
- const local = useLocal()
1409
- const { theme } = useTheme()
1410
- const sync = useSync()
1411
- const toast = useToast()
1412
- const renderer = useRenderer()
1413
- const t = useLanguage().t
1414
- const [copyHover, setCopyHover] = createSignal(false)
1415
- const messages = createMemo(() => sync.data.message[props.message.sessionID]?.[props.message.agentID ?? "main"] ?? [])
1416
- const model = createMemo(() => Model.name(ctx.providers(), props.message.providerID, props.message.modelID))
1417
-
1418
- const final = createMemo(() => {
1419
- return props.message.finish && props.message.finish !== "tool-calls"
1420
- })
1421
-
1422
- const duration = createMemo(() => {
1423
- if (!final()) return 0
1424
- if (!props.message.time.completed) return 0
1425
- const user = messages().find((x) => x.role === "user" && x.id === props.message.parentID)
1426
- if (!user || !user.time) return 0
1427
- return props.message.time.completed - user.time.created
1428
- })
1429
-
1430
- const keybind = useKeybind()
1431
-
1432
- const handleCopy = () => {
1433
- if (renderer.getSelection()?.getSelectedText()) return
1434
- const text = props.parts
1435
- .filter((p) => p.type === "text")
1436
- .map((p) => (p as TextPart).text)
1437
- .join("\n")
1438
- .trim()
1439
- if (!text) return
1440
- Clipboard.copy(text)
1441
- .then(() => toast.show({ message: t("tui.toast.copied_to_clipboard"), variant: "success" }))
1442
- .catch(() => toast.show({ message: "Failed to copy to clipboard", variant: "error" }))
1443
- }
1444
-
1445
- // Goal judge verdict for this specific turn, if the stop-condition judge
1446
- // evaluated it. Rendered as a foldable per-turn marker so the user can trace
1447
- // back which turn failed the check — without polluting the message stream.
1448
- const verdict = createMemo(() => sync.data.session_goal?.[props.message.sessionID]?.verdicts?.[props.message.id])
1449
- const [verdictOpen, setVerdictOpen] = createSignal(false)
1450
- const verdictMark = createMemo(() => {
1451
- const v = verdict()
1452
- if (!v) return undefined
1453
- if (v.error) return { icon: "!", fg: theme.textMuted, label: "Judge: error (stopped)" }
1454
- if (v.ok) return { icon: "✓", fg: theme.success, label: "Judge: met" }
1455
- if (v.impossible) return { icon: "⊘", fg: theme.error, label: "Judge: impossible" }
1456
- return { icon: "⟳", fg: theme.warning, label: `Judge [round ${v.attempt}]: not met` }
1457
- })
1458
-
1459
- // Both the `actor` and `workflow` tools spawn agents that register as
1460
- // subagents in this session, so the `session_child_first` keybind opens the
1461
- // Subagents panel for either. Advertise it with copy matching the tool that
1462
- // produced the message; a mixed message falls back to the generic subagent
1463
- // wording.
1464
- const hasActorPart = createMemo(() => props.parts.some((x) => x.type === "tool" && x.tool === "actor"))
1465
- const hasWorkflowPart = createMemo(() => props.parts.some((x) => x.type === "tool" && x.tool === "workflow"))
1466
-
1467
- return (
1468
- <>
1469
- <For each={props.parts}>
1470
- {(part, index) => {
1471
- // The StructuredOutput tool call is the mechanism that produces
1472
- // message.structured; we render that value as a dedicated colored block
1473
- // below, so skip the redundant gray one-liner tool part here.
1474
- if (part.type === "tool" && part.tool === "StructuredOutput") return null
1475
- const component = createMemo(() => PART_MAPPING[part.type as keyof typeof PART_MAPPING])
1476
- return (
1477
- <Show when={component()}>
1478
- <Dynamic
1479
- last={index() === props.parts.length - 1}
1480
- component={component()}
1481
- part={part as any}
1482
- message={props.message}
1483
- />
1484
- </Show>
1485
- )
1486
- }}
1487
- </For>
1488
- <Show when={props.message.structured !== undefined && props.message.structured !== null}>
1489
- <StructuredOutput value={props.message.structured} />
1490
- </Show>
1491
- <Show when={hasActorPart() || hasWorkflowPart()}>
1492
- <box paddingTop={1} paddingLeft={3}>
1493
- <text fg={theme.text}>
1494
- {keybind.print("session_child_first")}
1495
- <span style={{ fg: theme.textMuted }}>{hasWorkflowPart() ? " view workflow agents" : " view subagents"}</span>
1496
- </text>
1497
- </box>
1498
- </Show>
1499
- <Show when={props.message.error && props.message.error.name !== "MessageAbortedError"}>
1500
- <ErrorBlock error={props.message.error!} />
1501
- </Show>
1502
- <Switch>
1503
- <Match when={props.last || final() || props.message.error?.name === "MessageAbortedError"}>
1504
- <box paddingLeft={3} flexDirection="row" justifyContent="space-between" marginTop={1}>
1505
- <text>
1506
- <span
1507
- style={{
1508
- fg:
1509
- props.message.error?.name === "MessageAbortedError"
1510
- ? theme.textMuted
1511
- : local.agent.color(props.message.agent),
1512
- }}
1513
- >
1514
- ▣{" "}
1515
- </span>{" "}
1516
- <span style={{ fg: theme.text }}>{Locale.titlecase(props.message.mode)}</span>
1517
- <span style={{ fg: theme.textMuted }}> · {model()}</span>
1518
- <Show when={duration()}>
1519
- <span style={{ fg: theme.textMuted }}> · {Locale.duration(duration())}</span>
1520
- </Show>
1521
- <Show when={props.message.error?.name === "MessageAbortedError"}>
1522
- <span style={{ fg: theme.textMuted }}> · interrupted</span>
1523
- </Show>
1524
- </text>
1525
- <Show when={props.message.time.completed}>
1526
- <box
1527
- onMouseOver={() => setCopyHover(true)}
1528
- onMouseOut={() => setCopyHover(false)}
1529
- onMouseUp={handleCopy}
1530
- >
1531
- <text fg={copyHover() ? theme.text : theme.textMuted}>⎘ copy</text>
1532
- </box>
1533
- </Show>
1534
- </box>
1535
- </Match>
1536
- </Switch>
1537
- <Show when={verdictMark()}>
1538
- {(mark) => (
1539
- <box paddingLeft={3} onMouseUp={() => setVerdictOpen((x) => !x)}>
1540
- <text>
1541
- <span style={{ fg: theme.textMuted }}>{verdictOpen() ? "▼" : "▶"} </span>
1542
- <span style={{ fg: mark().fg }}>
1543
- {mark().icon} {mark().label}
1544
- </span>
1545
- </text>
1546
- <Show when={verdictOpen()}>
1547
- <box paddingLeft={2}>
1548
- <text fg={theme.textMuted} wrapMode="word">
1549
- {verdict()!.reason}
1550
- </text>
1551
- </box>
1552
- </Show>
1553
- </box>
1554
- )}
1555
- </Show>
1556
- </>
1557
- )
1558
- }
1559
-
1560
- const PART_MAPPING = {
1561
- text: TextPart,
1562
- tool: ToolPart,
1563
- reasoning: ReasoningPart,
1564
- }
1565
-
1566
- type MessageError = NonNullable<AssistantMessage["error"]>
1567
-
1568
- function errorBody(error: MessageError): string {
1569
- if (error.name === "MessageOutputLengthError") return "Output length limit reached"
1570
- return (error.data as { message?: string }).message ?? "Unknown error"
1571
- }
1572
-
1573
- function errorMeta(error: MessageError): string | undefined {
1574
- if (error.name === "APIError") {
1575
- const parts: string[] = []
1576
- if (error.data.statusCode !== undefined) parts.push(`status ${error.data.statusCode}`)
1577
- parts.push(error.data.isRetryable ? "retryable" : "non-retryable")
1578
- return parts.join(" · ")
1579
- }
1580
- if (error.name === "ProviderAuthError") return `provider: ${error.data.providerID}`
1581
- if (error.name === "StructuredOutputError") return `retries: ${error.data.retries}`
1582
- return undefined
1583
- }
1584
-
1585
- function ErrorBlock(props: { error: MessageError }) {
1586
- const { theme } = useTheme()
1587
- const meta = createMemo(() => errorMeta(props.error))
1588
- return (
1589
- <box flexDirection="column" paddingLeft={3} marginTop={1}>
1590
- <text fg={theme.error} wrapMode="word">
1591
- <span style={{ fg: theme.error }}>✗ </span>
1592
- {errorBody(props.error)}
1593
- </text>
1594
- <Show when={meta()}>
1595
- <box paddingLeft={3}>
1596
- <text fg={theme.textMuted} wrapMode="word">
1597
- {meta()}
1598
- </text>
1599
- </box>
1600
- </Show>
1601
- </box>
1602
- )
1603
- }
1604
-
1605
- // Structured output is a message-level field (AssistantMessage.structured), not a
1606
- // part, so the parts loop never shows it. Agents called with a schema (common in
1607
- // workflows) put their whole answer here — render it as syntax-highlighted JSON so
1608
- // it's not invisible. Collapsible for large payloads.
1609
- function StructuredOutput(props: { value: unknown }) {
1610
- const { theme, syntax } = useTheme()
1611
- const [collapsed, setCollapsed] = createSignal(false)
1612
- const json = createMemo(() => {
1613
- try {
1614
- return JSON.stringify(props.value, null, 2)
1615
- } catch {
1616
- return String(props.value)
1617
- }
1618
- })
1619
- const lineCount = createMemo(() => json().split("\n").length)
1620
- const overflow = createMemo(() => lineCount() > 20)
1621
- const shown = createMemo(() => (collapsed() ? json().split("\n").slice(0, 20).join("\n") + "\n…" : json()))
1622
- return (
1623
- <box paddingLeft={3} marginTop={1} flexDirection="column" flexShrink={0}>
1624
- <box flexDirection="row" gap={1} onMouseUp={() => overflow() && setCollapsed((p) => !p)}>
1625
- <text fg={theme.accent} attributes={TextAttributes.BOLD}>
1626
- ⊟ structured output
1627
- </text>
1628
- <Show when={overflow()}>
1629
- <text fg={theme.textMuted}>
1630
- · {lineCount()} lines{collapsed() ? " · click to expand" : ""}
1631
- </text>
1632
- </Show>
1633
- </box>
1634
- <box marginTop={1}>
1635
- <code filetype="json" drawUnstyledText={false} syntaxStyle={syntax()} content={shown()} fg={theme.text} />
1636
- </box>
1637
- </box>
1638
- )
1639
- }
1640
-
1641
- function ReasoningPart(props: { last: boolean; part: ReasoningPart; message: AssistantMessage }) {
1642
- const { theme, subtleSyntax } = useTheme()
1643
- const ctx = use()
1644
- const [expanded, setExpanded] = createSignal(false)
1645
-
1646
- const content = createMemo(() => {
1647
- return props.part.text.replace("[REDACTED]", "").trim()
1648
- })
1649
- const isDone = createMemo(() => props.part.time.end !== undefined)
1650
- const inMinimal = createMemo(() => ctx.thinkingMode() === "hide")
1651
- const duration = createMemo(() => {
1652
- const end = props.part.time.end
1653
- return end === undefined ? 0 : Math.max(0, end - props.part.time.start)
1654
- })
1655
- const summary = createMemo(() => reasoningSummary(content()))
1656
-
1657
- const toggle = () => {
1658
- if (!inMinimal()) return
1659
- setExpanded((prev) => !prev)
1660
- }
1661
-
1662
- return (
1663
- <Show when={content()}>
1664
- <box id={"text-" + props.part.id} paddingLeft={3} marginTop={1} flexDirection="column" flexShrink={0}>
1665
- <box onMouseUp={toggle}>
1666
- <ReasoningHeader
1667
- toggleable={inMinimal()}
1668
- open={!inMinimal() || expanded()}
1669
- done={isDone()}
1670
- title={summary().title}
1671
- duration={isDone() ? Locale.duration(duration()) : undefined}
1672
- />
1673
- </box>
1674
- <Show when={(!inMinimal() || expanded()) && summary().body}>
1675
- <box paddingLeft={inMinimal() ? 2 : 0} marginTop={1}>
1676
- <code
1677
- filetype="markdown"
1678
- drawUnstyledText={false}
1679
- streaming={true}
1680
- syntaxStyle={subtleSyntax()}
1681
- content={summary().body}
1682
- conceal={ctx.conceal()}
1683
- fg={theme.textMuted}
1684
- />
1685
- </box>
1686
- </Show>
1687
- </box>
1688
- </Show>
1689
- )
1690
- }
1691
-
1692
- function ReasoningHeader(props: {
1693
- toggleable: boolean
1694
- open: boolean
1695
- done: boolean
1696
- title: string | null
1697
- duration?: string
1698
- }) {
1699
- const { theme } = useTheme()
1700
- const fg = () =>
1701
- props.open
1702
- ? RGBA.fromValues(theme.warning.r, theme.warning.g, theme.warning.b, theme.thinkingOpacity)
1703
- : theme.warning
1704
-
1705
- return (
1706
- <Switch>
1707
- <Match when={!props.done}>
1708
- <box flexDirection="row">
1709
- <Spinner color={fg()}>{props.title ? "Thinking: " + props.title : "Thinking"}</Spinner>
1710
- </box>
1711
- </Match>
1712
- <Match when={true}>
1713
- <text fg={fg()} wrapMode="none">
1714
- <Show when={props.toggleable}>
1715
- <span>{props.open ? "- " : "+ "}</span>
1716
- </Show>
1717
- <span>Thought</span>
1718
- <Show when={props.title || props.duration}>
1719
- <span>: </span>
1720
- </Show>
1721
- <Show when={props.title}>
1722
- <span>{props.title}</span>
1723
- </Show>
1724
- <Show when={props.duration}>
1725
- <span>
1726
- {props.title ? " · " : ""}
1727
- {props.duration}
1728
- </span>
1729
- </Show>
1730
- </text>
1731
- </Match>
1732
- </Switch>
1733
- )
1734
- }
1735
-
1736
- function TextPart(props: { last: boolean; part: TextPart; message: AssistantMessage }) {
1737
- const ctx = use()
1738
- const { theme, syntax } = useTheme()
1739
- return (
1740
- <Show when={props.part.text.trim()}>
1741
- <box id={"text-" + props.part.id} paddingLeft={3} marginTop={1} flexShrink={0}>
1742
- <Switch>
1743
- <Match when={Flag.SLEEPYCODE_EXPERIMENTAL_MARKDOWN}>
1744
- <markdown
1745
- syntaxStyle={syntax()}
1746
- streaming={true}
1747
- content={props.part.text.trim()}
1748
- conceal={ctx.conceal()}
1749
- fg={theme.markdownText}
1750
- bg={theme.background}
1751
- />
1752
- </Match>
1753
- <Match when={!Flag.SLEEPYCODE_EXPERIMENTAL_MARKDOWN}>
1754
- <code
1755
- filetype="markdown"
1756
- drawUnstyledText={false}
1757
- streaming={true}
1758
- syntaxStyle={syntax()}
1759
- content={props.part.text.trim()}
1760
- conceal={ctx.conceal()}
1761
- fg={theme.text}
1762
- />
1763
- </Match>
1764
- </Switch>
1765
- </box>
1766
- </Show>
1767
- )
1768
- }
1769
-
1770
- // Pending messages moved to individual tool pending functions
1771
-
1772
- function ToolPart(props: { last: boolean; part: ToolPart; message: AssistantMessage }) {
1773
- const ctx = use()
1774
- const sync = useSync()
1775
-
1776
- // Hide tool if showDetails is false and tool completed successfully
1777
- const shouldHide = createMemo(() => {
1778
- if (ctx.showDetails()) return false
1779
- if (props.part.state.status !== "completed") return false
1780
- return true
1781
- })
1782
-
1783
- const toolprops = {
1784
- get metadata() {
1785
- return props.part.state.status === "pending" ? {} : (props.part.state.metadata ?? {})
1786
- },
1787
- get input() {
1788
- return props.part.state.input ?? {}
1789
- },
1790
- get output() {
1791
- return props.part.state.status === "completed" ? props.part.state.output : undefined
1792
- },
1793
- get permission() {
1794
- const permissions = sync.data.permission[props.message.sessionID] ?? []
1795
- const permissionIndex = permissions.findIndex((x) => x.tool?.callID === props.part.callID)
1796
- return permissions[permissionIndex]
1797
- },
1798
- get tool() {
1799
- return props.part.tool
1800
- },
1801
- get part() {
1802
- return props.part
1803
- },
1804
- }
1805
-
1806
- return (
1807
- <Show when={!shouldHide()}>
1808
- <Switch>
1809
- <Match when={props.part.tool === "bash"}>
1810
- <Bash {...toolprops} />
1811
- </Match>
1812
- <Match when={props.part.tool === "glob"}>
1813
- <Glob {...toolprops} />
1814
- </Match>
1815
- <Match when={props.part.tool === "read"}>
1816
- <Read {...toolprops} />
1817
- </Match>
1818
- <Match when={props.part.tool === "grep"}>
1819
- <Grep {...toolprops} />
1820
- </Match>
1821
- <Match when={props.part.tool === "webfetch"}>
1822
- <WebFetch {...toolprops} />
1823
- </Match>
1824
- <Match when={props.part.tool === "codesearch"}>
1825
- <CodeSearch {...toolprops} />
1826
- </Match>
1827
- <Match when={props.part.tool === "websearch"}>
1828
- <WebSearch {...toolprops} />
1829
- </Match>
1830
- <Match when={props.part.tool === "write"}>
1831
- <Write {...toolprops} />
1832
- </Match>
1833
- <Match when={props.part.tool === "edit"}>
1834
- <Edit {...toolprops} />
1835
- </Match>
1836
- <Match when={props.part.tool === "actor"}>
1837
- <Task {...toolprops} />
1838
- </Match>
1839
- <Match when={props.part.tool === "task"}>
1840
- <WorkItemTask {...toolprops} />
1841
- </Match>
1842
- <Match when={props.part.tool === "apply_patch"}>
1843
- <ApplyPatch {...toolprops} />
1844
- </Match>
1845
- <Match when={props.part.tool === "question"}>
1846
- <Question {...toolprops} />
1847
- </Match>
1848
- <Match when={props.part.tool === "skill"}>
1849
- <Skill {...toolprops} />
1850
- </Match>
1851
- <Match when={props.part.tool === "workflow"}>
1852
- <Workflow {...toolprops} />
1853
- </Match>
1854
- <Match when={props.part.tool === "plan_exit"}>
1855
- <PlanExit {...toolprops} />
1856
- </Match>
1857
- <Match when={true}>
1858
- <GenericTool {...toolprops} />
1859
- </Match>
1860
- </Switch>
1861
- </Show>
1862
- )
1863
- }
1864
-
1865
- type ToolProps<T> = {
1866
- input: Partial<Tool.InferParameters<T>>
1867
- metadata: Partial<Tool.InferMetadata<T>>
1868
- permission: Record<string, any>
1869
- tool: string
1870
- output?: string
1871
- part: ToolPart
1872
- }
1873
- function PlanExit(props: ToolProps<any>) {
1874
- const { theme } = useTheme()
1875
- const dismissed = createMemo(
1876
- () => props.part.state.status === "completed" && props.part.state.metadata?.switched === false,
1877
- )
1878
- const feedback = createMemo(() => (dismissed() ? props.metadata.feedback : undefined))
1879
-
1880
- return (
1881
- <>
1882
- <InlineTool icon="⚙" pending="Asking..." complete={true} part={props.part} dismissed={dismissed()}>
1883
- plan_exit
1884
- </InlineTool>
1885
- <Show when={feedback()}>
1886
- <box paddingLeft={6}>
1887
- <text fg={theme.textMuted}>{feedback()}</text>
1888
- </box>
1889
- </Show>
1890
- </>
1891
- )
1892
- }
1893
-
1894
- function GenericTool(props: ToolProps<any>) {
1895
- const { theme } = useTheme()
1896
- const ctx = use()
1897
- const output = createMemo(() => props.output?.trim() ?? "")
1898
- const [expanded, setExpanded] = createSignal(false)
1899
- const lines = createMemo(() => output().split("\n"))
1900
- const maxLines = 3
1901
- const overflow = createMemo(() => lines().length > maxLines)
1902
- const limited = createMemo(() => {
1903
- if (expanded() || !overflow()) return output()
1904
- return [...lines().slice(0, maxLines), "…"].join("\n")
1905
- })
1906
-
1907
- return (
1908
- <Show
1909
- when={props.output && ctx.showGenericToolOutput()}
1910
- fallback={
1911
- <InlineTool icon="⚙" pending="Writing command..." complete={true} part={props.part}>
1912
- {props.tool} {input(props.input)}
1913
- </InlineTool>
1914
- }
1915
- >
1916
- <BlockTool
1917
- title={`# ${props.tool} ${input(props.input)}`}
1918
- part={props.part}
1919
- onClick={overflow() ? () => setExpanded((prev) => !prev) : undefined}
1920
- >
1921
- <box gap={1}>
1922
- <text fg={theme.text}>{limited()}</text>
1923
- <Show when={overflow()}>
1924
- <text fg={theme.textMuted}>{expanded() ? "Click to collapse" : "Click to expand"}</text>
1925
- </Show>
1926
- </box>
1927
- </BlockTool>
1928
- </Show>
1929
- )
1930
- }
1931
-
1932
- // Inline renderer for the work-item `task` tool (distinct from <Task>, which
1933
- // renders the subagent-spawning `actor` tool). Shows the operation as a concise
1934
- // one-liner derived from the nested `{ operation: { action } }` discriminator,
1935
- // so task create/start/done don't fall through to GenericTool's raw-JSON dump.
1936
- function WorkItemTask(props: ToolProps<typeof TaskTool>) {
1937
- const summary = createMemo(() => {
1938
- const op = (props.input as { operation?: Record<string, any> }).operation
1939
- if (!op || typeof op !== "object") return "task"
1940
- const verb = typeof op.action === "string" ? op.action : "task"
1941
- if (verb === "create") return op.summary ? `create "${op.summary}"` : "create"
1942
- if (verb === "list") return op.status ? `list ${op.status}` : "list"
1943
- if (op.id) return `${verb} ${op.id}`
1944
- return verb
1945
- })
1946
- return (
1947
- <InlineTool icon="#" pending="Updating tasks..." complete={true} part={props.part}>
1948
- task {summary()}
1949
- </InlineTool>
1950
- )
1951
- }
1952
-
1953
- // Inline renderer for the dynamic-workflow `workflow` tool. The "run" op blocks
1954
- // until terminal and streams a transcript (phase transitions + log() messages)
1955
- // into part-state metadata via ctx.metadata; the tool's `Workflow` view here
1956
- // reads metadata.transcript reactively (each ctx.metadata call fires a
1957
- // message.part.delta) and renders it as multi-line chat content alongside the
1958
- // live header from sync.data.workflow[runID]. That way phase/log events show
1959
- // up in the main agent's conversation as the workflow runs, not as a single
1960
- // silent line that only updates once the run finishes.
1961
- function Workflow(props: ToolProps<typeof WorkflowTool>) {
1962
- const sync = useSync()
1963
- const fullRoute = useRoute()
1964
-
1965
- const operation = createMemo(() => {
1966
- const op = (props.input as { operation?: string }).operation
1967
- return typeof op === "string" ? op : "run"
1968
- })
1969
-
1970
- const runID = createMemo(
1971
- () => (props.metadata.runID as string | undefined) ?? (props.input as { run_id?: string }).run_id,
1972
- )
1973
-
1974
- const run = createMemo(() => {
1975
- const id = runID()
1976
- if (!id) return undefined
1977
- return sync.data.workflow[id]
1978
- })
1979
-
1980
- // Spinner is true while EITHER side reports running — the tool part stays
1981
- // running until execute() returns (the whole workflow duration, since we
1982
- // block), and the bus-fed run row independently reports "running" until the
1983
- // workflow.finished event lands. Either signal alone is enough.
1984
- const isRunning = createMemo(() => {
1985
- if (props.part.state.status === "running") return true
1986
- const r = run()
1987
- return r?.status === "running"
1988
- })
1989
-
1990
- const transcript = createMemo(() => {
1991
- const t = (props.metadata as { transcript?: { kind: "phase" | "log"; text: string }[] }).transcript
1992
- return Array.isArray(t) ? t : []
1993
- })
1994
-
1995
- const name = createMemo(() => run()?.name ?? (props.input as { name?: string }).name ?? "inline")
1996
- const status = createMemo(() => run()?.status ?? (props.metadata.status as string | undefined))
1997
-
1998
- // Counters/phase prefer the live metadata streamed by the tool's 250ms flush
1999
- // loop, falling back to the bus run row. The bus row only learns counters via
2000
- // loadWorkflows polling (which only the /workflows dialog runs), so during a run
2001
- // the inline panel would otherwise sit at 0✓ 0✗ 0⟳ — the streamed metadata is
2002
- // the authoritative live source for this in-conversation view.
2003
- const counters = createMemo(() => {
2004
- const m = (props.metadata as { counters?: { running: number; succeeded: number; failed: number } }).counters
2005
- if (m) return m
2006
- const r = run()
2007
- return r ? { running: r.running, succeeded: r.succeeded, failed: r.failed } : undefined
2008
- })
2009
- const currentPhase = createMemo(
2010
- () => (props.metadata as { currentPhase?: string }).currentPhase ?? run()?.currentPhase,
2011
- )
2012
-
2013
- // Non-"run" ops (status/wait/cancel/resume) are one-shot control calls with no
2014
- // live transcript — keep them as a compact inline line.
2015
- return (
2016
- <Show when={operation() === "run"} fallback={
2017
- <InlineTool icon="⚡" spinner={isRunning()} pending="Starting workflow..." complete={true} part={props.part}>
2018
- {`workflow ${operation()}${runID() ? ` ${runID()}` : ""}`}
2019
- </InlineTool>
2020
- }>
2021
- <WorkflowPanel
2022
- name={name()}
2023
- status={status()}
2024
- counters={counters()}
2025
- currentPhase={currentPhase()}
2026
- transcript={transcript()}
2027
- running={isRunning()}
2028
- part={props.part}
2029
- onOpen={
2030
- runID() && fullRoute.data.type === "session"
2031
- ? () => {
2032
- const d = fullRoute.data
2033
- if (d.type === "session") fullRoute.navigate({ ...d, workflowRunID: runID() })
2034
- }
2035
- : undefined
2036
- }
2037
- />
2038
- </Show>
2039
- )
2040
- }
2041
-
2042
- // Bold panel for a `workflow run`. The transcript (phase + log lines streamed
2043
- // every 250ms into part metadata) is the run's live activity — agents spawning,
2044
- // per-source hits, facts checked. The old renderer dumped it all as one muted-
2045
- // gray InlineTool blob, so a busy run read as "stuck". Here phases are bold
2046
- // accent section headers, logs render in readable text, and a running run shows
2047
- // a spinner on its current phase so progress is always visible. Bounded to the
2048
- // last N lines in the conversation flow; full history lives in the detail dialog.
2049
- const WORKFLOW_PANEL_TAIL = 12
2050
-
2051
- // WorkflowPage is conditionally rendered (fallback of the conversation Show), so it
2052
- // fully unmounts when you navigate into a subagent and remounts on return — which
2053
- // would reset its scrollbox to the top. Remember the last scroll offset per runID
2054
- // here so returning restores the position, like the persistent conversation scroll.
2055
- const workflowScrollByRun = new Map<string, number>()
2056
- function WorkflowPanel(props: {
2057
- name: string
2058
- status?: string
2059
- counters?: { succeeded: number; failed: number; running: number }
2060
- currentPhase?: string
2061
- transcript: { kind: "phase" | "log"; text: string }[]
2062
- running: boolean
2063
- part: ToolPart
2064
- onOpen?: () => void
2065
- }) {
2066
- const { theme } = useTheme()
2067
- const renderer = useRenderer()
2068
- const [collapsed, setCollapsed] = createSignal(false)
2069
- const [openHover, setOpenHover] = createSignal(false)
2070
-
2071
- const statusColor = createMemo(() => {
2072
- const s = props.status
2073
- if (s === "completed") return theme.success
2074
- if (s === "failed") return theme.error
2075
- if (s === "cancelled") return theme.textMuted
2076
- return theme.warning
2077
- })
2078
-
2079
- const hiddenCount = createMemo(() => Math.max(0, props.transcript.length - WORKFLOW_PANEL_TAIL))
2080
- const entries = createMemo(() => (collapsed() ? [] : props.transcript.slice(-WORKFLOW_PANEL_TAIL)))
2081
-
2082
- return (
2083
- <box
2084
- border={["left"]}
2085
- paddingTop={1}
2086
- paddingBottom={1}
2087
- paddingLeft={2}
2088
- marginTop={1}
2089
- gap={1}
2090
- backgroundColor={theme.backgroundPanel}
2091
- customBorderChars={SplitBorder.customBorderChars}
2092
- borderColor={statusColor()}
2093
- onMouseUp={() => {
2094
- if (renderer.getSelection()?.getSelectedText()) return
2095
- setCollapsed((p) => !p)
2096
- }}
2097
- >
2098
- <box flexDirection="row" gap={1} paddingLeft={3}>
2099
- <Show when={props.running} fallback={<text fg={theme.accent} attributes={TextAttributes.BOLD}>⚡</text>}>
2100
- <spinner frames={["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]} interval={80} color={theme.accent} />
2101
- </Show>
2102
- <text attributes={TextAttributes.BOLD} fg={theme.accent}>
2103
- {props.name}
2104
- </text>
2105
- <Show when={props.status}>
2106
- <text fg={statusColor()} attributes={TextAttributes.BOLD}>
2107
- {props.status}
2108
- </text>
2109
- </Show>
2110
- <Show when={props.currentPhase}>
2111
- <text fg={theme.textMuted}>· {props.currentPhase}</text>
2112
- </Show>
2113
- <Show when={props.counters}>
2114
- <text fg={theme.success}>{props.counters!.succeeded}✓</text>
2115
- <text fg={props.counters!.failed > 0 ? theme.error : theme.textMuted}>{props.counters!.failed}✗</text>
2116
- <text fg={props.counters!.running > 0 ? theme.warning : theme.textMuted}>{props.counters!.running}⟳</text>
2117
- </Show>
2118
- <Show when={props.onOpen}>
2119
- <box flexGrow={1} />
2120
- <text
2121
- fg={openHover() ? theme.text : theme.markdownLink}
2122
- onMouseOver={() => setOpenHover(true)}
2123
- onMouseOut={() => setOpenHover(false)}
2124
- onMouseUp={(evt) => {
2125
- evt.stopPropagation()
2126
- if (renderer.getSelection()?.getSelectedText()) return
2127
- props.onOpen?.()
2128
- }}
2129
- >
2130
- open ↗
2131
- </text>
2132
- </Show>
2133
- </box>
2134
- <Show
2135
- when={!collapsed()}
2136
- fallback={
2137
- <text paddingLeft={3} fg={theme.textMuted}>
2138
- {props.transcript.length} lines · click to expand
2139
- </text>
2140
- }
2141
- >
2142
- <box paddingLeft={3}>
2143
- <Show when={hiddenCount() > 0}>
2144
- <text fg={theme.textMuted}>+{hiddenCount()} earlier lines · open detail for full history</text>
2145
- </Show>
2146
- <For each={entries()}>
2147
- {(e) => (
2148
- <Show
2149
- when={e.kind === "phase"}
2150
- fallback={<text fg={theme.text}>{e.text}</text>}
2151
- >
2152
- <text fg={theme.accent} attributes={TextAttributes.BOLD}>
2153
- ▸ {e.text}
2154
- </text>
2155
- </Show>
2156
- )}
2157
- </For>
2158
- </box>
2159
- </Show>
2160
- </box>
2161
- )
2162
- }
2163
-
2164
- // Full-screen workflow detail page: occupies the conversation column (like a
2165
- // subagent view) and shows one run's structure tree + transcript, live while
2166
- // running. An agent row navigates to that subagent's full conversation; a nested
2167
- // workflow row drills into the child run; "Main" returns to the conversation.
2168
- function WorkflowPage(props: {
2169
- runID: string
2170
- onBack: () => void
2171
- onOpenAgent: (actorID: string) => void
2172
- onOpenChild: (childRunID: string) => void
2173
- }) {
2174
- const sync = useSync()
2175
- const dialog = useDialog()
2176
- const keybind = useKeybind()
2177
- const { theme } = useTheme()
2178
- const renderer = useRenderer()
2179
- const tuiConfig = useTuiConfig()
2180
- const scrollAcceleration = createMemo(() => getScrollAcceleration(tuiConfig))
2181
- let pageScroll: ScrollBoxRenderable | undefined
2182
-
2183
- const run = createMemo(() => sync.data.workflow[props.runID])
2184
-
2185
- // Describe what a running subagent is currently doing, from its live message
2186
- // stream (last message's last meaningful part): a tool call → "⚙ <tool>", else
2187
- // the latest text snippet. Returns undefined when nothing's streamed yet.
2188
- const liveActivity = (actorID: string): string | undefined => {
2189
- const sid = run()?.sessionID
2190
- if (!sid) return undefined
2191
- const msgs = sync.data.message[sid]?.[actorID]
2192
- const last = msgs?.[msgs.length - 1]
2193
- if (!last) return undefined
2194
- const parts = sync.data.part[last.id] ?? []
2195
- for (let i = parts.length - 1; i >= 0; i--) {
2196
- const p = parts[i] as { type?: string; tool?: string; text?: string }
2197
- if (p.type === "tool" && p.tool) return `⚙ ${p.tool}`
2198
- if (p.type === "text" && p.text) return p.text
2199
- }
2200
- return undefined
2201
- }
2202
- const transcript = createMemo(() => sync.data.workflowTranscript[props.runID] ?? [])
2203
- const structure = createMemo(() => sync.data.workflowStructure[props.runID] ?? [])
2204
-
2205
- // Keyed on props.runID so a parent→child sub-workflow navigation (which does NOT
2206
- // remount this component, since the <Show> fallback stays mounted while
2207
- // workflowRunID is merely a different value) re-loads the new run's data, restores
2208
- // its scroll, and re-arms the poll. The effect's onCleanup runs with `runID` bound
2209
- // to the run being LEFT, so it saves that run's scroll under the correct key —
2210
- // unlike reading props.runID at unmount, which is already stale.
2211
- createEffect(
2212
- on(
2213
- () => props.runID,
2214
- (runID) => {
2215
- sync.loadWorkflowTranscript(runID)
2216
- sync.loadWorkflowStructure(runID)
2217
- // Restore the scroll position saved when we last left this run's page.
2218
- // Deferred + retried so the cards (from the persisted structure store) are
2219
- // laid out first, giving scrollTop a real range to land within.
2220
- const saved = workflowScrollByRun.get(runID)
2221
- if (saved) {
2222
- let tries = 0
2223
- const restore = () => {
2224
- if (pageScroll) pageScroll.scrollTop = saved
2225
- if (++tries < 5 && (pageScroll?.scrollTop ?? 0) < saved - 1) setTimeout(restore, 60)
2226
- }
2227
- setTimeout(restore, 0)
2228
- } else if (pageScroll) {
2229
- pageScroll.scrollTop = 0
2230
- }
2231
- const interval = setInterval(() => {
2232
- sync.loadWorkflowStructure(runID)
2233
- sync.loadWorkflowTranscript(runID)
2234
- }, 1000)
2235
- onCleanup(() => {
2236
- clearInterval(interval)
2237
- if (pageScroll) workflowScrollByRun.set(runID, pageScroll.scrollTop)
2238
- })
2239
- },
2240
- ),
2241
- )
2242
-
2243
- const statusColor = createMemo(() => {
2244
- const s = run()?.status
2245
- if (s === "completed") return theme.success
2246
- if (s === "failed") return theme.error
2247
- if (s === "cancelled") return theme.textMuted
2248
- return theme.warning
2249
- })
2250
-
2251
- const resumable = createMemo(() => {
2252
- const s = run()?.status
2253
- return s === "running" || s === "failed" || s === "cancelled"
2254
- })
2255
- const resume = async () => {
2256
- const ok = await DialogConfirm.show(
2257
- dialog,
2258
- "Resume workflow",
2259
- `Re-run "${run()?.name ?? props.runID}"? This re-executes the workflow and may incur cost.`,
2260
- )
2261
- if (ok === true) void sync.resumeWorkflow(props.runID)
2262
- }
2263
-
2264
- return (
2265
- <box flexGrow={1} gap={1}>
2266
- <box flexDirection="row" gap={1} flexShrink={0}>
2267
- <text
2268
- fg={theme.text}
2269
- onMouseUp={() => {
2270
- if (renderer.getSelection()?.getSelectedText()) return
2271
- props.onBack()
2272
- }}
2273
- >
2274
- ‹ Main <span style={{ fg: theme.textMuted }}>{keybind.print("session_parent")}</span>
2275
- </text>
2276
- <box flexGrow={1} />
2277
- <text attributes={TextAttributes.BOLD} fg={theme.accent}>
2278
- {run()?.name ?? props.runID}
2279
- </text>
2280
- <Show when={run()?.status}>
2281
- <text attributes={TextAttributes.BOLD} fg={statusColor()}>
2282
- {run()!.status}
2283
- </text>
2284
- </Show>
2285
- </box>
2286
- <Show when={run()}>
2287
- <box flexDirection="row" gap={1} flexShrink={0}>
2288
- <Show when={run()!.currentPhase}>
2289
- <text fg={theme.textMuted}>{run()!.currentPhase}</text>
2290
- </Show>
2291
- <text fg={theme.success}>{run()!.succeeded}✓</text>
2292
- <text fg={run()!.failed > 0 ? theme.error : theme.textMuted}>{run()!.failed}✗</text>
2293
- <text fg={run()!.running > 0 ? theme.warning : theme.textMuted}>{run()!.running}⟳</text>
2294
- <Show when={resumable()}>
2295
- <text fg={theme.markdownLink} onMouseUp={() => void resume()}>
2296
- ↻ resume
2297
- </text>
2298
- </Show>
2299
- </box>
2300
- </Show>
2301
- <scrollbox
2302
- ref={(r) => (pageScroll = r)}
2303
- flexGrow={1}
2304
- scrollAcceleration={scrollAcceleration()}
2305
- >
2306
- <WorkflowTree
2307
- nodes={structure()}
2308
- onOpenChild={props.onOpenChild}
2309
- onOpenAgent={props.onOpenAgent}
2310
- liveActivity={liveActivity}
2311
- />
2312
- <Show when={transcript().length > 0}>
2313
- <box paddingTop={1}>
2314
- <text fg={theme.textMuted}>transcript</text>
2315
- <For each={transcript()}>
2316
- {(e) => (
2317
- <Show when={e.kind === "phase"} fallback={<text fg={theme.text}>{e.text}</text>}>
2318
- <text attributes={TextAttributes.BOLD} fg={theme.accent}>
2319
- ▸ {e.text}
2320
- </text>
2321
- </Show>
2322
- )}
2323
- </For>
2324
- </box>
2325
- </Show>
2326
- <Show when={run()?.error}>
2327
- <text fg={theme.error}>{run()!.error}</text>
2328
- </Show>
2329
- </scrollbox>
2330
- </box>
2331
- )
2332
- }
2333
-
2334
- function CollapsibleError(props: { error: string; paddingLeft?: number }) {
2335
- const { theme } = useTheme()
2336
- const renderer = useRenderer()
2337
- const [expanded, setExpanded] = createSignal(false)
2338
-
2339
-
2340
- const lineCount = createMemo(() => props.error.split("\n").length)
2341
-
2342
- return (
2343
- <box
2344
- paddingLeft={props.paddingLeft}
2345
- onMouseUp={(evt) => {
2346
- evt.stopPropagation()
2347
- if (renderer.getSelection()?.getSelectedText()) return
2348
- setExpanded((prev) => !prev)
2349
- }}
2350
- >
2351
- <Show
2352
- when={expanded()}
2353
- fallback={
2354
- <text fg={theme.error}>
2355
- + Error ({lineCount()} {lineCount() === 1 ? "line" : "lines"})
2356
- </text>
2357
- }
2358
- >
2359
- <text fg={theme.error}>- Error</text>
2360
- <box paddingLeft={2}>
2361
- <text fg={theme.error}>{props.error}</text>
2362
- </box>
2363
- </Show>
2364
- </box>
2365
- )
2366
- }
2367
-
2368
- function InlineTool(props: {
2369
- icon: string
2370
- iconColor?: RGBA
2371
- complete: any
2372
- pending: string
2373
- spinner?: boolean
2374
- dismissed?: boolean
2375
- children: JSX.Element
2376
- part: ToolPart
2377
- onClick?: () => void
2378
- }) {
2379
- const [margin, setMargin] = createSignal(0)
2380
- const { theme } = useTheme()
2381
- const ctx = use()
2382
- const sync = useSync()
2383
- const renderer = useRenderer()
2384
- const [hover, setHover] = createSignal(false)
2385
-
2386
- const permission = createMemo(() => {
2387
- const callID = sync.data.permission[ctx.sessionID]?.at(0)?.tool?.callID
2388
- if (!callID) return false
2389
- return callID === props.part.callID
2390
- })
2391
-
2392
- const fg = createMemo(() => {
2393
- if (permission()) return theme.warning
2394
- if (hover() && props.onClick) return theme.text
2395
- if (props.complete) return theme.textMuted
2396
- return theme.text
2397
- })
2398
-
2399
- const error = createMemo(() => (props.part.state.status === "error" ? props.part.state.error : undefined))
2400
-
2401
- const denied = createMemo(
2402
- () =>
2403
- error()?.includes("QuestionRejectedError") ||
2404
- error()?.includes("rejected permission") ||
2405
- error()?.includes("specified a rule") ||
2406
- error()?.includes("user dismissed"),
2407
- )
2408
-
2409
- // Agent-recoverable failures (bad args, malformed call, unknown task/actor id)
2410
- // are flagged on the error state. Render them muted (struck through, no red
2411
- // block) like denials — the agent self-corrects; the user needn't be alarmed.
2412
- const recoverable = createMemo(() => {
2413
- const state = props.part.state
2414
- return state.status === "error" && state.metadata?.recoverable === true
2415
- })
2416
-
2417
- return (
2418
- <box
2419
- marginTop={margin()}
2420
- paddingLeft={3}
2421
- onMouseOver={() => props.onClick && setHover(true)}
2422
- onMouseOut={() => setHover(false)}
2423
- onMouseUp={() => {
2424
- if (renderer.getSelection()?.getSelectedText()) return
2425
- props.onClick?.()
2426
- }}
2427
- renderBefore={function () {
2428
- const el = this as BoxRenderable
2429
- const parent = el.parent
2430
- if (!parent) {
2431
- return
2432
- }
2433
- if (el.height > 1) {
2434
- setMargin(1)
2435
- return
2436
- }
2437
- const children = parent.getChildren()
2438
- const index = children.indexOf(el)
2439
- const previous = children[index - 1]
2440
- if (!previous) {
2441
- setMargin(0)
2442
- return
2443
- }
2444
- if (previous.height > 1 || previous.id.startsWith("text-")) {
2445
- setMargin(1)
2446
- return
2447
- }
2448
- }}
2449
- >
2450
- <Switch>
2451
- <Match when={props.spinner}>
2452
- <Spinner color={fg()} children={props.children} />
2453
- </Match>
2454
- <Match when={true}>
2455
- <text paddingLeft={3} fg={fg()} attributes={denied() || recoverable() || props.dismissed ? TextAttributes.STRIKETHROUGH : undefined}>
2456
- <Show fallback={<>~ {props.pending}</>} when={props.complete}>
2457
- <span style={{ fg: props.iconColor }}>{props.icon}</span> {props.children}
2458
- </Show>
2459
- </text>
2460
- </Match>
2461
- </Switch>
2462
- <Show when={error() && !denied() && !recoverable()}>
2463
- <CollapsibleError error={error()!} paddingLeft={3} />
2464
- </Show>
2465
- </box>
2466
- )
2467
- }
2468
-
2469
- function BlockTool(props: {
2470
- title: string
2471
- children: JSX.Element
2472
- onClick?: () => void
2473
- part?: ToolPart
2474
- spinner?: boolean
2475
- }) {
2476
- const { theme } = useTheme()
2477
- const renderer = useRenderer()
2478
- const [hover, setHover] = createSignal(false)
2479
- const error = createMemo(() => (props.part?.state.status === "error" ? props.part.state.error : undefined))
2480
- return (
2481
- <box
2482
- border={["left"]}
2483
- paddingTop={1}
2484
- paddingBottom={1}
2485
- paddingLeft={2}
2486
- marginTop={1}
2487
- gap={1}
2488
- backgroundColor={hover() ? theme.backgroundMenu : theme.backgroundPanel}
2489
- customBorderChars={SplitBorder.customBorderChars}
2490
- borderColor={theme.background}
2491
- onMouseOver={() => props.onClick && setHover(true)}
2492
- onMouseOut={() => setHover(false)}
2493
- onMouseUp={() => {
2494
- if (renderer.getSelection()?.getSelectedText()) return
2495
- props.onClick?.()
2496
- }}
2497
- >
2498
- <Show
2499
- when={props.spinner}
2500
- fallback={
2501
- <text paddingLeft={3} fg={theme.textMuted}>
2502
- {props.title}
2503
- </text>
2504
- }
2505
- >
2506
- <Spinner color={theme.textMuted}>{props.title.replace(/^# /, "")}</Spinner>
2507
- </Show>
2508
- {props.children}
2509
- <Show when={error()}>
2510
- <CollapsibleError error={error()!} />
2511
- </Show>
2512
- </box>
2513
- )
2514
- }
2515
-
2516
- const TOOL_COLLAPSE_MAX_LINES = 3
2517
- const TOOL_COLLAPSE_MAX_LINE_LENGTH = 120
2518
-
2519
- function displayLines(content: string) {
2520
- if (!content) return []
2521
- return content.replace(/\n$/, "").split("\n")
2522
- }
2523
-
2524
- function hasLongDisplayLine(content: string) {
2525
- return displayLines(content).some((line) => line.length > TOOL_COLLAPSE_MAX_LINE_LENGTH)
2526
- }
2527
-
2528
- function Bash(props: ToolProps<typeof BashTool>) {
2529
- const { theme } = useTheme()
2530
- const sync = useSync()
2531
- const isRunning = createMemo(() => props.part.state.status === "running")
2532
- const output = createMemo(() => stripAnsi(props.metadata.output?.trim() ?? ""))
2533
- const [expanded, setExpanded] = createSignal(false)
2534
- const lines = createMemo(() => output().split("\n"))
2535
- const overflow = createMemo(() => lines().length > 10)
2536
- const limited = createMemo(() => {
2537
- if (expanded() || !overflow()) return output()
2538
- return [...lines().slice(0, 10), "…"].join("\n")
2539
- })
2540
-
2541
- const workdirDisplay = createMemo(() => {
2542
- const workdir = props.input.workdir
2543
- if (!workdir || workdir === ".") return undefined
2544
-
2545
- const base = sync.path.directory
2546
- if (!base) return undefined
2547
-
2548
- const absolute = path.resolve(base, workdir)
2549
- if (absolute === base) return undefined
2550
-
2551
- const home = Global.Path.home
2552
- if (!home) return absolute
2553
-
2554
- const match = absolute === home || absolute.startsWith(home + path.sep)
2555
- return match ? absolute.replace(home, "~") : absolute
2556
- })
2557
-
2558
- const title = createMemo(() => {
2559
- const desc = props.input.description ?? "Shell"
2560
- const wd = workdirDisplay()
2561
- if (!wd) return `# ${desc}`
2562
- if (desc.includes(wd)) return `# ${desc}`
2563
- return `# ${desc} in ${wd}`
2564
- })
2565
-
2566
- return (
2567
- <Switch>
2568
- <Match when={props.metadata.output !== undefined}>
2569
- <BlockTool
2570
- title={title()}
2571
- part={props.part}
2572
- spinner={isRunning()}
2573
- onClick={overflow() ? () => setExpanded((prev) => !prev) : undefined}
2574
- >
2575
- <box gap={1}>
2576
- <text fg={theme.text}>$ {props.input.command}</text>
2577
- <Show when={output()}>
2578
- <text fg={theme.text}>{limited()}</text>
2579
- </Show>
2580
- <Show when={overflow()}>
2581
- <text fg={theme.textMuted}>{expanded() ? "Click to collapse" : "Click to expand"}</text>
2582
- </Show>
2583
- </box>
2584
- </BlockTool>
2585
- </Match>
2586
- <Match when={true}>
2587
- <InlineTool icon="$" pending="Writing command..." complete={props.input.command} part={props.part}>
2588
- {props.input.command}
2589
- </InlineTool>
2590
- </Match>
2591
- </Switch>
2592
- )
2593
- }
2594
-
2595
- function Write(props: ToolProps<typeof WriteTool>) {
2596
- const { theme, syntax } = useTheme()
2597
- const [expanded, setExpanded] = createSignal(false)
2598
- const code = createMemo(() => {
2599
- if (!props.input.content) return ""
2600
- return props.input.content
2601
- })
2602
- const lineCount = createMemo(() => displayLines(code()).length)
2603
- const collapsed = createMemo(() => lineCount() > TOOL_COLLAPSE_MAX_LINES || hasLongDisplayLine(code()))
2604
-
2605
- return (
2606
- <Switch>
2607
- <Match when={props.metadata.diagnostics !== undefined}>
2608
- <BlockTool
2609
- title={"# Wrote " + normalizePath(props.input.file_path!)}
2610
- part={props.part}
2611
- onClick={collapsed() ? () => setExpanded((prev) => !prev) : undefined}
2612
- >
2613
- <Show
2614
- when={!collapsed() || expanded()}
2615
- fallback={
2616
- <text fg={theme.textMuted}>
2617
- Click to expand ({lineCount()} {lineCount() === 1 ? "line" : "lines"})
2618
- </text>
2619
- }
2620
- >
2621
- <line_number fg={theme.textMuted} minWidth={3} paddingRight={1}>
2622
- <code
2623
- conceal={false}
2624
- fg={theme.text}
2625
- filetype={filetype(props.input.file_path!)}
2626
- syntaxStyle={syntax()}
2627
- content={code()}
2628
- />
2629
- </line_number>
2630
- <Show when={collapsed()}>
2631
- <text fg={theme.textMuted}>Click to collapse</text>
2632
- </Show>
2633
- </Show>
2634
- <Diagnostics diagnostics={props.metadata.diagnostics} filePath={props.input.file_path ?? ""} />
2635
- </BlockTool>
2636
- </Match>
2637
- <Match when={true}>
2638
- <InlineTool icon="←" pending="Preparing write..." complete={props.input.file_path} part={props.part}>
2639
- Write {normalizePath(props.input.file_path!)}
2640
- </InlineTool>
2641
- </Match>
2642
- </Switch>
2643
- )
2644
- }
2645
-
2646
- function Glob(props: ToolProps<typeof GlobTool>) {
2647
- return (
2648
- <InlineTool icon="✱" pending="Finding files..." complete={props.input.pattern} part={props.part}>
2649
- Glob "{props.input.pattern}" <Show when={props.input.path}>in {normalizePath(props.input.path)} </Show>
2650
- <Show when={props.metadata.count}>
2651
- ({props.metadata.count} {props.metadata.count === 1 ? "match" : "matches"})
2652
- </Show>
2653
- </InlineTool>
2654
- )
2655
- }
2656
-
2657
- function Read(props: ToolProps<typeof ReadTool>) {
2658
- const { theme } = useTheme()
2659
- const isRunning = createMemo(() => props.part.state.status === "running")
2660
- const loaded = createMemo(() => {
2661
- if (props.part.state.status !== "completed") return []
2662
- if (props.part.state.time.compacted) return []
2663
- const value = props.metadata.loaded
2664
- if (!value || !Array.isArray(value)) return []
2665
- return value.filter((p): p is string => typeof p === "string")
2666
- })
2667
- return (
2668
- <>
2669
- <InlineTool
2670
- icon="→"
2671
- pending="Reading file..."
2672
- complete={props.input.file_path}
2673
- spinner={isRunning()}
2674
- part={props.part}
2675
- >
2676
- Read {normalizePath(props.input.file_path!)} {input(props.input, ["file_path"])}
2677
- </InlineTool>
2678
- <For each={loaded()}>
2679
- {(filepath) => (
2680
- <box paddingLeft={3}>
2681
- <text paddingLeft={3} fg={theme.textMuted}>
2682
- ↳ Loaded {normalizePath(filepath)}
2683
- </text>
2684
- </box>
2685
- )}
2686
- </For>
2687
- </>
2688
- )
2689
- }
2690
-
2691
- function Grep(props: ToolProps<typeof GrepTool>) {
2692
- return (
2693
- <InlineTool icon="✱" pending="Searching content..." complete={props.input.pattern} part={props.part}>
2694
- Grep "{props.input.pattern}" <Show when={props.input.path}>in {normalizePath(props.input.path)} </Show>
2695
- <Show when={props.metadata.matches}>
2696
- ({props.metadata.matches} {props.metadata.matches === 1 ? "match" : "matches"})
2697
- </Show>
2698
- </InlineTool>
2699
- )
2700
- }
2701
-
2702
- function WebFetch(props: ToolProps<typeof WebFetchTool>) {
2703
- return (
2704
- <InlineTool icon="%" pending="Fetching from the web..." complete={props.input.url} part={props.part}>
2705
- WebFetch {props.input.url}
2706
- </InlineTool>
2707
- )
2708
- }
2709
-
2710
- function CodeSearch(props: ToolProps<typeof CodeSearchTool>) {
2711
- const metadata = props.metadata as { results?: number }
2712
- return (
2713
- <InlineTool icon="◇" pending="Searching code..." complete={props.input.query} part={props.part}>
2714
- Exa Code Search "{props.input.query}" <Show when={metadata.results}>({metadata.results} results)</Show>
2715
- </InlineTool>
2716
- )
2717
- }
2718
-
2719
- function WebSearch(props: ToolProps<typeof WebSearchTool>) {
2720
- const metadata = props.metadata as { numResults?: number }
2721
- return (
2722
- <InlineTool icon="◈" pending="Searching web..." complete={props.input.query} part={props.part}>
2723
- Web Search "{props.input.query}" <Show when={metadata.numResults}>({metadata.numResults} results)</Show>
2724
- </InlineTool>
2725
- )
2726
- }
2727
-
2728
- function Task(props: ToolProps<typeof ActorTool>) {
2729
- const route = useRoute()
2730
- const sync = useSync()
2731
-
2732
- const input = createMemo(() => {
2733
- const raw = props.input as Partial<{ operation: { description: string; subagent_type: string } } & {
2734
- description: string
2735
- subagent_type: string
2736
- }>
2737
- return (raw?.operation ?? raw) as Partial<{ description: string; subagent_type: string }>
2738
- })
2739
-
2740
- const targetSession = createMemo(() => props.metadata.sessionId as string | undefined)
2741
- const targetBucket = createMemo(() => (props.metadata.actorId as string | undefined) ?? "main")
2742
-
2743
- createEffect(() => {
2744
- const session = targetSession()
2745
- const bucket = targetBucket()
2746
- if (session && !sync.data.message[session]?.[bucket]?.length)
2747
- void sync.session.sync(session)
2748
- })
2749
-
2750
- const messages = createMemo(() => sync.data.message[targetSession() ?? ""]?.[targetBucket()] ?? [])
2751
-
2752
- const tools = createMemo(() => {
2753
- return messages().flatMap((msg) =>
2754
- (sync.data.part[msg.id] ?? [])
2755
- .filter((part): part is ToolPart => part.type === "tool")
2756
- .map((part) => ({ tool: part.tool, state: part.state })),
2757
- )
2758
- })
2759
-
2760
- const current = createMemo(() =>
2761
- tools().findLast((x) => (x.state.status === "running" || x.state.status === "completed") && x.state.title),
2762
- )
2763
-
2764
- const isRunning = createMemo(() => props.part.state.status === "running")
2765
-
2766
- const duration = createMemo(() => {
2767
- const first = messages().find((x) => x.role === "user")?.time.created
2768
- const assistant = messages().findLast((x) => x.role === "assistant")?.time.completed
2769
- if (!first || !assistant) return 0
2770
- return assistant - first
2771
- })
2772
-
2773
- const content = createMemo(() => {
2774
- if (!input().description) return ""
2775
- let content = [`${Locale.titlecase(input().subagent_type ?? "General")} Task — ${input().description}`]
2776
-
2777
- if (isRunning() && tools().length > 0) {
2778
- // content[0] += ` · ${tools().length} toolcalls`
2779
- if (current()) {
2780
- const state = current()!.state
2781
- const title = state.status === "running" || state.status === "completed" ? state.title : undefined
2782
- content.push(`↳ ${Locale.titlecase(current()!.tool)} ${title}`)
2783
- } else content.push(`↳ ${tools().length} toolcalls`)
2784
- }
2785
-
2786
- if (props.part.state.status === "completed") {
2787
- content.push(`└ ${tools().length} toolcalls · ${Locale.duration(duration())}`)
2788
- }
2789
-
2790
- return content.join("\n")
2791
- })
2792
-
2793
- return (
2794
- <InlineTool
2795
- icon="│"
2796
- spinner={isRunning()}
2797
- complete={input().description}
2798
- pending="Delegating..."
2799
- part={props.part}
2800
- onClick={() => {
2801
- const session = targetSession()
2802
- if (!session) return
2803
- const actor = targetBucket()
2804
- if (
2805
- route.data.type === "session" &&
2806
- session === route.data.sessionID &&
2807
- actor !== "main"
2808
- ) {
2809
- route.navigate({ ...route.data, agentID: actor })
2810
- return
2811
- }
2812
- route.navigate({ type: "session", sessionID: session, agentID: actor !== "main" ? actor : undefined })
2813
- }}
2814
- >
2815
- {content()}
2816
- </InlineTool>
2817
- )
2818
- }
2819
-
2820
- function Edit(props: ToolProps<typeof EditTool>) {
2821
- const ctx = use()
2822
- const { theme, syntax } = useTheme()
2823
-
2824
- const view = createMemo(() => {
2825
- const diffStyle = ctx.tui.diff_style
2826
- if (diffStyle === "stacked") return "unified"
2827
- // Default to "auto" behavior
2828
- return ctx.width > 120 ? "split" : "unified"
2829
- })
2830
-
2831
- const ft = createMemo(() => filetype(props.input.file_path))
2832
-
2833
- const diffContent = createMemo(() => props.metadata.diff)
2834
-
2835
- return (
2836
- <Switch>
2837
- <Match when={props.metadata.diff !== undefined}>
2838
- <BlockTool title={"← Edit " + normalizePath(props.input.file_path!)} part={props.part}>
2839
- <box paddingLeft={1}>
2840
- <diff
2841
- diff={diffContent()}
2842
- view={view()}
2843
- filetype={ft()}
2844
- syntaxStyle={syntax()}
2845
- showLineNumbers={true}
2846
- width="100%"
2847
- wrapMode={ctx.diffWrapMode()}
2848
- fg={theme.text}
2849
- addedBg={theme.diffAddedBg}
2850
- removedBg={theme.diffRemovedBg}
2851
- contextBg={theme.diffContextBg}
2852
- addedSignColor={theme.diffHighlightAdded}
2853
- removedSignColor={theme.diffHighlightRemoved}
2854
- lineNumberFg={theme.diffLineNumber}
2855
- lineNumberBg={theme.diffContextBg}
2856
- addedLineNumberBg={theme.diffAddedLineNumberBg}
2857
- removedLineNumberBg={theme.diffRemovedLineNumberBg}
2858
- />
2859
- </box>
2860
- <Diagnostics diagnostics={props.metadata.diagnostics} filePath={props.input.file_path ?? ""} />
2861
- </BlockTool>
2862
- </Match>
2863
- <Match when={true}>
2864
- <InlineTool icon="←" pending="Preparing edit..." complete={props.input.file_path} part={props.part}>
2865
- Edit {normalizePath(props.input.file_path!)} {input({ replace_all: props.input.replace_all })}
2866
- </InlineTool>
2867
- </Match>
2868
- </Switch>
2869
- )
2870
- }
2871
-
2872
- function ApplyPatch(props: ToolProps<typeof ApplyPatchTool>) {
2873
- const ctx = use()
2874
- const { theme, syntax } = useTheme()
2875
- const [expanded, setExpanded] = createSignal<string[]>([])
2876
-
2877
- const files = createMemo(() => props.metadata.files ?? [])
2878
-
2879
- const view = createMemo(() => {
2880
- const diffStyle = ctx.tui.diff_style
2881
- if (diffStyle === "stacked") return "unified"
2882
- return ctx.width > 120 ? "split" : "unified"
2883
- })
2884
-
2885
- function Diff(p: { diff: string; filePath: string }) {
2886
- return (
2887
- <box paddingLeft={1}>
2888
- <diff
2889
- diff={p.diff}
2890
- view={view()}
2891
- filetype={filetype(p.filePath)}
2892
- syntaxStyle={syntax()}
2893
- showLineNumbers={true}
2894
- width="100%"
2895
- wrapMode={ctx.diffWrapMode()}
2896
- fg={theme.text}
2897
- addedBg={theme.diffAddedBg}
2898
- removedBg={theme.diffRemovedBg}
2899
- contextBg={theme.diffContextBg}
2900
- addedSignColor={theme.diffHighlightAdded}
2901
- removedSignColor={theme.diffHighlightRemoved}
2902
- lineNumberFg={theme.diffLineNumber}
2903
- lineNumberBg={theme.diffContextBg}
2904
- addedLineNumberBg={theme.diffAddedLineNumberBg}
2905
- removedLineNumberBg={theme.diffRemovedLineNumberBg}
2906
- />
2907
- </box>
2908
- )
2909
- }
2910
-
2911
- function title(file: { type: string; relativePath: string; filePath: string; deletions: number }) {
2912
- if (file.type === "delete") return "# Deleted " + file.relativePath
2913
- if (file.type === "add") return "# Created " + file.relativePath
2914
- if (file.type === "move") return "# Moved " + normalizePath(file.filePath) + " → " + file.relativePath
2915
- return "← Patched " + file.relativePath
2916
- }
2917
-
2918
- function toggle(filePath: string) {
2919
- setExpanded((prev) => (prev.includes(filePath) ? prev.filter((item) => item !== filePath) : [...prev, filePath]))
2920
- }
2921
-
2922
- return (
2923
- <Switch>
2924
- <Match when={files().length > 0}>
2925
- <For each={files()}>
2926
- {(file) => {
2927
- const open = createMemo(() => expanded().includes(file.filePath))
2928
- const count = createMemo(() => file.additions + file.deletions)
2929
- const collapsed = createMemo(() => count() > TOOL_COLLAPSE_MAX_LINES || hasLongDisplayLine(file.patch))
2930
-
2931
- return (
2932
- <BlockTool
2933
- title={title(file)}
2934
- part={props.part}
2935
- onClick={file.type !== "delete" && collapsed() ? () => toggle(file.filePath) : undefined}
2936
- >
2937
- <Show
2938
- when={file.type !== "delete"}
2939
- fallback={
2940
- <text fg={theme.diffRemoved}>
2941
- -{file.deletions} line{file.deletions !== 1 ? "s" : ""}
2942
- </text>
2943
- }
2944
- >
2945
- <Show
2946
- when={!collapsed() || open()}
2947
- fallback={
2948
- <text fg={theme.textMuted}>
2949
- Click to expand ({count()} change{count() !== 1 ? "s" : ""})
2950
- </text>
2951
- }
2952
- >
2953
- <Diff diff={file.patch} filePath={file.filePath} />
2954
- <Show when={collapsed()}>
2955
- <text fg={theme.textMuted}>Click to collapse</text>
2956
- </Show>
2957
- </Show>
2958
- <Diagnostics diagnostics={props.metadata.diagnostics} filePath={file.movePath ?? file.filePath} />
2959
- </Show>
2960
- </BlockTool>
2961
- )
2962
- }}
2963
- </For>
2964
- </Match>
2965
- <Match when={true}>
2966
- <InlineTool icon="%" pending="Preparing patch..." complete={false} part={props.part}>
2967
- Patch
2968
- </InlineTool>
2969
- </Match>
2970
- </Switch>
2971
- )
2972
- }
2973
-
2974
- function Question(props: ToolProps<typeof QuestionTool>) {
2975
- const { theme } = useTheme()
2976
- const count = createMemo(() => props.input.questions?.length ?? 0)
2977
-
2978
- function format(answer?: ReadonlyArray<string>) {
2979
- if (!answer?.length) return "(no answer)"
2980
- return answer.join(", ")
2981
- }
2982
-
2983
- return (
2984
- <Switch>
2985
- <Match when={props.metadata.answers}>
2986
- <BlockTool title="# Questions" part={props.part}>
2987
- <box gap={1}>
2988
- <For each={props.input.questions ?? []}>
2989
- {(q, i) => (
2990
- <box flexDirection="column">
2991
- <text fg={theme.textMuted}>{q.question}</text>
2992
- <text fg={theme.text}>{format(props.metadata.answers?.[i()])}</text>
2993
- </box>
2994
- )}
2995
- </For>
2996
- </box>
2997
- </BlockTool>
2998
- </Match>
2999
- <Match when={true}>
3000
- <InlineTool icon="→" pending="Asking questions..." complete={count()} part={props.part}>
3001
- Asked {count()} question{count() !== 1 ? "s" : ""}
3002
- </InlineTool>
3003
- </Match>
3004
- </Switch>
3005
- )
3006
- }
3007
-
3008
- function Skill(props: ToolProps<typeof SkillTool>) {
3009
- return (
3010
- <InlineTool icon="→" pending="Loading skill..." complete={props.input.name} part={props.part}>
3011
- Skill "{props.input.name}"
3012
- </InlineTool>
3013
- )
3014
- }
3015
-
3016
- function Diagnostics(props: { diagnostics?: Record<string, Record<string, any>[]>; filePath: string }) {
3017
- const { theme } = useTheme()
3018
- const errors = createMemo(() => {
3019
- const normalized = Filesystem.normalizePath(props.filePath)
3020
- const arr = props.diagnostics?.[normalized] ?? []
3021
- return arr.filter((x) => x.severity === 1).slice(0, 3)
3022
- })
3023
-
3024
- return (
3025
- <Show when={errors().length}>
3026
- <box>
3027
- <For each={errors()}>
3028
- {(diagnostic) => (
3029
- <text fg={theme.error}>
3030
- Error [{diagnostic.range.start.line + 1}:{diagnostic.range.start.character + 1}] {diagnostic.message}
3031
- </text>
3032
- )}
3033
- </For>
3034
- </box>
3035
- </Show>
3036
- )
3037
- }
3038
-
3039
- function normalizePath(input?: string) {
3040
- if (!input) return ""
3041
-
3042
- const cwd = process.cwd()
3043
- const absolute = path.isAbsolute(input) ? input : path.resolve(cwd, input)
3044
- const relative = path.relative(cwd, absolute)
3045
-
3046
- if (!relative) return "."
3047
- if (!relative.startsWith("..")) return relative
3048
-
3049
- // outside cwd - use absolute
3050
- return absolute
3051
- }
3052
-
3053
- function input(input: Record<string, any>, omit?: string[]): string {
3054
- const primitives = Object.entries(input).filter(([key, value]) => {
3055
- if (omit?.includes(key)) return false
3056
- return typeof value === "string" || typeof value === "number" || typeof value === "boolean"
3057
- })
3058
- if (primitives.length === 0) return ""
3059
- return `[${primitives.map(([key, value]) => `${key}=${value}`).join(", ")}]`
3060
- }
3061
-
3062
- function filetype(input?: string) {
3063
- if (!input) return "none"
3064
- const ext = path.extname(input)
3065
- const language = LANGUAGE_EXTENSIONS[ext]
3066
- if (["typescriptreact", "javascriptreact", "javascript"].includes(language)) return "typescript"
3067
- return language
3068
- }