rovecode 0.3.3 → 0.4.0-beta.3

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 (495) hide show
  1. package/CHANGELOG.md +527 -0
  2. package/README.md +46 -48
  3. package/bin/rovecode.js +24 -0
  4. package/dist/cli/app-j6gn14w3.js +2 -0
  5. package/dist/cli/ask-user-cwstt8fz.js +2 -0
  6. package/dist/cli/auth-login-9bbp9915.js +2 -0
  7. package/dist/cli/auth-m8p9grty.js +2 -0
  8. package/dist/cli/bench-16zqdms5.js +9 -0
  9. package/dist/cli/catalog-1xchffa4.js +2 -0
  10. package/dist/cli/cli-1n1zb64f.js +2 -0
  11. package/dist/cli/client-2t9gjkck.js +2 -0
  12. package/dist/cli/commands-exafvm2b.js +2 -0
  13. package/dist/cli/connect-6zde0kn3.js +2 -0
  14. package/dist/cli/context-cmd-5t43wgqt.js +2 -0
  15. package/dist/cli/context-report-kt01pw8y.js +2 -0
  16. package/dist/cli/count-remote-ap7x3vh6.js +2 -0
  17. package/dist/cli/design-ne5zszyh.js +2 -0
  18. package/dist/cli/dispatch-2r5myxye.js +2 -0
  19. package/dist/cli/doctor-ws4fh4tn.js +3 -0
  20. package/dist/cli/executor-bdrjn634.js +2 -0
  21. package/dist/cli/export-1mxb9g5p.js +2 -0
  22. package/dist/cli/files-g104xghh.js +2 -0
  23. package/dist/cli/gauntlet-07xrjpj7.js +2 -0
  24. package/dist/cli/gauntlet-runner-xvy64436.js +10 -0
  25. package/dist/cli/gauntlet-wave3-jm91yt5w.js +5 -0
  26. package/dist/cli/gauntlet-wave4-r13py7p1.js +14 -0
  27. package/dist/cli/hashline-znvrat11.js +2 -0
  28. package/dist/cli/http-xafw6fsh.js +143 -0
  29. package/dist/cli/index-1sgjm25y.js +2 -0
  30. package/dist/cli/init-g2m0tn4m.js +51 -0
  31. package/dist/cli/install-avaqjjqq.js +2 -0
  32. package/dist/cli/loop-mmpfft01.js +2 -0
  33. package/dist/cli/main-0904f6ps.js +5 -0
  34. package/dist/cli/main-0ab9fc26.js +9 -0
  35. package/dist/cli/main-0jys2ccn.js +3 -0
  36. package/dist/cli/main-0mtcdbs7.js +3 -0
  37. package/dist/cli/main-0z1w2zsg.js +3 -0
  38. package/dist/cli/main-1dchs7xv.js +18 -0
  39. package/dist/cli/main-1ereejm1.js +3 -0
  40. package/dist/cli/main-1k1kw6b5.js +3 -0
  41. package/dist/cli/main-27y4sm2k.js +38 -0
  42. package/dist/cli/main-2wwjex5j.js +58 -0
  43. package/dist/cli/main-2yeveeve.js +6 -0
  44. package/dist/cli/main-2yfck9b5.js +3 -0
  45. package/dist/cli/main-2zmzgkwh.js +3 -0
  46. package/dist/cli/main-351pz3z7.js +7 -0
  47. package/dist/cli/main-3gjqfh7a.js +6 -0
  48. package/dist/cli/main-3nf3kgve.js +3 -0
  49. package/dist/cli/main-3pjrb2hd.js +3 -0
  50. package/dist/cli/main-3rxcvgna.js +19 -0
  51. package/dist/cli/main-4b3jgy66.js +19 -0
  52. package/dist/cli/main-4wndhjdc.js +7 -0
  53. package/dist/cli/main-4xcmvxnk.js +3 -0
  54. package/dist/cli/main-5tbz0wbz.js +4 -0
  55. package/dist/cli/main-5ywnwthm.js +3 -0
  56. package/dist/cli/main-6b62vkz0.js +14 -0
  57. package/dist/cli/main-6dnk69vp.js +3 -0
  58. package/dist/cli/main-6genrmhs.js +136 -0
  59. package/dist/cli/main-73g7eff4.js +15 -0
  60. package/dist/cli/main-7c5thhjd.js +5 -0
  61. package/dist/cli/main-7rn6bqje.js +3 -0
  62. package/dist/cli/main-80haw7qk.js +4 -0
  63. package/dist/cli/main-875s60s2.js +4 -0
  64. package/dist/cli/main-8kjxbpw4.js +8 -0
  65. package/dist/cli/main-90ds1z4e.js +10 -0
  66. package/dist/cli/main-9etavkew.js +3 -0
  67. package/dist/cli/main-a9njrkk1.js +3 -0
  68. package/dist/cli/main-aecrjq2d.js +12 -0
  69. package/dist/cli/main-ck9asesq.js +9 -0
  70. package/dist/cli/main-cta9racd.js +4 -0
  71. package/dist/cli/main-ddv7j2ag.js +3 -0
  72. package/dist/cli/main-dfreez27.js +10 -0
  73. package/dist/cli/main-f7rw7des.js +3 -0
  74. package/dist/cli/main-ggcn7rd7.js +5 -0
  75. package/dist/cli/main-gzkmycnv.js +3 -0
  76. package/dist/cli/main-hq51jg8v.js +18 -0
  77. package/dist/cli/main-jft389w9.js +8 -0
  78. package/dist/cli/main-k1eqkg83.js +3 -0
  79. package/dist/cli/main-k2y8a2aw.js +9 -0
  80. package/dist/cli/main-kcpbykxz.js +4 -0
  81. package/dist/cli/main-kd488vje.js +22 -0
  82. package/dist/cli/main-kh32yvgk.js +5 -0
  83. package/dist/cli/main-kqxnqjnv.js +25 -0
  84. package/dist/cli/main-kyn0xnsg.js +3 -0
  85. package/dist/cli/main-m1kk6fp5.js +21 -0
  86. package/dist/cli/main-mv40pcr2.js +4 -0
  87. package/dist/cli/main-n0t3973w.js +3 -0
  88. package/dist/cli/main-nqveez48.js +4 -0
  89. package/dist/cli/main-pknhvrmj.js +3 -0
  90. package/dist/cli/main-pn1w7a7j.js +3 -0
  91. package/dist/cli/main-prxxs70n.js +4 -0
  92. package/dist/cli/main-q3vsesf9.js +3 -0
  93. package/dist/cli/main-qsevpgsv.js +3 -0
  94. package/dist/cli/main-rdgdw24b.js +25 -0
  95. package/dist/cli/main-rfth4tbm.js +16 -0
  96. package/dist/cli/main-rg0wn0xf.js +5 -0
  97. package/dist/cli/main-sdmxhtv8.js +4 -0
  98. package/dist/cli/main-skbp13js.js +18 -0
  99. package/dist/cli/main-t4xnd213.js +7 -0
  100. package/dist/cli/main-vqak588n.js +4 -0
  101. package/dist/cli/main-w2n1303f.js +9 -0
  102. package/dist/cli/main-wbrdspr2.js +5 -0
  103. package/dist/cli/main-wsrg79c1.js +7 -0
  104. package/dist/cli/main-x4r0fne4.js +5 -0
  105. package/dist/cli/main-xea2f3tn.js +6 -0
  106. package/dist/cli/main-xg704a3c.js +3 -0
  107. package/dist/cli/main-xvnrabfp.js +16 -0
  108. package/dist/cli/main-xy53xf0r.js +4 -0
  109. package/dist/cli/main-y1fqy60y.js +3 -0
  110. package/dist/cli/main-yn8cd281.js +34 -0
  111. package/dist/cli/main-yr0ksc0h.js +4 -0
  112. package/dist/cli/main-z2ex2vyf.js +4 -0
  113. package/dist/cli/main-z3aayzvq.js +3 -0
  114. package/dist/cli/main-zaqh35jg.js +3 -0
  115. package/dist/cli/main-zc2e8e46.js +4 -0
  116. package/dist/cli/main-zzrfw6cf.js +13 -0
  117. package/dist/cli/main.js +280 -0
  118. package/dist/cli/market-cmd-e14kmx9n.js +5 -0
  119. package/dist/cli/mcp-login-wq7ktdek.js +2 -0
  120. package/dist/cli/mcp-market-cmd-9mg3jecy.js +2 -0
  121. package/dist/cli/notify-b7qc0cjb.js +2 -0
  122. package/dist/cli/oauth-z8whcgfx.js +2 -0
  123. package/dist/cli/output-b3ewj3ps.js +16 -0
  124. package/dist/cli/profiles-6mr5he5e.js +2 -0
  125. package/dist/cli/provider-config-g7j42q8x.js +2 -0
  126. package/dist/cli/provider-jr1y8vvm.js +2 -0
  127. package/dist/cli/registry-s8yk86g0.js +2 -0
  128. package/dist/cli/registry-t6p8d4mn.js +2 -0
  129. package/dist/cli/repl-bajwe1mh.js +11 -0
  130. package/dist/cli/resume-rwn9nz7y.js +2 -0
  131. package/dist/cli/run-flags-nah7ndpt.js +2 -0
  132. package/dist/cli/runtime-n7gafzhb.js +2 -0
  133. package/dist/cli/sandbox-config-emdy18x4.js +2 -0
  134. package/dist/cli/server-b0nvs2bn.js +5 -0
  135. package/dist/cli/session-arg-y75wd4kj.js +2 -0
  136. package/dist/cli/session-j62evmjq.js +2 -0
  137. package/dist/cli/sessions-cmd-tsnwz0ns.js +7 -0
  138. package/dist/cli/settings-df10wfez.js +2 -0
  139. package/dist/cli/setup-jzvv72fg.js +2 -0
  140. package/dist/cli/sextant-smoke-37m81ke6.js +5 -0
  141. package/dist/cli/skills-cmd-gjxnxnhx.js +2 -0
  142. package/dist/cli/smoke-p7748apt.js +8 -0
  143. package/dist/cli/start-chat-s4st3mm0.js +12 -0
  144. package/dist/cli/stream-gmeyewds.js +2 -0
  145. package/dist/cli/task-gh0kkp3n.js +2 -0
  146. package/dist/cli/tasks-z1kfpe8e.js +2 -0
  147. package/dist/cli/thinking-0eqkrz6t.js +2 -0
  148. package/dist/cli/todo-5brcrt9m.js +2 -0
  149. package/dist/cli/tools-7pzm0vj9.js +2 -0
  150. package/dist/cli/tools-s635p6s8.js +2 -0
  151. package/dist/cli/trust-cmd-cjav8zgm.js +2 -0
  152. package/dist/cli/update-check-pt31bm2f.js +2 -0
  153. package/dist/cli/update-cmd-tk131s9t.js +2 -0
  154. package/dist/cli/voice-56nabd8d.js +2 -0
  155. package/dist/cli/webfetch-xd8q596m.js +2 -0
  156. package/dist/cli/websearch-5hkf98k1.js +2 -0
  157. package/dist/cli/workflow-cmd-cy3cvzjp.js +4 -0
  158. package/dist/cli/workspace-q10g5z3e.js +2 -0
  159. package/dist/lib/index.js +62 -0
  160. package/dist/lib/models-index.json +1 -0
  161. package/dist/lib/plugins.js +55 -0
  162. package/dist/lib/providers.js +17 -0
  163. package/dist/lib/public-api.js +20 -0
  164. package/dist/lib/sdk.js +360 -0
  165. package/package.json +84 -72
  166. package/bin/rovecode.ts +0 -21
  167. package/src/acp/server.ts +0 -374
  168. package/src/cli/auth-login.ts +0 -122
  169. package/src/cli/connect.ts +0 -244
  170. package/src/cli/context-cmd.ts +0 -199
  171. package/src/cli/dispatch.ts +0 -82
  172. package/src/cli/doctor.ts +0 -362
  173. package/src/cli/export.ts +0 -276
  174. package/src/cli/help.ts +0 -293
  175. package/src/cli/is-tui-invocation.ts +0 -8
  176. package/src/cli/main.ts +0 -583
  177. package/src/cli/market-cmd.ts +0 -658
  178. package/src/cli/mcp-login.ts +0 -141
  179. package/src/cli/mcp-market-cmd.ts +0 -302
  180. package/src/cli/output.ts +0 -382
  181. package/src/cli/repl.ts +0 -250
  182. package/src/cli/repomap-root.ts +0 -14
  183. package/src/cli/resume.ts +0 -57
  184. package/src/cli/run-flags.ts +0 -43
  185. package/src/cli/run-limits.ts +0 -78
  186. package/src/cli/runtime.ts +0 -931
  187. package/src/cli/session-arg.ts +0 -30
  188. package/src/cli/sessions-cmd.ts +0 -145
  189. package/src/cli/setup.ts +0 -153
  190. package/src/cli/skills-cmd.ts +0 -194
  191. package/src/cli/start-chat.ts +0 -65
  192. package/src/cli/trust-cmd.ts +0 -52
  193. package/src/coding/bash.ts +0 -148
  194. package/src/coding/checkpoints.ts +0 -327
  195. package/src/coding/diff.ts +0 -138
  196. package/src/coding/files.ts +0 -341
  197. package/src/coding/hashline.ts +0 -274
  198. package/src/coding/lsp-gate.ts +0 -254
  199. package/src/coding/lsp-servers.ts +0 -147
  200. package/src/coding/lsp.ts +0 -283
  201. package/src/coding/repomap-cache.ts +0 -99
  202. package/src/coding/repomap-files.ts +0 -192
  203. package/src/coding/repomap.ts +0 -481
  204. package/src/core/agents.ts +0 -255
  205. package/src/core/compaction.ts +0 -259
  206. package/src/core/config.ts +0 -289
  207. package/src/core/context-report.ts +0 -228
  208. package/src/core/context.ts +0 -60
  209. package/src/core/count-remote.ts +0 -107
  210. package/src/core/execpolicy-rules.ts +0 -196
  211. package/src/core/execpolicy.ts +0 -385
  212. package/src/core/executor.ts +0 -454
  213. package/src/core/guardrails.ts +0 -400
  214. package/src/core/hooks.ts +0 -411
  215. package/src/core/images.ts +0 -230
  216. package/src/core/intro.ts +0 -266
  217. package/src/core/loop.ts +0 -567
  218. package/src/core/modes.ts +0 -372
  219. package/src/core/orchestrator.ts +0 -245
  220. package/src/core/proc-group.ts +0 -48
  221. package/src/core/project-trust.ts +0 -98
  222. package/src/core/reflection.ts +0 -165
  223. package/src/core/sandbox-config.ts +0 -186
  224. package/src/core/session-id.ts +0 -24
  225. package/src/core/session-images.ts +0 -73
  226. package/src/core/session-ops.ts +0 -183
  227. package/src/core/session-text.ts +0 -29
  228. package/src/core/session.ts +0 -469
  229. package/src/core/settings.ts +0 -170
  230. package/src/core/tasks.ts +0 -646
  231. package/src/core/token-scale.ts +0 -108
  232. package/src/core/tools.ts +0 -309
  233. package/src/core/trust.ts +0 -104
  234. package/src/core/types.ts +0 -330
  235. package/src/core/update-check.ts +0 -171
  236. package/src/core/usage.ts +0 -204
  237. package/src/core/validate.ts +0 -121
  238. package/src/core/verify-gate.ts +0 -159
  239. package/src/core/verify.ts +0 -236
  240. package/src/core/voice.ts +0 -158
  241. package/src/core/win-job.ts +0 -183
  242. package/src/core/workspace.ts +0 -184
  243. package/src/design/audit.ts +0 -797
  244. package/src/design/direction.ts +0 -190
  245. package/src/design/rules.ts +0 -157
  246. package/src/eval/bench.ts +0 -150
  247. package/src/eval/gauntlet-runner.ts +0 -215
  248. package/src/eval/gauntlet-support.ts +0 -84
  249. package/src/eval/gauntlet-wave3.ts +0 -269
  250. package/src/eval/gauntlet-wave4.ts +0 -217
  251. package/src/eval/gauntlet.ts +0 -253
  252. package/src/index.ts +0 -17
  253. package/src/lanes/agy.ts +0 -95
  254. package/src/lanes/approval.ts +0 -24
  255. package/src/lanes/claude.ts +0 -129
  256. package/src/lanes/codex.ts +0 -127
  257. package/src/lanes/events.ts +0 -130
  258. package/src/lanes/job.ts +0 -142
  259. package/src/lanes/opencode.ts +0 -122
  260. package/src/lanes/process.ts +0 -184
  261. package/src/lanes/progress.ts +0 -183
  262. package/src/lanes/registry.ts +0 -178
  263. package/src/lanes/runner.ts +0 -124
  264. package/src/lanes/types.ts +0 -112
  265. package/src/market/catalogs/mcp-docs.json +0 -111
  266. package/src/market/catalogs/plugins.json +0 -111
  267. package/src/market/catalogs/skills.json +0 -478
  268. package/src/market/clone.ts +0 -72
  269. package/src/market/context-cost.ts +0 -121
  270. package/src/market/digest.ts +0 -106
  271. package/src/market/index.ts +0 -22
  272. package/src/market/install.ts +0 -578
  273. package/src/market/manifest.ts +0 -187
  274. package/src/market/prereq.ts +0 -145
  275. package/src/market/registry.ts +0 -363
  276. package/src/market/resolve.ts +0 -111
  277. package/src/market/types.ts +0 -236
  278. package/src/market/validate.ts +0 -227
  279. package/src/mcp/client.ts +0 -449
  280. package/src/mcp/config.ts +0 -252
  281. package/src/mcp/local-package.ts +0 -211
  282. package/src/mcp/market-catalog.ts +0 -84
  283. package/src/mcp/market-install.ts +0 -289
  284. package/src/mcp/market.ts +0 -362
  285. package/src/mcp/oauth.ts +0 -251
  286. package/src/mcp/prompts-resources.ts +0 -249
  287. package/src/mcp/shared.ts +0 -149
  288. package/src/mcp/status.ts +0 -67
  289. package/src/mcp/tools.ts +0 -275
  290. package/src/mcp/transport.ts +0 -122
  291. package/src/mcp/trust.ts +0 -25
  292. package/src/memory/blocks.ts +0 -278
  293. package/src/memory/recall.ts +0 -355
  294. package/src/memory/scope.ts +0 -182
  295. package/src/memory/store.ts +0 -105
  296. package/src/memory/tools.ts +0 -99
  297. package/src/plugins/cli.ts +0 -119
  298. package/src/plugins/discover.ts +0 -108
  299. package/src/plugins/index.ts +0 -50
  300. package/src/plugins/install.ts +0 -184
  301. package/src/plugins/load.ts +0 -124
  302. package/src/plugins/manifest.ts +0 -92
  303. package/src/plugins/state.ts +0 -83
  304. package/src/providers/auth.ts +0 -408
  305. package/src/providers/cache.ts +0 -223
  306. package/src/providers/catalog-local.ts +0 -160
  307. package/src/providers/catalog.ts +0 -421
  308. package/src/providers/middleware-context.ts +0 -86
  309. package/src/providers/middleware.ts +0 -373
  310. package/src/providers/model-list.ts +0 -23
  311. package/src/providers/oauth/common.ts +0 -105
  312. package/src/providers/oauth/device-code.ts +0 -107
  313. package/src/providers/oauth/github-copilot.ts +0 -146
  314. package/src/providers/oauth/loopback.ts +0 -158
  315. package/src/providers/oauth/openai.ts +0 -163
  316. package/src/providers/oauth/openrouter.ts +0 -89
  317. package/src/providers/oauth/pkce.ts +0 -45
  318. package/src/providers/oauth/registry.ts +0 -39
  319. package/src/providers/oauth/seam.ts +0 -89
  320. package/src/providers/profile-glm53.ts +0 -111
  321. package/src/providers/profile-sonnet5-persona.ts +0 -65
  322. package/src/providers/profile-sonnet5-voice.ts +0 -23
  323. package/src/providers/profiles.ts +0 -156
  324. package/src/providers/provider-config.ts +0 -311
  325. package/src/providers/registry.ts +0 -333
  326. package/src/providers/responses.ts +0 -209
  327. package/src/providers/retry.ts +0 -234
  328. package/src/providers/router.ts +0 -294
  329. package/src/providers/sse.ts +0 -26
  330. package/src/providers/stream-errors.ts +0 -117
  331. package/src/providers/stream.ts +0 -566
  332. package/src/providers/thinking.ts +0 -189
  333. package/src/providers/wire-messages.ts +0 -129
  334. package/src/providers/wire-responses.ts +0 -79
  335. package/src/providers/wire-select.ts +0 -53
  336. package/src/server/http.ts +0 -291
  337. package/src/server/openapi.ts +0 -246
  338. package/src/sextant/card-hits.ts +0 -102
  339. package/src/sextant/card-keys.ts +0 -55
  340. package/src/sextant/context-source.ts +0 -157
  341. package/src/sextant/crew-cards.ts +0 -350
  342. package/src/sextant/draw-agents.ts +0 -273
  343. package/src/sextant/draw-code.ts +0 -388
  344. package/src/sextant/draw-context.ts +0 -222
  345. package/src/sextant/draw-frame.ts +0 -164
  346. package/src/sextant/draw-market.ts +0 -573
  347. package/src/sextant/draw-messages.ts +0 -386
  348. package/src/sextant/draw-pet.ts +0 -230
  349. package/src/sextant/draw-plan.ts +0 -187
  350. package/src/sextant/draw-tabs.ts +0 -85
  351. package/src/sextant/draw-util.ts +0 -65
  352. package/src/sextant/draw-wizard.ts +0 -378
  353. package/src/sextant/engine.ts +0 -230
  354. package/src/sextant/frame-hits.ts +0 -25
  355. package/src/sextant/frame.ts +0 -101
  356. package/src/sextant/git-status.ts +0 -197
  357. package/src/sextant/grid.ts +0 -59
  358. package/src/sextant/input.ts +0 -119
  359. package/src/sextant/keys.ts +0 -521
  360. package/src/sextant/layout.ts +0 -86
  361. package/src/sextant/local-commands.ts +0 -169
  362. package/src/sextant/market-source.ts +0 -287
  363. package/src/sextant/mentions.ts +0 -200
  364. package/src/sextant/message-hits.ts +0 -26
  365. package/src/sextant/model.ts +0 -387
  366. package/src/sextant/overlays.ts +0 -456
  367. package/src/sextant/panel-hits.ts +0 -38
  368. package/src/sextant/pet.ts +0 -399
  369. package/src/sextant/screen.ts +0 -324
  370. package/src/sextant/scroll-hits.ts +0 -66
  371. package/src/sextant/scrollbar.ts +0 -82
  372. package/src/sextant/sextant-bridge.ts +0 -174
  373. package/src/sextant/sextant-cards.ts +0 -142
  374. package/src/sextant/sextant-diff-base.ts +0 -63
  375. package/src/sextant/sextant-files.ts +0 -154
  376. package/src/sextant/sextant-frame-loop.ts +0 -335
  377. package/src/sextant/sextant-renderer.ts +0 -574
  378. package/src/sextant/sextant-repo.ts +0 -140
  379. package/src/sextant/theme.ts +0 -66
  380. package/src/sextant/tool-rows.ts +0 -189
  381. package/src/sextant/types.ts +0 -493
  382. package/src/skills/index.ts +0 -387
  383. package/src/skills/pack.ts +0 -220
  384. package/src/skills/spec.ts +0 -162
  385. package/src/skills/tools.ts +0 -69
  386. package/src/skills/versioned.ts +0 -227
  387. package/src/telemetry/otel-export.ts +0 -122
  388. package/src/telemetry/otel-lanes.ts +0 -89
  389. package/src/telemetry/otel-logs.ts +0 -131
  390. package/src/telemetry/otel-metrics.ts +0 -136
  391. package/src/telemetry/otel.ts +0 -397
  392. package/src/telemetry/otlp.ts +0 -76
  393. package/src/tools/ask-user.ts +0 -156
  394. package/src/tools/bash-bg.ts +0 -94
  395. package/src/tools/bash-jobs.ts +0 -237
  396. package/src/tools/design.ts +0 -151
  397. package/src/tools/evalcell.ts +0 -338
  398. package/src/tools/html-text.ts +0 -139
  399. package/src/tools/provider.ts +0 -149
  400. package/src/tools/task.ts +0 -250
  401. package/src/tools/todo.ts +0 -320
  402. package/src/tools/webfetch.ts +0 -332
  403. package/src/tools/websearch.ts +0 -359
  404. package/src/tui/agents-cmd.ts +0 -41
  405. package/src/tui/app.ts +0 -749
  406. package/src/tui/attach.ts +0 -127
  407. package/src/tui/boot-notes.ts +0 -41
  408. package/src/tui/builtin-prompts.ts +0 -59
  409. package/src/tui/checkpoints-cmd.ts +0 -70
  410. package/src/tui/clipboard-image.ts +0 -81
  411. package/src/tui/clipboard.ts +0 -78
  412. package/src/tui/commands.ts +0 -283
  413. package/src/tui/config-view.ts +0 -53
  414. package/src/tui/context-cmds.ts +0 -282
  415. package/src/tui/cost.ts +0 -108
  416. package/src/tui/crash-guard.ts +0 -173
  417. package/src/tui/focus-terminal.ts +0 -34
  418. package/src/tui/git-cmds.ts +0 -273
  419. package/src/tui/git-plain.ts +0 -58
  420. package/src/tui/info-cmd.ts +0 -150
  421. package/src/tui/input-plain.ts +0 -76
  422. package/src/tui/mcp-cmd.ts +0 -128
  423. package/src/tui/memory-note.ts +0 -77
  424. package/src/tui/modes-cmd.ts +0 -45
  425. package/src/tui/notify-seq.ts +0 -100
  426. package/src/tui/notify.ts +0 -318
  427. package/src/tui/overlays.ts +0 -97
  428. package/src/tui/pi-renderer.ts +0 -428
  429. package/src/tui/providers-cmd.ts +0 -377
  430. package/src/tui/reasoning-view.ts +0 -56
  431. package/src/tui/renderer.ts +0 -128
  432. package/src/tui/replay-marker.ts +0 -29
  433. package/src/tui/session-cmd.ts +0 -148
  434. package/src/tui/session-manage.ts +0 -95
  435. package/src/tui/sextant-attach.ts +0 -102
  436. package/src/tui/sextant-io.ts +0 -202
  437. package/src/tui/sextant-smoke.ts +0 -110
  438. package/src/tui/shell-cmd.ts +0 -158
  439. package/src/tui/smoke.ts +0 -72
  440. package/src/tui/staged-terminal.ts +0 -50
  441. package/src/tui/startup.ts +0 -12
  442. package/src/tui/theme.ts +0 -59
  443. package/src/tui/todo-label.ts +0 -7
  444. package/src/tui/trust-card.ts +0 -107
  445. package/src/tui/tui-commands.ts +0 -87
  446. package/tsconfig.json +0 -30
  447. package/vendor/pi-tui/LICENSE +0 -21
  448. package/vendor/pi-tui/PATCHES.md +0 -12
  449. package/vendor/pi-tui/PROVENANCE.md +0 -12
  450. package/vendor/pi-tui/README.upstream.md +0 -854
  451. package/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  452. package/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  453. package/vendor/pi-tui/src/alt-screen-search.ts +0 -158
  454. package/vendor/pi-tui/src/autocomplete.ts +0 -827
  455. package/vendor/pi-tui/src/components/alt-screen-flash.ts +0 -52
  456. package/vendor/pi-tui/src/components/box.ts +0 -138
  457. package/vendor/pi-tui/src/components/cancellable-loader.ts +0 -41
  458. package/vendor/pi-tui/src/components/editor.ts +0 -2364
  459. package/vendor/pi-tui/src/components/h-stack.ts +0 -45
  460. package/vendor/pi-tui/src/components/image.ts +0 -128
  461. package/vendor/pi-tui/src/components/input.ts +0 -448
  462. package/vendor/pi-tui/src/components/loader.ts +0 -93
  463. package/vendor/pi-tui/src/components/markdown.ts +0 -1016
  464. package/vendor/pi-tui/src/components/scroll-view.ts +0 -217
  465. package/vendor/pi-tui/src/components/select-list.ts +0 -230
  466. package/vendor/pi-tui/src/components/settings-list.ts +0 -277
  467. package/vendor/pi-tui/src/components/spacer.ts +0 -29
  468. package/vendor/pi-tui/src/components/stack.ts +0 -155
  469. package/vendor/pi-tui/src/components/text.ts +0 -108
  470. package/vendor/pi-tui/src/components/truncated-text.ts +0 -66
  471. package/vendor/pi-tui/src/components/v-stack.ts +0 -34
  472. package/vendor/pi-tui/src/editor-component.ts +0 -75
  473. package/vendor/pi-tui/src/fuzzy.ts +0 -138
  474. package/vendor/pi-tui/src/index.ts +0 -149
  475. package/vendor/pi-tui/src/keybindings.ts +0 -321
  476. package/vendor/pi-tui/src/keys.ts +0 -1402
  477. package/vendor/pi-tui/src/kill-ring.ts +0 -47
  478. package/vendor/pi-tui/src/latex.ts +0 -1381
  479. package/vendor/pi-tui/src/layout-node.ts +0 -52
  480. package/vendor/pi-tui/src/layout.ts +0 -411
  481. package/vendor/pi-tui/src/native-modifiers.ts +0 -60
  482. package/vendor/pi-tui/src/native-module-path.ts +0 -32
  483. package/vendor/pi-tui/src/stdin-buffer.ts +0 -445
  484. package/vendor/pi-tui/src/terminal-colors.ts +0 -74
  485. package/vendor/pi-tui/src/terminal-image.ts +0 -701
  486. package/vendor/pi-tui/src/terminal.ts +0 -554
  487. package/vendor/pi-tui/src/tui-alt-screen.ts +0 -1379
  488. package/vendor/pi-tui/src/tui-main-screen.ts +0 -655
  489. package/vendor/pi-tui/src/tui.ts +0 -1264
  490. package/vendor/pi-tui/src/undo-stack.ts +0 -29
  491. package/vendor/pi-tui/src/utils.ts +0 -1327
  492. package/vendor/pi-tui/src/word-navigation.ts +0 -118
  493. package/vendor/pi-tui/test/test-themes.ts +0 -39
  494. package/vendor/pi-tui/test/virtual-terminal.ts +0 -219
  495. /package/{src/providers → dist/cli}/models-index.json +0 -0
