@sleepy-ai/cli 0.1.6 → 0.1.8

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