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
@@ -1,574 +0,0 @@
1
- /** SextantRenderer (port #44): the sextant surface as rovecode's `Renderer` — the ONE controller stays
2
- * runTui (ADR-003, no second loop); this class maps the Renderer seam plus the optional onEvent/attach
3
- * members onto the SextantState (#41 model), paints through FrameLoop (#40 screen, #41-#46 painters)
4
- * and routes keys through #43. Ported from the user's sextant v0.4.0 app.js:1599-1632 (boot: enter
5
- * the alt screen, state, tick, input, resize, leave).
6
- *
7
- * ONE source of truth for transcript rows while a run is busy: the RunEvent stream (onEvent →
8
- * applyEvent). The per-event Renderer calls app.ts makes for the same events are therefore no-ops
9
- * while busy (beginAssistant views, toolStart/toolUpdate/toolEnd) or dropped by exact count (the
10
- * compaction / "↪ steering applied" / run_end notes — sextant-bridge duplicateNotes); every other
11
- * note (boot, slash commands, task settlements, "queued as steering", router notes) is a row + toast.
12
- * When NOT busy (history replay after /resume, /rewind, /new) the same methods build rows directly.
13
- * Renderer-local slash commands (/theme /open /diff /focus /agents, the /help card) run in keys.ts
14
- * runLocal through the `local` hooks below — src/sextant/local-commands.ts is their future home.
15
- * I/O: the terminal through TerminalIO; git/fs through sextant-repo.ts (RepoWatcher: scheduled OFF
16
- * the frame loop from the tick and run async, so a slow `git status` never freezes a frame). */
17
-
18
- import { homedir } from "node:os";
19
- import { basename, join } from "node:path";
20
- import pkg from "../../package.json";
21
- import type { RunEvent } from "../core/types.ts";
22
- import { loadTodos } from "../tools/todo.ts";
23
- import type { ApprovalAnswer, AssistantView, PickItem, QuestionAnswer, QuestionPrompt, Renderer, RendererHooks, RendererStartOptions, SlashCommand, StatusInfo } from "../tui/renderer.ts";
24
- import { drawAgents } from "./draw-agents.ts";
25
- import { openMarket } from "./draw-market.ts";
26
- import { closeWizard, openWizard, wizardEndpointRegistered, wizardKeyStored, wizardModelConfirmed, type WizardProvider } from "./draw-wizard.ts";
27
- import { docsFor, install, loadMarket, planFor } from "./market-source.ts";
28
- import { openContext } from "./draw-context.ts";
29
- import { fixedFrom, loadContext } from "./context-source.ts";
30
- import { hunksFromUnified, setAgentsPainter } from "./draw-code.ts";
31
- import { fuzzy } from "./engine.ts";
32
- import { spawnGitAsync, toAsync, type GitRunner, type GitRunnerAsync } from "./git-status.ts";
33
- import { enterSequence, leaveSequence } from "./input.ts";
34
- import { ESC_WINDOW_MS, type KeyCtx } from "./keys.ts";
35
- import { initialState, makeApplyEvent, planCounts, pushToast, setCrew, setPlan, setUsage } from "./model.ts";
36
- import { suggestions } from "./overlays.ts";
37
- import { createPet, type Pet } from "./pet.ts";
38
- import { argsFromPreview, duplicateNotes, petOnEvent, petOnTask, replayToolRow, userRow, type LiveCall } from "./sextant-bridge.ts";
39
- import { CardHost } from "./sextant-cards.ts";
40
- import { editOpsOf, type HashlineOp } from "./sextant-diff-base.ts";
41
- import { readFileBounded } from "./sextant-files.ts";
42
- import { FrameLoop } from "./sextant-frame-loop.ts";
43
- import { RepoWatcher } from "./sextant-repo.ts";
44
- import { buildTheme, isThemeName } from "./theme.ts";
45
- import { clipText, relPath, summarizeEnd } from "./tool-rows.ts";
46
- import type { ApplyEvent, InputEvent, SextantAttach, SextantState, TerminalIO, Theme, ThemeName, ToolRow } from "./types.ts";
47
-
48
- export { IDLE_SCAN_MS } from "./sextant-repo.ts";
49
-
50
- export interface SextantRendererOptions {
51
- io: TerminalIO;
52
- /** the frame clock (Date.now in production; tests inject a controllable one) */
53
- clock?: () => number;
54
- /** starting palette (ROVECODE_THEME / --theme); an unknown name falls back to night */
55
- theme?: string;
56
- /** the pet's name (`--pet <name>`); attach() may override it */
57
- pet?: string;
58
- /** emit truecolor SGR (default true; false quantizes to xterm-256) */
59
- truecolor?: boolean;
60
- /** the project dir until attach() supplies the runtime's cwd */
61
- cwd?: string;
62
- /** git seam for tests (a sync fake is wrapped); undefined = spawn git asynchronously */
63
- git?: GitRunner | GitRunnerAsync;
64
- /** false disables the repo scan entirely (pure tests) */
65
- scan?: boolean;
66
- }
67
-
68
- const TOAST_MAX = 48;
69
- const NOOP_VIEW: AssistantView = { append() {}, done() {} };
70
- const NO_HOOKS: RendererHooks = { onSubmit() {}, onInterrupt() {}, onExit() {} };
71
-
72
- export class SextantRenderer implements Renderer {
73
- /** the surface state (keys.ts and the reducer mutate it; tests read it) */
74
- readonly state: SextantState;
75
- private theme: Theme;
76
- private readonly io: TerminalIO;
77
- private readonly clock: () => number;
78
- private readonly pet: Pet;
79
- private readonly loop: FrameLoop;
80
- /** the git/fs work, off the loop and async (sextant-repo.ts) */
81
- private readonly repo: RepoWatcher;
82
- private readonly applyEv: ApplyEvent;
83
- /** SGR mode the Screen emits (false = the xterm-256 quantizer; tests/pickRenderer read it) */
84
- readonly truecolor: boolean;
85
- private hooks: RendererHooks | null = null;
86
- private ctx: SextantAttach | null = null;
87
- private unsubTasks: (() => void) | null = null;
88
- private started = false;
89
- private stopped = false;
90
- /** between setBusy(true) and setBusy(false): the event stream owns the rows */
91
- private busy = false;
92
- private dropNotes = 0;
93
- /** the approval / question / picker promises (sextant-cards.ts) */
94
- private readonly cards: CardHost;
95
- private readonly liveCalls = new Map<string, LiveCall>();
96
- /** per cwd-relative path: the content when its edit/write was APPROVED (null = did not exist) — the
97
- * pre-edit diff base. Captured at askApproval (the one moment before the tool runs), NOT at
98
- * tool_execution_start: that event reaches the renderer through the loop's buffered pump after
99
- * rovecode's synchronous edit/write already wrote, so a snapshot then is the post-edit file. Ungated
100
- * calls (yolo / always) rebuild their base from the hashline ops instead (sextant-diff-base.ts). */
101
- private readonly before = new Map<string, string | null>();
102
- /** per live edit callId: its hashline ops (the base of an ungated edit is rebuilt from them at the end) */
103
- private readonly editOps = new Map<string, HashlineOp[]>();
104
- /** the file whose content the code panel holds (null → reload on the next tick) */
105
- private loadedFile: string | null = null;
106
- private planLoaded = false;
107
- private todoDone = 0;
108
-
109
- /** the model step's ONE fetch: the chosen provider through the wizardModels seam, the answer
110
- * written with wizardModelConfirmed. Empty or failed leaves the step on the typed fallback. */
111
- private fetchWizardModels(): void {
112
- const w = this.state.wizard;
113
- if (!w) return;
114
- w.modelsLoading = true;
115
- this.loop.markDirty();
116
- const provider = w.providers[w.selected]?.key ?? "";
117
- void (this.ctx?.wizardModels?.(provider) ?? Promise.resolve([]))
118
- .then((models) => {
119
- if (!this.state.wizard) return;
120
- wizardModelConfirmed(this.state.wizard, models);
121
- if (models.length === 0) this.state.wizard.error = "the endpoint listed no models — type the id by hand (it still pins)";
122
- this.loop.markDirty();
123
- })
124
- .catch(() => {
125
- if (!this.state.wizard) return;
126
- this.state.wizard.modelsLoading = false;
127
- this.state.wizard.error = "the model list could not be read — type the id by hand";
128
- this.loop.markDirty();
129
- });
130
- }
131
-
132
- constructor(o: SextantRendererOptions) {
133
- this.io = o.io; this.clock = o.clock ?? Date.now; this.truecolor = o.truecolor ?? true;
134
- const themeName: ThemeName = isThemeName(o.theme ?? "") ? (o.theme as ThemeName) : "night";
135
- this.theme = buildTheme(themeName);
136
- const cwd = o.cwd ?? process.cwd();
137
- this.state = initialState({ cwd, repo: { name: basename(cwd) || cwd, branch: null }, version: pkg.version, theme: themeName, mode: "act", yolo: false, commands: [], now: this.clock() });
138
- this.pet = createPet({ name: o.pet });
139
- // the reducer's diffFor seam stays empty: the hunks arrive through the watcher's scheduleDiff (a
140
- // setTimeout(0) from the end event, then async git — nothing runs inside the event dispatch or a painter)
141
- this.applyEv = makeApplyEvent();
142
- this.repo = new RepoWatcher({ state: this.state, clock: this.clock, git: o.git ? toAsync(o.git) : spawnGitAsync, dirty: () => this.loop.markDirty() }, o.scan ?? true);
143
- this.cards = new CardHost({
144
- state: this.state, dirty: () => this.loop.markDirty(), stopped: () => this.stopped,
145
- onApprovalOpen: () => this.pet.event("permission", undefined, this.clock()),
146
- onAllowed: () => this.pet.event("allowed", undefined, this.clock()),
147
- });
148
- setAgentsPainter(drawAgents); // #46: the crew board paints the ∷ mode
149
- this.loop = new FrameLoop({
150
- io: this.io, clock: this.clock, state: this.state, theme: () => this.theme, pet: this.pet, truecolor: this.truecolor,
151
- keyCtx: () => ({ hooks: this.hooks ?? NO_HOOKS, local: this.local }),
152
- beforeInput: (ev, now) => this.beforeInput(ev, now),
153
- afterInput: (ev, now) => this.afterInput(ev, now),
154
- onTick: (now) => this.onTick(now),
155
- });
156
- }
157
-
158
- // ------------------------------------------------------------------ test/smoke seams
159
- /** the last painted frame as text ("" before start) */
160
- frameText(): string { return this.loop.frameText(); }
161
- /** the frame interval is live */
162
- get active(): boolean { return this.loop.active; }
163
- /** frames painted so far (tests: "nothing paints after stop") */
164
- get frames(): number { return this.loop.frames; }
165
- get themeName(): ThemeName { return this.theme.name; }
166
- /** run one frame-loop tick now (tests: deterministic instead of waiting 40 ms) */
167
- tick(): void { this.loop.tick(); }
168
- /** parse input held by the ESC-hold timer now (tests) */
169
- flushInput(): void { this.loop.flushInput(); }
170
-
171
- // ------------------------------------------------------------------ lifecycle
172
- start(hooks: RendererHooks, options?: RendererStartOptions): void | Promise<void> {
173
- if (this.started) return;
174
- this.started = true; this.hooks = hooks;
175
- this.state.bootAt = options?.animate === false ? -Infinity : this.clock();
176
- options?.beforeFirstRender?.();
177
- const reveal = (): void => {
178
- if (this.stopped) return;
179
- // Rebuild at the current size/state before handing off: a resize or file refresh may have landed.
180
- this.loop.start(() => {
181
- options?.onReveal?.();
182
- this.terminalEntered = true;
183
- this.io.enterRaw();
184
- this.io.write(enterSequence(true));
185
- }, options?.beforeReveal !== undefined);
186
- };
187
- if (!options?.beforeReveal) { reveal(); return; }
188
- this.loop.prepare(); // textbox, cursor and all panels really rendered behind the intro
189
- const files = this.repo.prepare().catch(() => {
190
- if (!this.stopped) this.addSystemNote("initial file list unavailable — file tools remain available", "warn");
191
- });
192
- return Promise.all([files, options.beforeReveal()]).then(reveal);
193
- }
194
- private terminalEntered = false;
195
-
196
- /** clear the interval, settle every open card/picker (deny / null), restore the terminal */
197
- /** settled once stop() has run AND the repo watcher's git children are gone (sextant-repo.ts stop) */
198
- private drained: Promise<void> | null = null;
199
- /** wait for the git children a stop() killed to be gone — call after stop(); a caller that removes
200
- * the cwd (a test's scratch repo) must await this first, or Windows answers EBUSY */
201
- drain(): Promise<void> { return this.drained ?? this.repo.stop(); }
202
- stop(): void {
203
- if (this.stopped) return;
204
- this.stopped = true;
205
- this.loop.stop();
206
- this.drained = this.repo.stop(); // kills the git children in flight; drain() awaits them
207
- this.unsubTasks?.(); this.unsubTasks = null;
208
- this.cards.settleAll();
209
- this.restoreTerminal();
210
- this.hooks = null;
211
- }
212
-
213
- /** The terminal, and nothing else. `stop()` ends with this; the crash guard (tui/crash-guard.ts)
214
- * calls it on its own, because everything above in `stop()` — settling cards, killing the repo
215
- * watcher's git children — can throw, and a throw before the restore leaves the alt screen open with
216
- * mouse reporting on, which is the state where every pointer movement types escape sequences at the
217
- * shell prompt. Idempotent (the second call has nothing to undo) and silent on failure: if writing to
218
- * stdout is itself what broke, there is nothing left to try. */
219
- restoreTerminal(): void {
220
- if (!this.terminalEntered || this.restored) return;
221
- this.restored = true;
222
- try { this.io.write(leaveSequence()); } catch { /* the pipe may already be gone */ }
223
- try { this.io.leaveRaw(); } catch { /* ditto */ }
224
- }
225
- private restored = false;
226
-
227
- attach(ctx: SextantAttach): void {
228
- this.ctx = ctx;
229
- const s = this.state;
230
- s.cwd = ctx.cwd; s.repo.name = basename(ctx.cwd) || ctx.cwd;
231
- if (ctx.petName) this.pet.state.name = ctx.petName.slice(0, 14);
232
- this.unsubTasks?.();
233
- const sync = (): void => {
234
- const list = ctx.tasks.list();
235
- if (list.length > s.crew.length) s.code.lane = list.length - 1; // the newest lane is selected
236
- setCrew(s, list); this.loop.markDirty();
237
- };
238
- sync();
239
- this.unsubTasks = ctx.tasks.subscribe((t) => { sync(); petOnTask(this.pet, t, this.clock()); });
240
- this.repo.afterAttach();
241
- this.refreshPlan();
242
- }
243
-
244
- // ------------------------------------------------------------------ the event stream (source of truth while busy)
245
- onEvent(ev: RunEvent): void {
246
- const now = this.clock(), s = this.state;
247
- this.applyEv(s, ev, now);
248
- petOnEvent(this.pet, ev, this.liveCalls, s.cwd, now);
249
- this.dropNotes += duplicateNotes(ev);
250
- if (ev.type === "tool_execution_start" && ev.tool === "edit") this.editOps.set(ev.callId, editOpsOf(ev.args));
251
- if (ev.type === "tool_execution_end" || ev.type === "tool_call_failed") {
252
- const row = this.rowOf(ev.callId);
253
- const before = row?.path !== undefined ? this.before.get(row.path) : undefined;
254
- if (row?.path !== undefined) this.before.delete(row.path);
255
- const ops = this.editOps.get(ev.callId) ?? [];
256
- this.editOps.delete(ev.callId);
257
- if (row && (row.verb === "edit" || row.verb === "write" || row.verb === "remove" || row.verb === "run")) {
258
- this.repo.afterMutation();
259
- if (row.path !== undefined && row.path === s.code.file) s.code.content = null; // reload the edited file (before the next paint: onTick)
260
- this.loadedFile = null;
261
- if (ev.type === "tool_execution_end" && ev.ok && row.path !== undefined && (row.verb === "edit" || row.verb === "write")) this.repo.scheduleDiff(row, row.path, before, ops);
262
- }
263
- }
264
- this.loop.markDirty();
265
- }
266
-
267
- /** pre-approval (the approver runs BEFORE the tool executes — the one real-time moment): capture the
268
- * file's current content as the diff base, and let the card's previewDiff text fill the code panel's
269
- * diff view for the pending file; returns the cwd-relative file (null when the card names none) */
270
- private previewInCodePanel(tool: string, argsPreview: string, detail: string | undefined): string | null {
271
- if (tool !== "edit" && tool !== "write") return null;
272
- const path = argsFromPreview(argsPreview).path;
273
- if (typeof path !== "string" || !path) return null;
274
- const file = relPath(this.state.cwd, path);
275
- this.before.set(file, readFileBounded(this.state.cwd, file));
276
- const hunks = detail ? hunksFromUnified(detail) : [];
277
- if (!hunks.length) return file;
278
- const rows = hunks.flatMap((h) => h.rows);
279
- this.state.code.diff = { file, hunks, add: rows.filter((r) => r.op === "+").length, del: rows.filter((r) => r.op === "-").length };
280
- this.state.code.mode = "diff";
281
- return file;
282
- }
283
-
284
- // ------------------------------------------------------------------ Renderer → state
285
- setCommands(cmds: SlashCommand[]): void {
286
- this.state.commands = cmds.map((c) => ({ name: c.name, description: c.description, ...(c.choices ? { choices: c.choices } : {}), ...(c.choicesThen ? { choicesThen: c.choicesThen } : {}) }));
287
- this.loop.markDirty();
288
- }
289
-
290
- addUser(text: string): void { this.push(userRow(text, this.clock())); }
291
-
292
- addSystemNote(text: string, tone: "info" | "warn" | "error" = "info"): void {
293
- if (this.dropNotes > 0) { this.dropNotes--; return; } // the reducer already made this row from the event
294
- this.push({ kind: "system", text, tone });
295
- // toasts: every warning/error, and one-line info notes; a multi-line info note (boot banner, /help,
296
- // /cost) is a transcript row only — a clipped first line would only smear the top of the code panel
297
- if (tone !== "info" || (!text.includes("\n") && text.length <= TOAST_MAX)) this.toast(text.split("\n")[0] ?? "", tone);
298
- }
299
-
300
- beginAssistant(): AssistantView {
301
- if (this.busy) return NOOP_VIEW; // message_update events stream the row
302
- const row: Extract<SextantState["messages"][number], { kind: "assistant" }> = { kind: "assistant", text: "", streaming: true };
303
- this.push(row);
304
- return { append: (delta) => { row.text += delta; this.loop.markDirty(); }, done: () => { row.streaming = false; this.loop.markDirty(); } };
305
- }
306
-
307
- toolStart(callId: string, tool: string, argsPreview: string): void {
308
- if (this.busy) return;
309
- this.push(replayToolRow(callId, tool, argsPreview, this.state.cwd));
310
- }
311
- toolUpdate(callId: string, note: string): void {
312
- if (this.busy) return;
313
- const row = this.rowOf(callId);
314
- if (row) { row.detail = clipText(note, 80); this.loop.markDirty(); }
315
- }
316
- toolEnd(callId: string, ok: boolean, outputPreview: string, durationMs: number): void {
317
- if (this.busy) return;
318
- let row = this.rowOf(callId);
319
- if (!row) { row = replayToolRow(callId, "tool", "{}", this.state.cwd); this.push(row); }
320
- row.running = false; row.ok = ok; row.ms = durationMs;
321
- const end = summarizeEnd({ verb: row.verb }, row.tool, ok, outputPreview);
322
- if (end.detail) row.detail = end.detail;
323
- this.loop.markDirty();
324
- }
325
-
326
- /** the approval card (sextant-cards.ts); an edit/write preview also fills the code panel's diff view.
327
- * A denied call never executes (no row, no end event), so its captured base is dropped here — a later
328
- * ungated edit of the same file must not diff against that stale snapshot */
329
- askApproval(tool: string, argsPreview: string, detail?: string): Promise<ApprovalAnswer> {
330
- const file = this.previewInCodePanel(tool, argsPreview, detail);
331
- const answer = this.cards.approval(tool, argsPreview, detail);
332
- if (file !== null) void answer.then((a) => { if (a === "deny") this.before.delete(file); });
333
- return answer;
334
- }
335
- /** the palette as a picker (sextant-cards.ts) */
336
- pickOne(items: PickItem[], title?: string): Promise<string | null> { return this.cards.pick(items, title); }
337
- /** the question card (sextant-cards.ts): `signal` abort dismisses it, a second concurrent ask is rejected */
338
- askQuestion(q: QuestionPrompt, signal?: AbortSignal): Promise<QuestionAnswer | null> { return this.cards.question(q, signal); }
339
-
340
- clearTranscript(): void { this.state.messages = []; this.state.stick = true; this.pet.event("fresh", undefined, this.clock()); this.loop.markDirty(); }
341
-
342
- prefillEditor(text: string): void {
343
- const I = this.state.input;
344
- I.text = text; I.cur = text.length; I.sgSel = 0; I.histIdx = -1; this.state.focus = "messages"; this.loop.markDirty();
345
- }
346
-
347
- /** busy = a run is in flight: the event stream owns the rows. setBusy(false) after a run the
348
- * reducer never saw end (an interrupted generator yields no run_end) settles what run_end would have. */
349
- setBusy(busy: boolean, label?: string): void {
350
- const s = this.state, now = this.clock();
351
- this.busy = busy;
352
- if (busy) { s.running = true; if (label) s.activity.label = label.replace(/…$/, ""); }
353
- else {
354
- this.dropNotes = 0;
355
- if (s.running) {
356
- s.running = false;
357
- if (s.activity.startedAt !== null && s.activity.endedAt === null) s.activity.endedAt = now;
358
- if (s.activity.state !== "SUCCESS" && s.activity.state !== "ERROR") { s.activity.state = "IDLE"; s.activity.label = "stopped"; }
359
- for (const r of s.messages) if (r.kind === "tool" && r.running) { r.running = false; r.ok = false; r.detail ??= "interrupted"; }
360
- this.pet.event("stopped", undefined, now);
361
- }
362
- s.escUntil = 0; delete s.ctrlCUntil;
363
- }
364
- this.loop.markDirty();
365
- }
366
-
367
- setStatus(info: StatusInfo): void {
368
- const s = this.state;
369
- s.yolo = info.yolo; s.acceptEdits = info.permission === "accept-edits"; if (info.mode) s.mode = info.mode;
370
- const u = this.ctx?.usage?.();
371
- setUsage(s, {
372
- provider: info.provider, model: info.model, turns: info.turns, tokensIn: info.tokensIn, tokensOut: info.tokensOut,
373
- ...(info.effort !== undefined ? { effort: info.effort } : {}),
374
- contextTokens: u?.contextTokens ?? 0, contextWindow: this.ctx?.contextWindow(), costUsd: u ? u.costUsd : null,
375
- });
376
- this.refreshPlan();
377
- this.loop.markDirty();
378
- }
379
-
380
- // ------------------------------------------------------------------ internals
381
- private push(row: SextantState["messages"][number]): void { this.state.messages.push(row); this.state.stick = true; this.loop.markDirty(); }
382
- private rowOf(callId: string): ToolRow | undefined {
383
- for (let i = this.state.messages.length - 1; i >= 0; i--) { const r = this.state.messages[i]!; if (r.kind === "tool" && r.callId === callId) return r; }
384
- return undefined;
385
- }
386
- private toast(text: string, tone: "info" | "warn" | "error" = "info"): void { pushToast(this.state, clipText(text.trim(), TOAST_MAX), this.clock(), tone); this.loop.markDirty(); }
387
-
388
- /** the session's todos.json → plan panel; a newly completed step is a pet quip */
389
- private refreshPlan(): void {
390
- if (!this.ctx) return;
391
- setPlan(this.state, loadTodos(join(this.ctx.sessionsDir, this.ctx.store().id)));
392
- const c = planCounts(this.state);
393
- if (this.planLoaded && c.completed > this.todoDone) this.pet.event("todo_done", { d: c.completed, n: c.total }, this.clock());
394
- this.planLoaded = true; this.todoDone = c.completed;
395
- }
396
-
397
- /** the renderer-owned effects keys.ts fires after writing the state field (theme rebuild, file/diff loading, toasts) */
398
- private readonly local: KeyCtx["local"] = {
399
- setTheme: (name) => { this.theme = buildTheme(name); this.state.theme = name; this.toast(`theme · ${name}`); this.pet.event("theme", undefined, this.clock()); },
400
- // port #54: an @image mention stages onto the store the surface attached (SextantAttach.attachImage)
401
- attachImage: (abs) => this.ctx?.attachImage?.(abs),
402
- setMode: (mode) => { if (mode === "diff" && this.state.code.file) this.repo.loadDiff(this.state.code.file); },
403
- openFile: (path) => { this.state.code.content = readFileBounded(this.state.cwd, path); this.state.code.hl = null; this.loadedFile = path; },
404
- toast: (text) => this.toast(text),
405
- // the market: the overlay is opened at once in its loading state so the frame after ⌃m already shows
406
- // the box, and the catalog is joined in when the module answers (it may touch the network)
407
- openMarket: () => {
408
- openMarket(this.state, [], { kind: "loading" });
409
- this.loop.markDirty();
410
- void loadMarket(this.state.cwd, homedir())
411
- .then((load) => { if (this.state.market) { this.state.market.rows = load.rows; this.state.market.status = load.status; this.state.market.notes = load.notes; this.loop.markDirty(); } })
412
- .catch((e: unknown) => { if (this.state.market) { this.state.market.status = { kind: "error", reason: e instanceof Error ? e.message : String(e) }; this.loop.markDirty(); } });
413
- },
414
- // /context (⌃g). The panel opens on the NEXT frame with what we already have — the model and an
415
- // empty count — so the chord feels instant, and the real numbers are joined in when counting is
416
- // done. Counting is synchronous but not free on a long transcript, so it happens off the frame.
417
- openWizard: () => {
418
- const rows = this.ctx?.wizardProviders?.();
419
- if (!rows || rows.length === 0) { this.toast("no provider table behind this surface"); return; }
420
- openWizard(this.state, rows satisfies WizardProvider[]);
421
- this.loop.markDirty();
422
- },
423
- openContext: () => {
424
- const ref = this.ctx?.model() ?? { provider: "unknown", model: "unknown" };
425
- const inputs = this.ctx?.contextInputs?.();
426
- openContext(this.state, {
427
- model: `${ref.provider}/${ref.model}`, raw: 0, estimated: 0,
428
- scale: { factor: 1, measured: false, note: "counting…" },
429
- slices: [], images: 0, billed: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
430
- unpricedTurns: 0, live: false, tolerance: 0.05,
431
- });
432
- this.loop.markDirty();
433
- if (!inputs) return; // the panel stays in its "not counted" state and says so
434
- void loadContext({
435
- messages: inputs.messages, current: ref, lookup: inputs.lookup,
436
- fixed: fixedFrom(inputs.runtime, ref),
437
- }).then((state) => {
438
- // the human may have closed it while we counted; writing into a closed panel would reopen it
439
- if (!this.state.context) return;
440
- this.state.context = { ...state, scroll: this.state.context.scroll };
441
- this.loop.markDirty();
442
- }).catch(() => { /* loadContext does not reject; this is belt and braces on the dynamic import */ });
443
- },
444
- marketPlan: (row, local) => {
445
- void planFor(row, { scope: "user", cwd: this.state.cwd, home: homedir() }, local)
446
- .then((plan) => {
447
- const m = this.state.market;
448
- if (!m) return;
449
- if ("error" in plan) { this.toast(plan.error); return; }
450
- m.plan = plan;
451
- this.loop.markDirty();
452
- });
453
- },
454
- // the connect wizard (draw-wizard.ts): the overlay owns the WALK, the attach seams own the
455
- // registry — a surface without them (the smoke harness) degrades to honest errors, the same
456
- // way reloadMcp does
457
- wizardRegister: (id, url, protocol) => {
458
- const w = this.state.wizard;
459
- const r = this.ctx?.wizardRegister?.(id, url, protocol);
460
- if (!w) return;
461
- // on success the chosen row IS this endpoint now — the walk continues at KEY with a live row
462
- wizardEndpointRegistered(w, r?.ok ?? false, r?.error ?? "this surface cannot register endpoints — use rovecode provider add");
463
- if (r?.ok) w.providers[w.selected] = { key: id, label: `${id} — ${url}`, configured: false };
464
- this.loop.markDirty();
465
- },
466
- wizardStoreKey: (provider, secret) => {
467
- const w = this.state.wizard;
468
- const r = this.ctx?.wizardStoreKey?.(provider, secret);
469
- if (!w) return;
470
- wizardKeyStored(w, r?.ok ?? false, r?.error ?? "this surface cannot store keys — use rovecode auth set");
471
- // a stored key is what makes the endpoint ASKABLE: the model step that just opened fetches now
472
- if (r?.ok) this.fetchWizardModels();
473
- this.loop.markDirty();
474
- },
475
- wizardLoadModels: () => { this.fetchWizardModels(); },
476
- // confirmModels: pin the checked rows as the provider's active list + name the default. The
477
- // seam (SextantAttach.wizardActivateModels → app.ts activateModels → registry setModels) is
478
- // Berkay's; this only runs it and advances on its answer. The confirmed id rides the state
479
- // (modelDefaultTo), so a walk back to re-edit the list keeps the marker where the person left it.
480
- wizardActivate: (provider, models, makeDefault) => {
481
- const w = this.state.wizard;
482
- if (!w) return;
483
- const r = this.ctx?.wizardActivateModels?.(provider, models, makeDefault);
484
- if (r?.ok) {
485
- closeWizard(this.state); // the walk is DONE: the list is pinned, the default is set
486
- this.toast(`connected — ${r.models !== undefined && r.models.length > 0 ? `${r.models.length} models active` : "all models live"}`);
487
- } else {
488
- w.error = r?.error ?? "the model list could not be written";
489
- }
490
- this.loop.markDirty();
491
- },
492
- wizardModel: (model) => {
493
- const w = this.state.wizard;
494
- if (!w) return;
495
- const provider = w.providers[w.selected]?.key ?? "";
496
- void (this.ctx?.wizardModels?.(provider) ?? Promise.resolve([]))
497
- .then((models) => {
498
- if (!this.state.wizard) return;
499
- if (models.length === 0) { closeWizard(this.state); this.toast(`connected — ${model} pinned by hand`); } // the typed fallback IS the finish
500
- else wizardModelConfirmed(this.state.wizard, models);
501
- this.loop.markDirty();
502
- })
503
- .catch(() => { if (this.state.wizard) { this.state.wizard.error = "the model list could not be read — type the id instead"; this.loop.markDirty(); } });
504
- },
505
- marketDocs: (row) => {
506
- void docsFor(row).then((docs) => {
507
- const m = this.state.market;
508
- if (!m || !docs) return;
509
- // write onto the row the list holds, so closing and reopening the pane costs nothing
510
- const target = m.rows.find((r) => r.kind === row.kind && r.id === row.id);
511
- if (target) target.docs = docs;
512
- this.loop.markDirty();
513
- });
514
- },
515
- marketInstall: (row, local) => {
516
- // `local` is the approved card's own answer (keys.ts hands over req.plan.local): npm runs only now
517
- void install(row, { scope: "user", cwd: this.state.cwd, home: homedir() }, local === true ? { local: true } : {}).then(async (outcome) => {
518
- // An MCP server lands in a file this session already read, so the install used to end in
519
- // "restart rovecode" — a poor answer to "I just installed it". Ask the runtime to re-read the
520
- // files and connect what is new, and report what actually happened instead of what to do next.
521
- // Only on success, only for MCP, and never fatal: a reload that throws leaves the install
522
- // reported exactly as it was, because the write DID happen and saying otherwise would be worse.
523
- let text = outcome.text;
524
- if (outcome.ok && row.kind === "mcp" && this.ctx?.reloadMcp) {
525
- try {
526
- const r = await this.ctx.reloadMcp();
527
- const failed = r.failed.find((f) => r.added.includes(f.name));
528
- text = text.replace(/ · restart rovecode[^·]*/, "");
529
- // `skipped` before `added`: an entry the loader refused never reaches `added`, so without this
530
- // the install read "installed" and then said nothing at all about why no tools appeared.
531
- const skipped = r.skipped.length > 0 ? ` · ${r.skipped.join(" · ")}` : "";
532
- text += failed
533
- ? ` · ${failed.name} did not connect: ${failed.error}`
534
- : r.added.length > 0 ? ` · connected as ${r.added.join(", ")} — no restart needed${skipped}` : skipped;
535
- } catch { /* the install stands on its own */ }
536
- }
537
- const m = this.state.market;
538
- if (!m?.plan) return;
539
- m.plan.running = false;
540
- m.plan.outcome = { ...outcome, text };
541
- this.loop.markDirty();
542
- });
543
- },
544
- };
545
-
546
- /** renderer-level intercepts: the picker's Enter/Esc, and the two-press ⌃c guarantee (a ⌃c while busy
547
- * interrupts through keys.ts and arms ctrlCUntil; a second one inside the window quits even before the
548
- * interrupted run settled — keys.ts alone would interrupt again while s.running is still true) */
549
- private beforeInput(ev: InputEvent, now: number): boolean {
550
- const s = this.state;
551
- if (this.cards.interceptKey(ev)) return true;
552
- if (ev.type === "key" && ev.ctrl && ev.name === "c" && s.running && s.ctrlCUntil !== undefined && now < s.ctrlCUntil) { this.hooks?.onExit(); return true; }
553
- return false;
554
- }
555
- private afterInput(ev: InputEvent, now: number): void {
556
- const s = this.state;
557
- this.cards.afterKey();
558
- if (ev.type === "key" && ev.ctrl && ev.name === "c" && s.running) s.ctrlCUntil = now + ESC_WINDOW_MS;
559
- if (ev.type === "key" && !ev.ctrl && s.input.text.startsWith("/")) { const first = suggestions(s, s.files.paths, fuzzy)[0]; if (first?.kind === "slash") this.pet.suggest(first.label, now); }
560
- }
561
-
562
- /** off-frame work, BEFORE the tick's paint: the code panel's file content (a bounded sync read, so
563
- * the frame that follows an edit shows the file, never `cannot read`) and the repo scan (full after
564
- * a mutating tool, statuses while idle — async, sextant-repo.ts) */
565
- private onTick(now: number): void {
566
- const s = this.state;
567
- if (s.code.file && s.code.content === null && this.loadedFile !== s.code.file) { s.code.content = readFileBounded(s.cwd, s.code.file); this.loadedFile = s.code.file; this.loop.markDirty(); }
568
- // the staged images are owned by the store (tui/attach.ts); mirror their names so the prompt shows
569
- // chips before the message is sent — one read per tick, never a second copy of the list
570
- const staged = this.ctx?.staged?.() ?? [];
571
- if (staged.length !== s.staged.length || staged.some((n, i) => n !== s.staged[i])) { s.staged = staged; this.loop.markDirty(); }
572
- this.repo.onTick(now, s.running);
573
- }
574
- }