package/src/tui/app.ts DELETED
@@ -1,749 +0,0 @@
1
- /** TUI chat app (port #1): wires the ONE agentLoop (ADR-003) into a Renderer.
2
- * All vendor contact lives behind Renderer (renderer.ts) — swap-friendly. Slash handlers live
3
- * beside it (ADR-002 cap): info-cmd.ts (/help /status /cost /skills /memory /export /todos
4
- * /tasks), session-cmd.ts (/new /rewind /sessions /resume), checkpoints-cmd.ts, modes-cmd.ts. */
5
-
6
- import { agentLoop, outstandingClause, outstandingTone } from "../core/loop.ts";
7
- import { resetTurnFailureCount } from "../memory/tools.ts";
8
- import { createRuntime } from "../cli/runtime.ts";
9
- import { SandboxConfigError } from "../core/sandbox-config.ts";
10
- import { SessionStore } from "../core/session.ts";
11
- import { adoptLegacyMemory } from "../memory/scope.ts";
12
- import { runMemoryNote } from "./memory-note.ts";
13
- import { runShellLine, shellLine, type ShellCtx } from "./shell-cmd.ts";
14
- import { cmdCommit, cmdUndo, type GitCmdCtx } from "./git-cmds.ts"; // port #65: /commit /undo
15
- import { ModelCatalog } from "../providers/catalog.ts";
16
- import { ModeManager, loadModesConfig, modeFromEntries, type AgentMode } from "../core/modes.ts";
17
- import { isTerminal, taskNote } from "../core/tasks.ts";
18
- import { togglePlanAct, applyModeToRun, flushModeSwitch } from "./modes-cmd.ts";
19
- import { cmdCheckpoints, cmdRestore, type CheckpointCmdCtx } from "./checkpoints-cmd.ts";
20
- import type { SessionCmdCtx } from "./session-cmd.ts";
21
- import type { InfoCmdCtx } from "./info-cmd.ts";
22
- import { todoLabel } from "./todo-label.ts";
23
- import { cmdAttach, cmdPasteImage, carryOverAttachments, queuedAttachNote, userTurnLine, type AttachCtx } from "./attach.ts";
24
- import { cmdConnect, cmdModel as cmdModelSwitch, cmdModels, cmdProvider, cmdSetup, listModelIds, watchProviders, MODEL_COMMAND, type ProviderCmdCtx } from "./providers-cmd.ts";
25
- import { SETUP_PICKS } from "../cli/setup.ts";
26
- import { isConfigured } from "../providers/provider-config.ts";
27
- import { cmdMcp } from "./mcp-cmd.ts";
28
- import { cmdTrustCard } from "./trust-card.ts";
29
- import { ReasoningViews } from "./reasoning-view.ts";
30
- import { cmdAgents } from "./agents-cmd.ts";
31
- import { cmdConfigView } from "./config-view.ts";
32
- import { runContextCommand, type ContextCmdCtx } from "./context-cmds.ts"; // port #53
33
- import { emitBootNotes } from "./boot-notes.ts"; // boot-note extraction port
34
- import { waitForStartup } from "./startup.ts";
35
- import { acceptEditsNote, effortNote, modeSwitchNote, noModelHint } from "../core/voice.ts";
36
- import { checkForUpdate, updateLine } from "../core/update-check.ts";
37
- import pkg from "../../package.json";
38
- import { rovecodeHome, saveCredential } from "../providers/auth.ts";
39
- import { installCrashGuard } from "./crash-guard.ts";
40
- import { compactionNote } from "./replay-marker.ts";
41
- import { previewDiff } from "../coding/diff.ts";
42
- import { discoverCommands, commandsForPalette, dispatchCustomCommand, type CustomCommandCtx } from "./commands.ts";
43
- import type { Renderer, AssistantView, StatusInfo } from "./renderer.ts";
44
- // pi-renderer.ts (and the vendored pi-tui under it) costs ~27 MB resident; a sextant session never
45
- // constructs it, so it is required where it is constructed, not imported here (tests: pi-renderer-lazy)
46
- type PiRendererMod = typeof import("./pi-renderer.ts");
47
- import { buildSextantAttach, SEXTANT_LOCAL_NAMES } from "./sextant-attach.ts";
48
- import type { ApprovalFn, ModelRef, PermissionLevel, RunEvent, ThinkingEffort } from "../core/types.ts";
49
- import { thinkingLine } from "../providers/thinking.ts";
50
- import { anthropicShapeFor } from "../providers/stream.ts";
51
- import { parseEffort, THINKING_EFFORTS } from "../core/types.ts";
52
- import { resolvePermission, saveSetting } from "../core/settings.ts";
53
- import { TUI_COMMANDS, type TuiAppOptions } from "./tui-commands.ts"; // options/table extraction port
54
- export { TUI_COMMANDS, type TuiAppOptions } from "./tui-commands.ts"; // existing import paths keep working
55
- import { join } from "node:path";
56
-
57
- export { buildCostNote } from "./cost.ts"; // moved for the ADR-002 cap; re-exported for tests
58
-
59
- // lazy loaders — info-cmd and session-cmd are deferred until the first slash command
60
- type InfoCmdMod = typeof import("./info-cmd.ts");
61
- let _infoCmdMod: InfoCmdMod | null = null;
62
- function lazyInfoCmd(): InfoCmdMod {
63
- if (_infoCmdMod === null) {
64
- // eslint-disable-next-line @typescript-eslint/no-require-imports
65
- _infoCmdMod = require("./info-cmd.ts") as InfoCmdMod;
66
- }
67
- return _infoCmdMod;
68
- }
69
-
70
- type SessionCmdMod = typeof import("./session-cmd.ts");
71
- let _sessionCmdMod: SessionCmdMod | null = null;
72
- function lazySessionCmd(): SessionCmdMod {
73
- if (_sessionCmdMod === null) {
74
- // eslint-disable-next-line @typescript-eslint/no-require-imports
75
- _sessionCmdMod = require("./session-cmd.ts") as SessionCmdMod;
76
- }
77
- return _sessionCmdMod;
78
- }
79
-
80
- type SessionManageMod = typeof import("./session-manage.ts");
81
- let _sessionManageMod: SessionManageMod | null = null;
82
- function lazySessionManage(): SessionManageMod {
83
- if (_sessionManageMod === null) {
84
- // eslint-disable-next-line @typescript-eslint/no-require-imports
85
- _sessionManageMod = require("./session-manage.ts") as SessionManageMod;
86
- }
87
- return _sessionManageMod;
88
- }
89
-
90
- interface TuiState {
91
- yolo: boolean;
92
- /** the middle tier (port: Claude Code's acceptEdits): writes inside the workspace stop asking,
93
- * shell/spawn/network and writes outside it still do. Ignored while `yolo` is on — auto already
94
- * covers everything. Turned on by `/accept-edits`, `--accept-edits`, or the `all edits` button on
95
- * a write approval card. */
96
- acceptEdits: boolean;
97
- provider: string; model: string; mode: AgentMode;
98
- turns: number; tokensIn: number; tokensOut: number;
99
- busy: boolean;
100
- }
101
-
102
- export async function runTui(opts: TuiAppOptions = {}): Promise<void> {
103
- const _t0 = process.env.ROVECODE_TRACE_BOOT === "1" ? (Number(process.env._ROVECODE_BOOT_T0) || Date.now()) : -1;
104
- const _trace = _t0 >= 0 ? (label: string) => process.stderr.write(`[boot] +${Date.now() - _t0}ms ${label}\n`) : (_: string) => {};
105
- _trace("runTui entered");
106
- opts.startup?.signal?.throwIfAborted();
107
- opts.startup?.status("loading workspace");
108
- // opts.sessionId is already the full id of an existing session, or undefined (see TuiAppOptions.sessionId)
109
- const boot = { id: opts.sessionId, warn: opts.bootNote };
110
- // opts.stream passes through verbatim: a StreamFn overrides, explicit null forces
111
- // "no provider", undefined defers to the runtime's env-resolved provider
112
- // port #27: a sandbox MISCONFIG throws synchronously here (before any side effect) — a clean
113
- // one-line startup error (exit 2), never a stack, never a silent direct fallback. The rung
114
- // PROBE verdict (rt.sandbox.ready) is awaited at the end of boot: runTui must stay
115
- // synchronous until the renderer's input handlers are wired (tests/smoke send input right
116
- // after calling runTui), so no await may sit above that point.
117
- const rt = (() => {
118
- try {
119
- _trace("createRuntime start");
120
- const r = createRuntime({ cwd: opts.cwd, stream: opts.stream, sessionId: boot.id, spawnRunner: opts.spawnRunner, platform: opts.platform, ...(opts.addDirs !== undefined ? { addDirs: opts.addDirs } : {}) });
121
- _trace("createRuntime done");
122
- return r;
123
- }
124
- catch (e) {
125
- if (e instanceof SandboxConfigError && opts.exitOnClose !== false) { console.error(`error: ${e.message}`); process.exit(2); }
126
- throw e;
127
- }
128
- })();
129
- const renderer: Renderer = opts.renderer ?? new (require("./pi-renderer.ts") as PiRendererMod).PiTuiRenderer({ cwd: rt.cwd }); // eslint-disable-line @typescript-eslint/no-require-imports
130
- _trace("renderer created");
131
- rt.setAskUser((q, signal) => renderer.askQuestion(q, signal)); // port #33: ask_user → the question overlay (Esc/abort dismisses it via signal)
132
- const sessionsDir = join(rt.cwd, ".rovecode", "sessions");
133
- // /cost pricing + context window. Boots from the offline snapshot; the live models.dev
134
- // half is user-invoked only (/cost refresh), cached to .rovecode/cache with a 24h TTL —
135
- // lookup() itself never fetches, so the TUI stays network-free unless asked.
136
- const catalog = new ModelCatalog({ fetchFn: fetch, cacheDir: join(rt.cwd, ".rovecode", "cache") });
137
- // session-scoped stores are swappable at runtime (/sessions, /rewind-to-root)
138
- let store = rt.store;
139
- let blocks = rt.blockStore;
140
- // port #26: the runtime's ONE steering queue — background-task completion notes land on the
141
- // next model turn; settled tasks also show in the transcript as they happen (failed → warn)
142
- const steering = rt.steering;
143
- rt.tasks.subscribe((t) => { if (isTerminal(t.status)) renderer.addSystemNote(taskNote(t), t.status === "failed" ? "warn" : "info"); });
144
- // port #20: per-mode model slots from .rovecode/modes.json, restored from session entries
145
- const modesCfg = loadModesConfig(rt.cwd);
146
- const modes = new ModeManager(modesCfg, {
147
- provider: rt.provider?.id ?? "mock",
148
- model: opts.model ?? process.env.ROVECODE_MODEL ?? rt.defaultModel ?? "",
149
- });
150
- modes.restore(modeFromEntries(store.messages()) ?? modes.mode);
151
- // port #30: custom slash commands — .rovecode/commands/*.md, project shadows ~/.rovecode/commands (commands.ts);
152
- // LOW-1: /quit is a `case` alias of /exit below, not a TUI_COMMANDS entry — reserve it explicitly;
153
- // port #44: the sextant surface's own /theme /open /diff /focus /agents never reach handleSlash — reserved too
154
- // plugins (src/plugins): an ACTIVE plugin's commands folder is one more root, after the folder of its own
155
- // scope — the same path setCommands feeds the palette and suggestions from, so nothing else changes
156
- const pluginCommandDirs = rt.plugins.found.flatMap((p) => (p.status === "active" && p.commandsDir ? [{ dir: p.commandsDir, scope: p.scope }] : []));
157
- const custom = discoverCommands(rt.cwd, { reserved: [...TUI_COMMANDS.map((c) => c.name), "quit", ...SEXTANT_LOCAL_NAMES], extraDirs: pluginCommandDirs });
158
- if (opts.effort !== undefined) rt.setEffort(opts.effort);
159
- // one resolved answer instead of two independent booleans: flag → env → project file → user file →
160
- // "ask" (core/settings.ts). This is what makes `/yolo --save` survive the terminal closing.
161
- // opts.permission is the flag rung for in-process callers; yolo/acceptEdits stay the boolean flags they were
162
- // (true = demand, false = say nothing) — see TuiAppOptions.permission for why false cannot mean "ask"
163
- const flagLevel: PermissionLevel | undefined = opts.permission ?? (opts.yolo === true ? "auto" : opts.acceptEdits === true ? "accept-edits" : undefined);
164
- const startLevel = resolvePermission(rt.cwd, flagLevel, { ROVECODE_PERMISSION: process.env.ROVECODE_PERMISSION, ROVECODE_YOLO: process.env.ROVECODE_YOLO, ROVECODE_ACCEPT_EDITS: process.env.ROVECODE_ACCEPT_EDITS });
165
- const state: TuiState = {
166
- yolo: startLevel === "auto",
167
- acceptEdits: startLevel === "accept-edits",
168
- provider: modes.modelFor().provider,
169
- model: modes.modelFor().model,
170
- mode: modes.mode,
171
- turns: 0, tokensIn: 0, tokensOut: 0, busy: false,
172
- };
173
- let run: AsyncGenerator<RunEvent> | null = null; let runAbort: AbortController | null = null; // port #21: one controller per run
174
- let closed = false;
175
- let resolveClosed: () => void = () => {};
176
- const closedP = new Promise<void>((r) => { resolveClosed = r; });
177
-
178
- const status = (): StatusInfo => {
179
- const todos = todoLabel(join(sessionsDir, store.id)); // port #32: "todos done/total"; key omitted while the list is empty
180
- return {
181
- provider: state.provider, model: state.model, yolo: state.yolo, mode: state.mode,
182
- permission: state.yolo ? "auto" : state.acceptEdits ? "accept-edits" : "ask",
183
- effort: rt.effort,
184
- turns: state.turns, tokensIn: state.tokensIn, tokensOut: state.tokensOut,
185
- ...(todos !== undefined ? { todos } : {}),
186
- };
187
- };
188
- const pushStatus = () => renderer.setStatus(status());
189
-
190
- /** set once the surface owns the screen (below renderer.start); puts Node's warning printer back */
191
- let restoreWarnings: (() => void) | undefined;
192
- /** set once renderer.start() has put the terminal into raw/alt mode (crash-guard.ts); cleared on a
193
- * clean quit, which performs its own restore and its own process.exit — a guard still listening
194
- * would restore a second time and log an "exit" it caused itself. */
195
- let uninstallCrashGuard: (() => void) | null = null;
196
- const close = (exitProcess = true) => {
197
- if (closed) return;
198
- closed = true;
199
- uninstallCrashGuard?.(); uninstallCrashGuard = null;
200
- // port #20 MED-2: /plan then quit resumes in plan (append is sync — lands pre-exit)
201
- flushModeSwitch(modes, store);
202
- runAbort?.abort(); // abort kills in-flight fetch/tools; return() settles the generator — kept, so the exit below waits for it
203
- const settled = run?.return(undefined as never).then(() => undefined, () => undefined) ?? Promise.resolve();
204
- const mapSettled = rt.stopRepoMapWarmup(); // no background scan or cache write after the app resolves
205
- rt.tasks.cancelAll(); // port #26: background children die with the surface, never after it
206
- const mcpSettled = rt.mcp?.close().catch(() => {}); // stop and drain MCP child processes/connections
207
- restoreWarnings?.(); // the screen is going away; Node's own printer is the right one again
208
- renderer.stop();
209
- // port #29: session_close fires ONCE, after the aborted run settled and its in-flight on_event
210
- // taps drained (hooks.close() waits for those) — cmdRun's exit() order; the app promise
211
- // resolves (and the process exits) only after it, so a quit never outruns the hook
212
- void (async () => {
213
- await Promise.all([settled, mapSettled, mcpSettled]);
214
- await rt.hooks.close().catch(() => {});
215
- // the sextant renderer's git children (repo watcher) must be GONE before the process exits — on
216
- // Windows a live child holds its cwd, so a scratch repo removed at quit throws EBUSY (fee2e8c root
217
- // cause). Optional: the Renderer seam stays untouched; FakeRenderer and pi-tui have no drain()
218
- await (renderer as { drain?: () => Promise<void> }).drain?.()?.catch(() => {});
219
- resolveClosed();
220
- if (exitProcess && opts.exitOnClose !== false) process.exit(0);
221
- })();
222
- };
223
-
224
- // both read the ACTIVE store live — /sessions and a root /rewind swap it (session-cmd.ts helpers)
225
- const refreshUsage = () => { const u = lazySessionCmd().usageOf(store); state.tokensIn = u.tokensIn; state.tokensOut = u.tokensOut; };
226
- const replayHistory = () => lazySessionCmd().replayTranscript(renderer, store);
227
- // port #34: /attach context — the stage lives on the ACTIVE store (read live); the vision check uses the current mode's model
228
- const attachCtx: AttachCtx = { renderer, cwd: rt.cwd, store: () => store, modelRef: () => modes.modelFor() };
229
-
230
- const switchSession = (id: string, announce = true) => {
231
- flushModeSwitch(modes, store); // port #20 MED-2: don't discard a pending switch on /sessions away
232
- const pending = store.stagedAttachments; // port #34: the stage lives on the instance — re-staged on the new one below
233
- store = new SessionStore(sessionsDir, id);
234
- // memory is PROJECT-scoped (memory/scope.ts), so switching sessions KEEPS the store — it is the same project.
235
- // The exception: a switched-to session that still carries a legacy per-session store copies it forward once,
236
- // and then the prompt should show the migrated text now rather than next run, so that hands back a fresh store.
237
- const adopted = adoptLegacyMemory(rt.cwd, sessionsDir, id);
238
- if (adopted.blocks) blocks = adopted.blocks;
239
- if (adopted.note !== null) for (const line of adopted.note.split("\n")) renderer.addSystemNote(line);
240
- // rebind BOTH consumers: the memory tool AND the system prompt's memory block
241
- // (critic finding: prompt kept reading the boot session's memory after /resume)
242
- rt.setBlockStore(blocks);
243
- rt.setSessionStore(store); // port #11: checkpoint entryId capture follows the active session
244
- // port #20: the switched-to session resumes ITS last recorded mode
245
- modes.restore(modeFromEntries(store.messages()) ?? modesCfg.defaultMode ?? "act");
246
- const cur = modes.modelFor();
247
- state.mode = modes.mode; state.model = cur.model; state.provider = cur.provider;
248
- state.turns = 0;
249
- replayHistory();
250
- refreshUsage();
251
- pushStatus();
252
- if (announce) renderer.addSystemNote(`session ${id.slice(0, 8)} (${store.messages().length} messages)`);
253
- carryOverAttachments(attachCtx, pending); // port #34: a swap must never lose staged images silently
254
- };
255
-
256
- // port #11: checkpoint command context (store/busy read live via closures)
257
- const cpCtx: CheckpointCmdCtx = {
258
- renderer,
259
- busy: () => state.busy,
260
- sessionId: () => store.id,
261
- checkpointsFor: (sid) => rt.checkpointsFor(sid),
262
- branchTo: (entryId) => store.branch(entryId),
263
- replayAndRefresh: () => { replayHistory(); refreshUsage(); pushStatus(); },
264
- };
265
-
266
- // port #2: session navigation context (store read live via closure — /sessions and a root /rewind swap it)
267
- const sessCtx: SessionCmdCtx = {
268
- renderer,
269
- cwd: rt.cwd, // /sessions delete removes the checkpoints shadow dir under the cwd as well (session-manage.ts)
270
- sessionsDir,
271
- busy: () => state.busy,
272
- store: () => store,
273
- switchSession,
274
- replayHistory,
275
- refreshUsage,
276
- pushStatus,
277
- };
278
-
279
- // read-only info commands (info-cmd.ts) — store/blocks read live, the status slice is `state` itself
280
- const infoCtx: InfoCmdCtx = {
281
- renderer, rt, sessionsDir, catalog, state,
282
- store: () => store, blocks: () => blocks,
283
- commands: { builtin: TUI_COMMANDS, custom: custom.commands },
284
- };
285
-
286
- // port #30: custom command dispatch context (submit = the plain user-turn path, defined below)
287
- const cmdCtx: CustomCommandCtx = { renderer, modes, state, pushStatus, submit: (t) => submit(t) };
288
-
289
- // port #53: context commands (/compact /clear /init /copy) — store/modes read live, like sessCtx;
290
- // bindAbort is the app's interrupt target (runAbort), the same seam `!cmd` holds its child under
291
- const ctxCmdCtx: ContextCmdCtx = {
292
- renderer, cwd: rt.cwd,
293
- busy: () => state.busy,
294
- store: () => store,
295
- modes, state,
296
- defaultMode: modesCfg.defaultMode ?? "act",
297
- buildCfg: () => rt.buildCfg(permissionLevel(), humanApprover()),
298
- // no summarizer is wired on this tree (context-cmds.ts header): /compact falls back to
299
- // keep-window and says so — the seam is here for #68's runtime wiring to fill
300
- bindAbort: (ac) => { runAbort = ac; },
301
- switchSession,
302
- replayHistory,
303
- refreshUsage,
304
- pushStatus,
305
- submit: (t) => submit(t),
306
- };
307
-
308
- // /model /models /provider (providers-cmd.ts) read the live registry; built lazily so pushStatus is bound
309
- const provCtx = (): ProviderCmdCtx => ({ rt, modes, state, renderer, pushStatus, catalog }); // port #60: existing picker reads /cost metadata
310
- /** `--save` writes the level the toggles just produced, so the next launch starts there;
311
- * `--project` pins it to this checkout instead of to you. Without --save nothing is written —
312
- * a toggle you meant for one run must not follow you into the next. */
313
- const persistLevel = (arg: string): string => {
314
- const words = arg.split(/\s+/).filter((w) => w.length > 0);
315
- if (!words.includes("--save")) return "this session only — add --save to make it the default (--project pins it to this repo)";
316
- const scope = words.includes("--project") ? "project" : "user";
317
- const level: PermissionLevel = state.yolo ? "auto" : state.acceptEdits ? "accept-edits" : "ask";
318
- try {
319
- const path = saveSetting("permission", level, scope, rt.cwd);
320
- return `saved: ${level} is the default now (${path})`;
321
- } catch (e) {
322
- return `could not save it: ${e instanceof Error ? e.message : String(e)}`;
323
- }
324
- };
325
-
326
- /** what the CURRENT model's endpoint receives for a level — the /effort note's second line (providers/thinking.ts).
327
- * Built like buildDef builds a ref (catalog reasoning flag), without touching the runtime's active model. */
328
- const receives = (level: ThinkingEffort): string => {
329
- const info = catalog.lookup(state.provider, state.model);
330
- const ref: ModelRef = { provider: state.provider, model: state.model, effort: level, ...(info?.supportsReasoning !== undefined ? { reasoning: info.supportsReasoning } : {}) };
331
- return thinkingLine(ref, rt.providers.get(state.provider)?.protocol ?? "openai", { shape: anthropicShapeFor(ref) });
332
- };
333
-
334
- const handleSlash = (text: string): boolean => {
335
- const [cmd, ...rest] = text.slice(1).split(/\s+/);
336
- const arg = rest.join(" ").trim();
337
- switch (cmd) {
338
- case "exit": case "quit": close(); return true;
339
- case "help": lazyInfoCmd().cmdHelp(infoCtx); return true;
340
- case "effort": {
341
- const want = arg.trim();
342
- if (want.length === 0) { renderer.addSystemNote(effortNote(rt.effort, receives(rt.effort))); return true; }
343
- const level = parseEffort(want);
344
- if (level === undefined) { renderer.addSystemNote(`"${want}" is not a level — ${THINKING_EFFORTS.join(" · ")}`, "warn"); return true; }
345
- rt.setEffort(level);
346
- renderer.addSystemNote(effortNote(level, receives(level)));
347
- pushStatus(); return true;
348
- }
349
- case "accept-edits":
350
- state.acceptEdits = !state.acceptEdits;
351
- renderer.addSystemNote(state.yolo
352
- ? `${acceptEditsNote(state.acceptEdits)} (auto mode is on, so nothing asks either way — /yolo turns it off)`
353
- : acceptEditsNote(state.acceptEdits));
354
- renderer.addSystemNote(persistLevel(arg));
355
- pushStatus(); return true;
356
- case "yolo":
357
- state.yolo = !state.yolo;
358
- renderer.addSystemNote(modeSwitchNote(state.yolo)); // "ask first" / "auto (never asks)" — the flag keeps its name
359
- renderer.addSystemNote(persistLevel(arg));
360
- pushStatus(); return true;
361
- // port #20: model writes land in the CURRENT mode's slot (mirrored to both when
362
- // planActSeparateModels is off); the selector may name another provider — the registry's
363
- // dispatcher routes per call, so the switch needs no restart. --save persists the default.
364
- case "model": cmdModelSwitch(provCtx(), arg); return true;
365
- case "models": void cmdModels(provCtx(), arg); return true;
366
- case "provider": void cmdProvider(provCtx(), arg); return true;
367
- case "setup": void cmdSetup(provCtx()); return true; // the classic chain; the cockpit answers /setup with the wizard before handleSlash runs
368
- // the same job on one line (cli/connect.ts through the live registry); bare, it hands over to /setup
369
- case "connect": void cmdConnect(provCtx(), arg); return true;
370
- case "plan": case "act":
371
- togglePlanAct(modes, cmd as AgentMode, state, renderer, pushStatus);
372
- return true;
373
- case "checkpoints": void cmdCheckpoints(cpCtx); return true;
374
- case "restore": void cmdRestore(cpCtx, arg); return true;
375
- case "commit": void cmdCommit(gitCtx, text.slice(1 + (cmd ?? "").length)); return true; // port #65: RAW remainder — a message body keeps its newlines
376
- case "undo": void cmdUndo(gitCtx); return true; // port #65
377
- case "status": lazyInfoCmd().cmdStatus(infoCtx); return true;
378
- case "cost": lazyInfoCmd().cmdCost(infoCtx, arg); return true;
379
- case "skills": lazyInfoCmd().cmdSkills(infoCtx); return true;
380
- case "memory": lazyInfoCmd().cmdMemory(infoCtx, arg); return true; // bare = show both blocks; `--user` one; with text = append (memory-note.ts)
381
- case "todos": lazyInfoCmd().cmdTodos(infoCtx); return true; // port #32
382
- case "tasks": lazyInfoCmd().cmdTasks(infoCtx, arg); return true; // port #26
383
- case "new": lazySessionCmd().cmdNew(sessCtx); return true;
384
- case "rewind": case "tree": void lazySessionCmd().cmdRewind(sessCtx); return true;
385
- case "sessions": void lazySessionManage().cmdSessionsVerb(sessCtx, arg); return true; // bare = the picker; rename/delete/fork/search = session-manage.ts
386
- case "compact": case "clear": case "init": case "copy": void runContextCommand(ctxCmdCtx, cmd ?? "", arg); return true; // port #53
387
- case "trust": void cmdTrustCard({ renderer, cwd: rt.cwd }, arg); return true; // the project trust gate, answerable without quitting (trust-card.ts)
388
- case "agents": cmdAgents({ renderer, agents: rt.agents }); return true; // the definitions list (port #62); the BARE /agents is the sextant crew board (local-commands.ts pass-through: only "/agents list" reaches here)
389
- case "config": cmdConfigView({ renderer, cwd: rt.cwd }, arg); return true; // the read-only settings view (config-view.ts): what THIS run actually loads
390
- case "resume":
391
- if (arg) void lazySessionCmd().cmdSessions(sessCtx, arg); else void lazySessionCmd().cmdSessions(sessCtx);
392
- return true;
393
- case "export": lazyInfoCmd().cmdExport(infoCtx, arg); return true;
394
- case "attach": cmdAttach(attachCtx, arg); return true; // port #34
395
- case "paste": cmdPasteImage(attachCtx); return true; // clipboard image → attachment (⌃v)
396
- case "mcp": void cmdMcp({ renderer, cwd: rt.cwd }, arg); return true; // the MCP market (mcp-cmd.ts): palette → approval card → mcp.json
397
- default:
398
- // port #30: a discovered custom command renders its template and submits it as a user turn.
399
- // MED-2: it gets the RAW remainder of the line (whitespace runs and pasted newlines intact —
400
- // renderCommand trims the ends itself); built-ins keep the collapsed `arg` above.
401
- if (!dispatchCustomCommand(cmdCtx, custom.commands, cmd ?? "", text.slice(1 + (cmd ?? "").length))) renderer.addSystemNote(`unknown command: /${cmd} (try /help)`, "warn");
402
- return true;
403
- }
404
- };
405
-
406
- /** the session's permission level as a model turn would run under */
407
- const permissionLevel = (): PermissionLevel => state.yolo ? "auto" : state.acceptEdits ? "accept-edits" : "ask";
408
-
409
- /** THE human approver — one implementation for a model-issued call and for the person's own `!cmd`, so the diff
410
- * preview and the "all edits" door behave identically either way. undefined under `auto`: nothing is asked. */
411
- const humanApprover = (): ApprovalFn | undefined => state.yolo ? undefined : async (req) => {
412
- // port #24: edit/write approvals carry a bounded unified diff of the pending change
413
- // (in-memory preview; any failure degrades to the plain overlay, never blocks the ask)
414
- const isEdit = req.tool === "edit" || req.tool === "write";
415
- // `all edits` pressed DURING this run: the rules were built before it, so the switch is honored
416
- // here too — otherwise the mode would only start at the next prompt, which is not what the
417
- // button says. The rules still gate the call; this only skips the card.
418
- if (isEdit && state.acceptEdits) return "once";
419
- let detail: string | undefined;
420
- if (isEdit) {
421
- try { detail = previewDiff(req.tool as "edit" | "write", req.revisedArgs, rt.cwd).text || undefined; } catch { detail = undefined; }
422
- }
423
- const answer = await renderer.askApproval(req.tool, JSON.stringify(req.revisedArgs).slice(0, 140), detail);
424
- if (answer !== "all-edits") return answer;
425
- // the surface-level door: flip the session and let THIS call through once. The core approval
426
- // engine stays a three-verdict system — "all-edits" never crosses into it.
427
- state.acceptEdits = true;
428
- renderer.addSystemNote(acceptEditsNote(true));
429
- pushStatus();
430
- return "once";
431
- };
432
-
433
- /** `!cmd` (tui/shell-cmd.ts): the person's own command, through the same dispatch, rules and approver */
434
- const shellCtx: ShellCtx = {
435
- renderer, rt, store: () => store,
436
- level: permissionLevel,
437
- approve: humanApprover,
438
- busy: () => state.busy,
439
- setBusy: (b) => { state.busy = b; pushStatus(); },
440
- bindAbort: (ac) => { runAbort = ac; },
441
- };
442
-
443
- // port #65 (tui/git-cmds.ts): /commit and /undo run on the SAME seams as `!cmd` — one busy flag,
444
- // one abort binding, one approver — plus the router (the COMMIT role drafts the message) and the
445
- // session's checkpoints (/undo restores through them; the conversation is left to /restore).
446
- const gitCtx: GitCmdCtx = {
447
- renderer, rt, store: () => store,
448
- approve: humanApprover,
449
- yolo: () => state.yolo,
450
- busy: () => state.busy,
451
- setBusy: (b) => { state.busy = b; pushStatus(); },
452
- bindAbort: (ac) => { runAbort = ac; },
453
- };
454
-
455
- const startRun = async (goal: string) => {
456
- const stream = rt.stream; // runtime already applied any opts.stream override
457
- // live check: /provider add + /provider key (or `rovecode provider add` in another terminal) clears
458
- // it for the next prompt — no restart
459
- const reason = rt.noProviderReason();
460
- if (!stream || reason !== null) {
461
- renderer.addSystemNote(reason !== null ? noModelHint("tui") : "no provider stream", "error");
462
- return;
463
- }
464
- state.busy = true;
465
- renderer.setBusy(true, "thinking…");
466
- pushStatus();
467
- const level = permissionLevel();
468
- const cfg = rt.buildCfg(level, humanApprover());
469
- // port #20: per-mode model resolution + plan-mode rule/prompt enforcement
470
- const cur = modes.modelFor();
471
- const def = rt.buildDef({ provider: cur.provider, model: cur.model });
472
- applyModeToRun(modes, cfg, def);
473
- const views = new Map<string, AssistantView>();
474
- const reasoning = new ReasoningViews(renderer); // port #60: one collapsed reasoning block per assistant message
475
- let lastView: AssistantView | null = null;
476
- runAbort = new AbortController();
477
- // port #26: Esc/quit cancel the background tasks THIS run starts; a normal end leaves them running.
478
- // The label is the run's own prompt: it titles the crew card that groups this run's agents
479
- // (sextant/crew-cards.ts). tasks.ts one-lines and clips it — a surface passes what the person typed.
480
- rt.tasks.bindRun(runAbort.signal, { label: goal });
481
- run = agentLoop(def, goal, {}, cfg, {
482
- stream, registry: rt.registry, store,
483
- tools: rt.registry.list().map((t) => t.schema),
484
- guard: rt.guard, planReminder: rt.planReminder, signal: runAbort.signal, // port #21: Esc aborts this run's controller
485
- cwd: rt.cwd, // cwd must be threaded — tools resolve relative paths against it, same as checkpoints/LSP/preview
486
- hooks: rt.hooks, // port #29: pre_tool/approval/post_tool at dispatch, pre_run/compaction/post_run/on_event via the loop observer
487
- }, steering);
488
- try {
489
- for await (const ev of run) {
490
- renderer.onEvent?.(ev); // port #44: FIRST — the sextant reducer is its rows' source of truth; the calls below are duplicates it ignores while busy
491
- reasoning.onEvent(ev); // port #60: reasoning_update → the collapsed block (settled when the text starts)
492
- if (ev.type === "turn_start") { resetTurnFailureCount(); state.turns++; pushStatus(); } // pushStatus here + in the finally also refreshes the port #32 todo label after a todo_write
493
- else if (ev.type === "message_update") {
494
- let v = views.get(ev.messageId);
495
- if (!v) { v = renderer.beginAssistant(); views.set(ev.messageId, v); lastView?.done(); lastView = v; }
496
- v.append(ev.delta);
497
- } else if (ev.type === "tool_execution_start") {
498
- renderer.toolStart(ev.callId, ev.tool, JSON.stringify(ev.args).slice(0, 120));
499
- } else if (ev.type === "tool_execution_update") {
500
- renderer.toolUpdate(ev.callId, ev.note);
501
- } else if (ev.type === "tool_execution_end") {
502
- renderer.toolEnd(ev.callId, ev.ok, ev.output.slice(0, 160).replace(/\n/g, " ⏎ "), ev.durationMs);
503
- } else if (ev.type === "tool_call_failed") {
504
- renderer.toolEnd(ev.callId, false, `${ev.reason}: ${ev.detail}`.slice(0, 160), 0);
505
- } else if (ev.type === "compaction") {
506
- renderer.addSystemNote(compactionNote(ev)); // one wording with the replayed marker (port #25 LOW-4)
507
- } else if (ev.type === "steer") {
508
- renderer.addSystemNote("↪ steering applied");
509
- } else if (ev.type === "verify") {
510
- // the verify gate (core/verify-gate.ts): say that the check is running — a silent two minutes reads as a hang
511
- renderer.addSystemNote(ev.state === "running" ? `⧗ verify: ${ev.command.slice(0, 120)}` : `verify ${ev.state}: ${ev.detail ?? ""}`, ev.state === "running" || ev.state === "passed" ? "info" : "warn");
512
- } else if (ev.type === "run_end") {
513
- lastView?.done();
514
- if (ev.status === "error") renderer.addSystemNote(ev.summary, "error");
515
- else if (ev.status !== "done") renderer.addSystemNote(`run ${ev.status}: ${ev.summary}`, "warn");
516
- else if (ev.outstanding) { const c = outstandingClause(ev.outstanding); if (c !== null) renderer.addSystemNote(`done · ${c}`, outstandingTone(ev.outstanding)); } // "done" ≠ finished: say what was left (core/loop.ts)
517
- // if the model produced no streaming deltas, surface the final text
518
- if (views.size === 0 && ev.status === "done" && ev.summary) {
519
- const v = renderer.beginAssistant(); v.append(ev.summary); v.done();
520
- }
521
- }
522
- }
523
- } finally {
524
- run = null; runAbort = null;
525
- state.busy = false;
526
- refreshUsage();
527
- // port #14: surface any fallback-chain advances the router made during the run
528
- for (const n of rt.drainRouterNotes()) renderer.addSystemNote(n, "warn");
529
- renderer.setBusy(false);
530
- pushStatus();
531
- }
532
- };
533
-
534
- /** A plain user turn — also the path custom commands submit their rendered prompt through (port #30). */
535
- const submit = (text: string): Promise<void> => {
536
- // port #34: text is required. The editor drops an empty Enter before onSubmit (pi-renderer.ts),
537
- // but a custom command whose template renders to "" (`/ask` on a bare `$ARGUMENTS`) lands here:
538
- // an empty goal never starts a run, is never queued as a steer, and leaves the stage (and any
539
- // pending mode switch) for the next real message
540
- if (!text.trim()) {
541
- const n = store.stagedAttachments.length;
542
- renderer.addSystemNote(n === 0 ? "nothing to send — the message is empty" : `type a message to send with the attached image${n === 1 ? "" : "s"}`, "warn");
543
- return Promise.resolve();
544
- }
545
- renderer.addUser(userTurnLine(text, store.stagedAttachments)); // port #34: image chips under the text — the stage folds into this message
546
- // port #20: a pending mode switch becomes a durable session entry on the next
547
- // submit (round-trip cancellation: toggling back before submitting records nothing)
548
- flushModeSwitch(modes, store);
549
- if (state.busy) { steering.push(text); renderer.addSystemNote(`queued as steering (applies before the next model turn)${queuedAttachNote(store)}`); return Promise.resolve(); }
550
- return startRun(text);
551
- };
552
- const failedStartup = async (e: unknown): Promise<never> => {
553
- opts.startup?.finish();
554
- close(false);
555
- await closedP;
556
- if (e instanceof SandboxConfigError && opts.exitOnClose !== false) { console.error(`error: ${e.message}`); process.exit(2); }
557
- throw e;
558
- };
559
- const ready = async (): Promise<void> => {
560
- try { await waitForStartup(rt.sandbox.ready, opts.startup?.signal); }
561
- catch (e) { await failedStartup(e); }
562
- };
563
- // CLI startup keeps the intro on screen through the required probe. Embedders without a loading
564
- // screen retain the synchronous input-wiring contract (tests may type immediately after runTui()).
565
- if (opts.startup) { opts.startup.status("checking sandbox"); await ready(); opts.startup.status("preparing interface"); }
566
- // port #44: a renderer with panels (sextant) reads the runtime through this handle — once, before start()
567
- _trace("renderer.attach");
568
- renderer.attach?.(buildSextantAttach({
569
- cwd: rt.cwd, sessionsDir, store: () => store, tasks: rt.tasks, model: () => modes.modelFor(), catalog, runtime: () => rt, petName: opts.pet,
570
- // the connect wizard's registry seams (draw-wizard.ts): the same ones /setup walks — SETUP_PICKS
571
- // for the rows, saveCredential + refresh for the key, reg.models for the list, probe + setDefault
572
- // for the test. Classic keeps cmdSetup's pickOne chain; only the sextant surface opens the wizard.
573
- wizard: {
574
- // the LIVE registry is the list: configured providers first (the ones one ⏎ from working),
575
- // then every other row the registry knows (built-ins without a key, custom rows like a proxy
576
- // added with /provider add), then the two "your own URL" doors. SETUP_PICKS alone hid the
577
- // person's own rows — a default of kaesra or an hn added by hand never appeared, and the
578
- // walk had no way to reach them.
579
- providers: () => {
580
- const rows: { key: string; label: string; configured?: boolean; local?: boolean; url?: "openai" | "anthropic" }[] = [];
581
- const seen = new Set<string>();
582
- const ordered = [...rt.providers.list()].sort((a, b) => Number(isConfigured(b)) - Number(isConfigured(a)));
583
- for (const p of ordered) {
584
- if (seen.has(p.id) || p.id === "custom") continue; // the env ROVECODE_BASE_URL pair is not a wizard row
585
- seen.add(p.id);
586
- rows.push({ key: p.id, label: p.id === "kaesra" ? "kaesra — the default host" : p.id, ...(isConfigured(p) ? { configured: true } : {}), ...(p.noKey === true ? { local: true } : {}) });
587
- }
588
- for (const p of SETUP_PICKS) if (p.url !== undefined) rows.push({ key: p.key, label: p.label, url: p.url });
589
- return rows;
590
- },
591
- // a url door (SETUP_PICKS 8/9): register the row FIRST — without it, KEY had "unknown provider 8"
592
- register: (id, url, protocol) => {
593
- const r = rt.providers.add({ id, baseUrl: url, protocol }, "user");
594
- return "error" in r ? { ok: false, error: r.error } : { ok: true };
595
- },
596
- storeKey: (provider, secret) => {
597
- try {
598
- // a registry row's key IS its id; a url door was rewritten to its id at registration
599
- const row = rt.providers.get(provider);
600
- const id = row?.id ?? provider;
601
- if (row === undefined) return { ok: false, error: `unknown provider "${provider}"` };
602
- saveCredential(row.id, secret, row.keyEnv);
603
- rt.providers.refresh();
604
- return { ok: true };
605
- } catch (e) { return { ok: false, error: e instanceof Error ? e.message : String(e) }; }
606
- },
607
- models: async (provider) => {
608
- const id = provider; // a registry row's key is its id; a door key never reaches here (it registers first)
609
- const row = rt.providers.get(id);
610
- // each row carries the provider's OWN facts so the step's checked set can seed from them:
611
- // `active` = pinned today (spec.models), `defaultModel` = the row's default. The list itself
612
- // is the same source /models reads — a pinned provider serves its file list, an unpinned one
613
- // its endpoint — so the rows and what /models would answer cannot disagree.
614
- const pinned = new Set(row?.models ?? []);
615
- const def = row?.defaultModel;
616
- const rows: { id: string; note?: string; active?: boolean; defaultModel?: boolean }[] = [];
617
- const mark = (m: string): void => {
618
- rows.push({ id: m, ...(pinned.has(m) ? { active: true } : {}), ...(m === def ? { defaultModel: true, note: "default" } : {}) });
619
- };
620
- const r = await rt.providers.models(id);
621
- if (r.ok) { for (const m of r.models) if (!rows.some((x) => x.id === m)) mark(m); }
622
- // a pinned list the endpoint no longer names still shows (the file is the truth until edited);
623
- // fetch failed AND nothing pinned: an empty list → the caller falls back to typing the id
624
- for (const m of pinned) if (!rows.some((x) => x.id === m)) mark(m);
625
- return rows;
626
- },
627
- // the model step's ACTIVATE answer: pin the checked rows as the provider's active list
628
- // (spec.models — /models serves them from the file after this), or un-pin with an empty
629
- // list ("use them all": the endpoint is asked every time, nothing frozen). `makeDefault`
630
- // also becomes the row's default and the session's model, like the test step does.
631
- activateModels: (provider, models, makeDefault) => {
632
- const id = provider;
633
- if (rt.providers.get(id) === undefined) return { ok: false, error: "no provider behind this row" };
634
- const r = rt.providers.setModels(id, models, "user");
635
- if ("error" in r) return { ok: false, error: r.error };
636
- if (makeDefault !== undefined) {
637
- const d = rt.providers.setDefault(`${id}/${makeDefault}`, "user");
638
- if (!("error" in d)) {
639
- modes.setModel({ provider: d.provider, model: d.model });
640
- state.provider = modes.modelFor().provider;
641
- state.model = modes.modelFor().model;
642
- pushStatus();
643
- }
644
- }
645
- return { ok: true, ...r };
646
- },
647
- test: async (provider, model) => {
648
- const id = provider;
649
- if (rt.providers.get(id) === undefined) return { ok: false, detail: "no provider behind this row" };
650
- const probe = await rt.providers.probe(id, model);
651
- if (!probe.ok) return { ok: false, detail: probe.detail };
652
- const d = rt.providers.setDefault(`${id}/${model}`, "user");
653
- if ("error" in d) return { ok: false, detail: d.error };
654
- // the session switches with the default (providers-cmd applyRef's three lines, inlined —
655
- // that helper needs a ProviderCmdCtx; the wizard seam already holds the same pieces)
656
- modes.setModel({ provider: d.provider, model: d.model });
657
- state.provider = modes.modelFor().provider;
658
- state.model = modes.modelFor().model;
659
- pushStatus();
660
- return { ok: true, detail: `${probe.detail} — default → ${id}/${model}` };
661
- },
662
- },
663
- }));
664
- // /model suggestions: the ids of every configured provider's models, fetched off the boot path and again
665
- // whenever the registry changes; the sextant reads the list at suggestion time (SlashCommand.choices)
666
- const modelChoices: string[] = [];
667
- const refreshModelChoices = (): void => { void listModelIds(rt.providers).then((ids) => { modelChoices.splice(0, modelChoices.length, ...ids); }).catch(() => {}); };
668
- renderer.setCommands([...TUI_COMMANDS.map((c) => (c.name === MODEL_COMMAND.name ? { ...c, choices: () => modelChoices } : c)), ...commandsForPalette(custom.commands)]);
669
- setTimeout(refreshModelChoices, 0);
670
- // Node prints warnings on stderr, and stderr goes straight onto the alternate screen. A
671
- // MaxListenersExceededWarning does not just say its sentence — it dumps the emitter it is complaining
672
- // about, which for a stream is pages of `[Function: …]`, over the panels. Berkay hit exactly that
673
- // during a Playwright MCP session (the leak itself is fixed in mcp/client.ts; this is the other half:
674
- // no warning from anywhere should be able to garble the screen). Node's own printer is removed and the
675
- // warning becomes a note — still said, never drawn over anything — and put back on the way out.
676
- const nodeWarnListeners = process.listeners("warning");
677
- // assigned here, read by `close` above (declared before this point, called only after start)
678
- process.removeAllListeners("warning");
679
- const onWarning = (w: Error): void => {
680
- // the first line only: a MaxListenersExceededWarning's body is the emitter it is complaining about
681
- const first = w.message.split("\n")[0] ?? w.message;
682
- renderer.addSystemNote(`node: ${w.name === "Warning" ? "" : `${w.name}: `}${first}`, "warn");
683
- };
684
- process.on("warning", onWarning);
685
- restoreWarnings = (): void => {
686
- process.off("warning", onWarning);
687
- for (const l of nodeWarnListeners) process.on("warning", l as (w: Error) => void);
688
- };
689
- let seeded = false;
690
- const seed = (): void => {
691
- if (seeded) return;
692
- seeded = true;
693
- if (boot.id !== undefined) { replayHistory(); refreshUsage(); }
694
- emitBootNotes({ renderer, rt, yolo: state.yolo, modelRef: state, mode: state.mode, version: pkg.version, width: process.stdout.columns ?? 80, sessionId: boot.id !== undefined ? store.id : undefined, bootWarn: boot.warn, commandWarnings: custom.warnings });
695
- pushStatus();
696
- };
697
- let revealed = false;
698
- const reveal = (): void => {
699
- if (revealed) return;
700
- opts.startup?.signal?.throwIfAborted();
701
- revealed = true;
702
- _trace("complete first frame ready; releasing intro");
703
- opts.startup?.finish();
704
- };
705
- _trace("renderer.start");
706
- try {
707
- const starting = renderer.start({
708
- // `#<text>` is a memory note, not a prompt: it short-circuits before any model turn (tui/memory-note.ts),
709
- // the way `/` does. The classifier is narrow on purpose — a pasted `#include` block or a `# heading` is a
710
- // prompt for the model, so only a single line of `#` + a letter or digit is taken as a note.
711
- onSubmit: (text) => {
712
- if (text.startsWith("/")) handleSlash(text);
713
- else if (runMemoryNote({ renderer, blocks: () => blocks }, text)) return;
714
- else if (shellLine(text) !== null) void runShellLine(shellCtx, text);
715
- else void submit(text);
716
- },
717
- // port #21: abort FIRST (kills in-flight fetch/subprocesses), then return() settles the generator
718
- onInterrupt: () => { runAbort?.abort(); void run?.return(undefined as never); renderer.addSystemNote("run interrupted", "warn"); },
719
- onExit: () => close(),
720
- }, { beforeFirstRender: seed, onReveal: reveal, ...(opts.startup ? {
721
- animate: false,
722
- beforeReveal: () => {
723
- _trace("first frame prepared; waiting for intro");
724
- return waitForStartup(opts.startup?.animationDone ?? Promise.resolve(), opts.startup?.signal);
725
- },
726
- } : {}) });
727
- seed(); // compatibility with injected renderers that ignore beforeFirstRender
728
- rt.warmRepoMap(); // cooperate with the intro while the prepared scene waits to be revealed
729
- if (starting) await waitForStartup(starting, opts.startup?.signal);
730
- reveal(); // legacy renderers without the optional handoff callback
731
- } catch (e) { await failedStartup(e); }
732
- if (closed) { await closedP; return; }
733
- _trace("interface ready");
734
- // From here the terminal is in a mode the shell cannot live in: alt screen, raw stdin, mouse and focus
735
- // reporting. `close()` undoes all of it; a crash used to undo none of it, leaving the prompt inside the
736
- // alt screen under a frame that never erased, with every mouse movement typing escape sequences at it.
737
- // The guard restores the terminal FIRST and only then writes the reason to a file — the screen a stack
738
- // trace would print on is the one the restore erases, so the file is the only copy that survives.
739
- uninstallCrashGuard = installCrashGuard({
740
- restore: () => renderer.restoreTerminal?.(),
741
- logDir: join(rovecodeHome(), "logs"),
742
- version: pkg.version,
743
- });
744
- // The update fetch stays outside the note emitter: fresh sessions only, never blocking the first prompt.
745
- if (boot.id === undefined) void checkForUpdate(pkg.version).then((s) => { const l = updateLine(s); if (l !== null) renderer.addSystemNote(l); }).catch(() => {});
746
- watchProviders(provCtx()); // follow a default-model change made elsewhere; announce the first provider
747
- if (!opts.startup) await ready();
748
- await closedP;
749
- }