rovecode 0.3.2 → 0.4.0-beta.2

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 (491) hide show
  1. package/CHANGELOG.md +512 -0
  2. package/README.md +33 -45
  3. package/bin/rovecode.js +24 -0
  4. package/dist/cli/app-dybnr56b.js +2 -0
  5. package/dist/cli/ask-user-p8hq4xgj.js +2 -0
  6. package/dist/cli/auth-login-ewpgw5sm.js +2 -0
  7. package/dist/cli/auth-m8p9grty.js +2 -0
  8. package/dist/cli/bench-xv3ypwev.js +9 -0
  9. package/dist/cli/catalog-737wb2s0.js +2 -0
  10. package/dist/cli/cli-arhg40m0.js +2 -0
  11. package/dist/cli/client-cf2pxx8q.js +2 -0
  12. package/dist/cli/commands-3p7e4xxs.js +2 -0
  13. package/dist/cli/connect-3q93d7cb.js +2 -0
  14. package/dist/cli/context-cmd-eqxmxhzq.js +2 -0
  15. package/dist/cli/context-report-hbw9zfes.js +2 -0
  16. package/dist/cli/count-remote-mby98cd0.js +2 -0
  17. package/dist/cli/design-122y0axd.js +2 -0
  18. package/dist/cli/dispatch-b4egzvvh.js +2 -0
  19. package/dist/cli/doctor-x4jkv72e.js +3 -0
  20. package/dist/cli/executor-ftvg6tsy.js +2 -0
  21. package/dist/cli/export-pdgdhkch.js +2 -0
  22. package/dist/cli/files-cez9a96p.js +2 -0
  23. package/dist/cli/gauntlet-r3xxaszc.js +2 -0
  24. package/dist/cli/gauntlet-runner-r515m7kk.js +10 -0
  25. package/dist/cli/gauntlet-wave3-bnkjk2v2.js +5 -0
  26. package/dist/cli/gauntlet-wave4-acs9s60q.js +14 -0
  27. package/dist/cli/hashline-ewg5hbe3.js +2 -0
  28. package/dist/cli/http-n0kehsk8.js +5 -0
  29. package/dist/cli/index-z5qt1s76.js +2 -0
  30. package/dist/cli/install-80mp63kx.js +2 -0
  31. package/dist/cli/loop-12twjcat.js +2 -0
  32. package/dist/cli/main-01pv9206.js +4 -0
  33. package/dist/cli/main-0jys2ccn.js +3 -0
  34. package/dist/cli/main-1ztz6fkj.js +10 -0
  35. package/dist/cli/main-23q7cmww.js +9 -0
  36. package/dist/cli/main-2rzbexn2.js +3 -0
  37. package/dist/cli/main-2wyax8k9.js +9 -0
  38. package/dist/cli/main-2yeveeve.js +6 -0
  39. package/dist/cli/main-2z3dek0b.js +3 -0
  40. package/dist/cli/main-2zgsknth.js +3 -0
  41. package/dist/cli/main-45ejth3a.js +4 -0
  42. package/dist/cli/main-45rn3trk.js +22 -0
  43. package/dist/cli/main-4p4e2w7x.js +4 -0
  44. package/dist/cli/main-4y0tnfpa.js +16 -0
  45. package/dist/cli/main-5py0rkmc.js +4 -0
  46. package/dist/cli/main-6dtqmbt6.js +7 -0
  47. package/dist/cli/main-6h9x282m.js +4 -0
  48. package/dist/cli/main-6vjeds42.js +3 -0
  49. package/dist/cli/main-78gq4bt9.js +6 -0
  50. package/dist/cli/main-7jd5vh3x.js +4 -0
  51. package/dist/cli/main-7kt6r53y.js +4 -0
  52. package/dist/cli/main-8c1tbazx.js +58 -0
  53. package/dist/cli/main-9a9rnh47.js +19 -0
  54. package/dist/cli/main-9ht36z12.js +3 -0
  55. package/dist/cli/main-a2yfvcy9.js +7 -0
  56. package/dist/cli/main-a3f51n0x.js +5 -0
  57. package/dist/cli/main-b8zq261k.js +3 -0
  58. package/dist/cli/main-bxtvnf6d.js +13 -0
  59. package/dist/cli/main-edxc3yzt.js +4 -0
  60. package/dist/cli/main-evgz4mp5.js +21 -0
  61. package/dist/cli/main-f33fc5je.js +9 -0
  62. package/dist/cli/main-fvnpq46y.js +12 -0
  63. package/dist/cli/main-gbbty4d4.js +3 -0
  64. package/dist/cli/main-gth53dnt.js +25 -0
  65. package/dist/cli/main-hqbz10aw.js +9 -0
  66. package/dist/cli/main-hrrvcfan.js +38 -0
  67. package/dist/cli/main-hzwtsb2m.js +5 -0
  68. package/dist/cli/main-j7ttv0sd.js +34 -0
  69. package/dist/cli/main-jak598k9.js +5 -0
  70. package/dist/cli/main-kba6zeyd.js +6 -0
  71. package/dist/cli/main-kwwsz6rq.js +3 -0
  72. package/dist/cli/main-m8vm17zq.js +3 -0
  73. package/dist/cli/main-mg4f96e1.js +3 -0
  74. package/dist/cli/main-mg9b20ac.js +18 -0
  75. package/dist/cli/main-mgb9ccnx.js +3 -0
  76. package/dist/cli/main-mjt2p7aj.js +3 -0
  77. package/dist/cli/main-n6qrdbmy.js +3 -0
  78. package/dist/cli/main-na7wse0x.js +5 -0
  79. package/dist/cli/main-nqveez48.js +4 -0
  80. package/dist/cli/main-ntqef02r.js +10 -0
  81. package/dist/cli/main-nvc3yjay.js +136 -0
  82. package/dist/cli/main-p0cfn6nr.js +16 -0
  83. package/dist/cli/main-qj2djy17.js +19 -0
  84. package/dist/cli/main-qsevpgsv.js +3 -0
  85. package/dist/cli/main-qvarybsp.js +3 -0
  86. package/dist/cli/main-rebtt91r.js +5 -0
  87. package/dist/cli/main-rpg7h8mb.js +3 -0
  88. package/dist/cli/main-rsy72qmw.js +15 -0
  89. package/dist/cli/main-rvetps99.js +18 -0
  90. package/dist/cli/main-s4bb0jav.js +3 -0
  91. package/dist/cli/main-s9v8k74e.js +3 -0
  92. package/dist/cli/main-tjvwmscs.js +3 -0
  93. package/dist/cli/main-tkgarpjj.js +4 -0
  94. package/dist/cli/main-v8y60bb2.js +3 -0
  95. package/dist/cli/main-vhrrq337.js +3 -0
  96. package/dist/cli/main-vp2dfb7s.js +4 -0
  97. package/dist/cli/main-vqbr22sz.js +8 -0
  98. package/dist/cli/main-vxnwe5xx.js +18 -0
  99. package/dist/cli/main-wgph00xf.js +5 -0
  100. package/dist/cli/main-wk2csfnj.js +5 -0
  101. package/dist/cli/main-wm997zjx.js +3 -0
  102. package/dist/cli/main-wpkyraxh.js +3 -0
  103. package/dist/cli/main-wqt32p5x.js +4 -0
  104. package/dist/cli/main-x9ct6y1a.js +3 -0
  105. package/dist/cli/main-xfekqh9m.js +7 -0
  106. package/dist/cli/main-xt9zc3n6.js +7 -0
  107. package/dist/cli/main-xx2z3zh5.js +4 -0
  108. package/dist/cli/main-y5c82rxr.js +3 -0
  109. package/dist/cli/main-yrjt2sqt.js +14 -0
  110. package/dist/cli/main-ys6zj3yr.js +3 -0
  111. package/dist/cli/main-ywbxshqc.js +8 -0
  112. package/dist/cli/main-z13755t8.js +25 -0
  113. package/dist/cli/main-zc7pyrbj.js +4 -0
  114. package/dist/cli/main.js +279 -0
  115. package/dist/cli/market-cmd-bm5xvn9f.js +5 -0
  116. package/dist/cli/mcp-login-bthtfpt7.js +2 -0
  117. package/dist/cli/mcp-market-cmd-mbeshfyd.js +2 -0
  118. package/dist/cli/notify-54v5z9dz.js +2 -0
  119. package/dist/cli/oauth-g5gme95c.js +2 -0
  120. package/dist/cli/output-satndjap.js +16 -0
  121. package/dist/cli/profiles-sfhpbq3m.js +2 -0
  122. package/dist/cli/provider-config-hv3xtdt4.js +2 -0
  123. package/dist/cli/provider-kwzq6g84.js +2 -0
  124. package/dist/cli/registry-fh0hdnyn.js +2 -0
  125. package/dist/cli/registry-y1y8e94r.js +2 -0
  126. package/dist/cli/repl-t4z03mqq.js +11 -0
  127. package/dist/cli/resume-fqt4chg8.js +2 -0
  128. package/dist/cli/run-flags-rysbag9t.js +2 -0
  129. package/dist/cli/runtime-j19fjbsa.js +2 -0
  130. package/dist/cli/sandbox-config-g4qxd7y5.js +2 -0
  131. package/dist/cli/server-r0b6bksk.js +5 -0
  132. package/dist/cli/session-arg-txmn5g4x.js +2 -0
  133. package/dist/cli/session-ed250d9j.js +2 -0
  134. package/dist/cli/sessions-cmd-adw7svfn.js +7 -0
  135. package/dist/cli/settings-y9rzcqx8.js +2 -0
  136. package/dist/cli/setup-jmbr11j0.js +2 -0
  137. package/dist/cli/sextant-smoke-tcth0vea.js +5 -0
  138. package/dist/cli/skills-cmd-zbdy99v6.js +2 -0
  139. package/dist/cli/smoke-1bg937kx.js +8 -0
  140. package/dist/cli/start-chat-p01cdks3.js +12 -0
  141. package/dist/cli/stream-4wmyaypz.js +2 -0
  142. package/dist/cli/task-eg4s093s.js +2 -0
  143. package/dist/cli/tasks-12v9rr9k.js +2 -0
  144. package/dist/cli/thinking-a5ngvqyh.js +2 -0
  145. package/dist/cli/todo-1wxpcecx.js +2 -0
  146. package/dist/cli/tools-2ftsya7w.js +2 -0
  147. package/dist/cli/tools-x1tj4fxm.js +2 -0
  148. package/dist/cli/trust-cmd-hccxehzb.js +2 -0
  149. package/dist/cli/update-check-ygt3vd7m.js +2 -0
  150. package/dist/cli/update-cmd-v23qhr8c.js +2 -0
  151. package/dist/cli/voice-g1gtck92.js +2 -0
  152. package/dist/cli/webfetch-0nnrjgb5.js +2 -0
  153. package/dist/cli/websearch-f0vr2p7d.js +2 -0
  154. package/dist/cli/workspace-9rq1w4ta.js +2 -0
  155. package/dist/lib/index.js +62 -0
  156. package/dist/lib/models-index.json +1 -0
  157. package/dist/lib/plugins.js +6 -0
  158. package/dist/lib/providers.js +17 -0
  159. package/dist/lib/public-api.js +20 -0
  160. package/dist/rovecode.exe +4 -0
  161. package/package.json +38 -11
  162. package/bin/rovecode.ts +0 -21
  163. package/src/acp/server.ts +0 -374
  164. package/src/cli/auth-login.ts +0 -122
  165. package/src/cli/connect.ts +0 -244
  166. package/src/cli/context-cmd.ts +0 -199
  167. package/src/cli/dispatch.ts +0 -82
  168. package/src/cli/doctor.ts +0 -362
  169. package/src/cli/export.ts +0 -276
  170. package/src/cli/help.ts +0 -293
  171. package/src/cli/is-tui-invocation.ts +0 -8
  172. package/src/cli/main.ts +0 -583
  173. package/src/cli/market-cmd.ts +0 -658
  174. package/src/cli/mcp-login.ts +0 -141
  175. package/src/cli/mcp-market-cmd.ts +0 -302
  176. package/src/cli/output.ts +0 -382
  177. package/src/cli/repl.ts +0 -250
  178. package/src/cli/repomap-root.ts +0 -14
  179. package/src/cli/resume.ts +0 -57
  180. package/src/cli/run-flags.ts +0 -43
  181. package/src/cli/run-limits.ts +0 -78
  182. package/src/cli/runtime.ts +0 -931
  183. package/src/cli/session-arg.ts +0 -30
  184. package/src/cli/sessions-cmd.ts +0 -145
  185. package/src/cli/setup.ts +0 -153
  186. package/src/cli/skills-cmd.ts +0 -194
  187. package/src/cli/start-chat.ts +0 -65
  188. package/src/cli/trust-cmd.ts +0 -52
  189. package/src/coding/bash.ts +0 -148
  190. package/src/coding/checkpoints.ts +0 -327
  191. package/src/coding/diff.ts +0 -138
  192. package/src/coding/files.ts +0 -341
  193. package/src/coding/hashline.ts +0 -274
  194. package/src/coding/lsp-gate.ts +0 -254
  195. package/src/coding/lsp-servers.ts +0 -147
  196. package/src/coding/lsp.ts +0 -283
  197. package/src/coding/repomap-cache.ts +0 -99
  198. package/src/coding/repomap-files.ts +0 -192
  199. package/src/coding/repomap.ts +0 -481
  200. package/src/core/agents.ts +0 -255
  201. package/src/core/compaction.ts +0 -259
  202. package/src/core/config.ts +0 -289
  203. package/src/core/context-report.ts +0 -228
  204. package/src/core/context.ts +0 -60
  205. package/src/core/count-remote.ts +0 -107
  206. package/src/core/execpolicy-rules.ts +0 -196
  207. package/src/core/execpolicy.ts +0 -385
  208. package/src/core/executor.ts +0 -454
  209. package/src/core/guardrails.ts +0 -400
  210. package/src/core/hooks.ts +0 -411
  211. package/src/core/images.ts +0 -230
  212. package/src/core/intro.ts +0 -266
  213. package/src/core/loop.ts +0 -567
  214. package/src/core/modes.ts +0 -372
  215. package/src/core/orchestrator.ts +0 -245
  216. package/src/core/proc-group.ts +0 -48
  217. package/src/core/project-trust.ts +0 -98
  218. package/src/core/reflection.ts +0 -165
  219. package/src/core/sandbox-config.ts +0 -186
  220. package/src/core/session-id.ts +0 -24
  221. package/src/core/session-images.ts +0 -73
  222. package/src/core/session-ops.ts +0 -183
  223. package/src/core/session-text.ts +0 -29
  224. package/src/core/session.ts +0 -469
  225. package/src/core/settings.ts +0 -170
  226. package/src/core/tasks.ts +0 -646
  227. package/src/core/token-scale.ts +0 -108
  228. package/src/core/tools.ts +0 -309
  229. package/src/core/trust.ts +0 -104
  230. package/src/core/types.ts +0 -330
  231. package/src/core/update-check.ts +0 -171
  232. package/src/core/usage.ts +0 -204
  233. package/src/core/validate.ts +0 -121
  234. package/src/core/verify-gate.ts +0 -159
  235. package/src/core/verify.ts +0 -236
  236. package/src/core/voice.ts +0 -158
  237. package/src/core/win-job.ts +0 -183
  238. package/src/core/workspace.ts +0 -184
  239. package/src/design/audit.ts +0 -797
  240. package/src/design/direction.ts +0 -190
  241. package/src/design/rules.ts +0 -157
  242. package/src/eval/bench.ts +0 -150
  243. package/src/eval/gauntlet-runner.ts +0 -215
  244. package/src/eval/gauntlet-support.ts +0 -84
  245. package/src/eval/gauntlet-wave3.ts +0 -269
  246. package/src/eval/gauntlet-wave4.ts +0 -217
  247. package/src/eval/gauntlet.ts +0 -253
  248. package/src/index.ts +0 -17
  249. package/src/lanes/agy.ts +0 -95
  250. package/src/lanes/approval.ts +0 -24
  251. package/src/lanes/claude.ts +0 -129
  252. package/src/lanes/codex.ts +0 -127
  253. package/src/lanes/events.ts +0 -130
  254. package/src/lanes/job.ts +0 -142
  255. package/src/lanes/opencode.ts +0 -122
  256. package/src/lanes/process.ts +0 -184
  257. package/src/lanes/progress.ts +0 -183
  258. package/src/lanes/registry.ts +0 -178
  259. package/src/lanes/runner.ts +0 -124
  260. package/src/lanes/types.ts +0 -112
  261. package/src/market/catalogs/mcp-docs.json +0 -111
  262. package/src/market/catalogs/plugins.json +0 -111
  263. package/src/market/catalogs/skills.json +0 -478
  264. package/src/market/clone.ts +0 -72
  265. package/src/market/context-cost.ts +0 -121
  266. package/src/market/digest.ts +0 -106
  267. package/src/market/index.ts +0 -22
  268. package/src/market/install.ts +0 -578
  269. package/src/market/manifest.ts +0 -187
  270. package/src/market/prereq.ts +0 -145
  271. package/src/market/registry.ts +0 -363
  272. package/src/market/resolve.ts +0 -111
  273. package/src/market/types.ts +0 -236
  274. package/src/market/validate.ts +0 -227
  275. package/src/mcp/client.ts +0 -449
  276. package/src/mcp/config.ts +0 -252
  277. package/src/mcp/local-package.ts +0 -211
  278. package/src/mcp/market-catalog.ts +0 -84
  279. package/src/mcp/market-install.ts +0 -289
  280. package/src/mcp/market.ts +0 -362
  281. package/src/mcp/oauth.ts +0 -251
  282. package/src/mcp/prompts-resources.ts +0 -249
  283. package/src/mcp/shared.ts +0 -149
  284. package/src/mcp/status.ts +0 -67
  285. package/src/mcp/tools.ts +0 -275
  286. package/src/mcp/transport.ts +0 -122
  287. package/src/mcp/trust.ts +0 -25
  288. package/src/memory/blocks.ts +0 -278
  289. package/src/memory/recall.ts +0 -355
  290. package/src/memory/scope.ts +0 -182
  291. package/src/memory/store.ts +0 -105
  292. package/src/memory/tools.ts +0 -99
  293. package/src/plugins/cli.ts +0 -119
  294. package/src/plugins/discover.ts +0 -108
  295. package/src/plugins/index.ts +0 -50
  296. package/src/plugins/install.ts +0 -184
  297. package/src/plugins/load.ts +0 -124
  298. package/src/plugins/manifest.ts +0 -92
  299. package/src/plugins/state.ts +0 -83
  300. package/src/providers/auth.ts +0 -408
  301. package/src/providers/cache.ts +0 -223
  302. package/src/providers/catalog-local.ts +0 -160
  303. package/src/providers/catalog.ts +0 -421
  304. package/src/providers/middleware-context.ts +0 -86
  305. package/src/providers/middleware.ts +0 -373
  306. package/src/providers/model-list.ts +0 -23
  307. package/src/providers/oauth/common.ts +0 -105
  308. package/src/providers/oauth/device-code.ts +0 -107
  309. package/src/providers/oauth/github-copilot.ts +0 -146
  310. package/src/providers/oauth/loopback.ts +0 -158
  311. package/src/providers/oauth/openai.ts +0 -163
  312. package/src/providers/oauth/openrouter.ts +0 -89
  313. package/src/providers/oauth/pkce.ts +0 -45
  314. package/src/providers/oauth/registry.ts +0 -39
  315. package/src/providers/oauth/seam.ts +0 -89
  316. package/src/providers/profile-glm53.ts +0 -111
  317. package/src/providers/profile-sonnet5-persona.ts +0 -65
  318. package/src/providers/profile-sonnet5-voice.ts +0 -23
  319. package/src/providers/profiles.ts +0 -156
  320. package/src/providers/provider-config.ts +0 -311
  321. package/src/providers/registry.ts +0 -333
  322. package/src/providers/responses.ts +0 -209
  323. package/src/providers/retry.ts +0 -234
  324. package/src/providers/router.ts +0 -294
  325. package/src/providers/sse.ts +0 -26
  326. package/src/providers/stream-errors.ts +0 -117
  327. package/src/providers/stream.ts +0 -566
  328. package/src/providers/thinking.ts +0 -189
  329. package/src/providers/wire-messages.ts +0 -129
  330. package/src/providers/wire-responses.ts +0 -79
  331. package/src/providers/wire-select.ts +0 -53
  332. package/src/server/http.ts +0 -291
  333. package/src/server/openapi.ts +0 -246
  334. package/src/sextant/card-hits.ts +0 -102
  335. package/src/sextant/card-keys.ts +0 -55
  336. package/src/sextant/context-source.ts +0 -157
  337. package/src/sextant/crew-cards.ts +0 -350
  338. package/src/sextant/draw-agents.ts +0 -273
  339. package/src/sextant/draw-code.ts +0 -388
  340. package/src/sextant/draw-context.ts +0 -222
  341. package/src/sextant/draw-frame.ts +0 -164
  342. package/src/sextant/draw-market.ts +0 -573
  343. package/src/sextant/draw-messages.ts +0 -386
  344. package/src/sextant/draw-pet.ts +0 -230
  345. package/src/sextant/draw-plan.ts +0 -187
  346. package/src/sextant/draw-tabs.ts +0 -85
  347. package/src/sextant/draw-util.ts +0 -65
  348. package/src/sextant/draw-wizard.ts +0 -378
  349. package/src/sextant/engine.ts +0 -230
  350. package/src/sextant/frame-hits.ts +0 -25
  351. package/src/sextant/frame.ts +0 -101
  352. package/src/sextant/git-status.ts +0 -197
  353. package/src/sextant/grid.ts +0 -59
  354. package/src/sextant/input.ts +0 -119
  355. package/src/sextant/keys.ts +0 -521
  356. package/src/sextant/layout.ts +0 -86
  357. package/src/sextant/local-commands.ts +0 -169
  358. package/src/sextant/market-source.ts +0 -287
  359. package/src/sextant/mentions.ts +0 -200
  360. package/src/sextant/message-hits.ts +0 -26
  361. package/src/sextant/model.ts +0 -387
  362. package/src/sextant/overlays.ts +0 -456
  363. package/src/sextant/panel-hits.ts +0 -38
  364. package/src/sextant/pet.ts +0 -399
  365. package/src/sextant/screen.ts +0 -324
  366. package/src/sextant/scroll-hits.ts +0 -66
  367. package/src/sextant/scrollbar.ts +0 -82
  368. package/src/sextant/sextant-bridge.ts +0 -174
  369. package/src/sextant/sextant-cards.ts +0 -142
  370. package/src/sextant/sextant-diff-base.ts +0 -63
  371. package/src/sextant/sextant-files.ts +0 -154
  372. package/src/sextant/sextant-frame-loop.ts +0 -335
  373. package/src/sextant/sextant-renderer.ts +0 -574
  374. package/src/sextant/sextant-repo.ts +0 -140
  375. package/src/sextant/theme.ts +0 -66
  376. package/src/sextant/tool-rows.ts +0 -189
  377. package/src/sextant/types.ts +0 -493
  378. package/src/skills/index.ts +0 -387
  379. package/src/skills/pack.ts +0 -220
  380. package/src/skills/spec.ts +0 -162
  381. package/src/skills/tools.ts +0 -69
  382. package/src/skills/versioned.ts +0 -227
  383. package/src/telemetry/otel-export.ts +0 -122
  384. package/src/telemetry/otel-lanes.ts +0 -89
  385. package/src/telemetry/otel-logs.ts +0 -131
  386. package/src/telemetry/otel-metrics.ts +0 -136
  387. package/src/telemetry/otel.ts +0 -397
  388. package/src/telemetry/otlp.ts +0 -76
  389. package/src/tools/ask-user.ts +0 -156
  390. package/src/tools/bash-bg.ts +0 -94
  391. package/src/tools/bash-jobs.ts +0 -237
  392. package/src/tools/design.ts +0 -151
  393. package/src/tools/evalcell.ts +0 -338
  394. package/src/tools/html-text.ts +0 -139
  395. package/src/tools/provider.ts +0 -149
  396. package/src/tools/task.ts +0 -250
  397. package/src/tools/todo.ts +0 -320
  398. package/src/tools/webfetch.ts +0 -332
  399. package/src/tools/websearch.ts +0 -359
  400. package/src/tui/agents-cmd.ts +0 -41
  401. package/src/tui/app.ts +0 -749
  402. package/src/tui/attach.ts +0 -127
  403. package/src/tui/boot-notes.ts +0 -41
  404. package/src/tui/builtin-prompts.ts +0 -59
  405. package/src/tui/checkpoints-cmd.ts +0 -70
  406. package/src/tui/clipboard-image.ts +0 -81
  407. package/src/tui/clipboard.ts +0 -78
  408. package/src/tui/commands.ts +0 -283
  409. package/src/tui/config-view.ts +0 -53
  410. package/src/tui/context-cmds.ts +0 -282
  411. package/src/tui/cost.ts +0 -108
  412. package/src/tui/crash-guard.ts +0 -173
  413. package/src/tui/focus-terminal.ts +0 -34
  414. package/src/tui/git-cmds.ts +0 -273
  415. package/src/tui/git-plain.ts +0 -58
  416. package/src/tui/info-cmd.ts +0 -150
  417. package/src/tui/input-plain.ts +0 -76
  418. package/src/tui/mcp-cmd.ts +0 -128
  419. package/src/tui/memory-note.ts +0 -77
  420. package/src/tui/modes-cmd.ts +0 -45
  421. package/src/tui/notify-seq.ts +0 -100
  422. package/src/tui/notify.ts +0 -318
  423. package/src/tui/overlays.ts +0 -97
  424. package/src/tui/pi-renderer.ts +0 -428
  425. package/src/tui/providers-cmd.ts +0 -377
  426. package/src/tui/reasoning-view.ts +0 -56
  427. package/src/tui/renderer.ts +0 -128
  428. package/src/tui/replay-marker.ts +0 -29
  429. package/src/tui/session-cmd.ts +0 -148
  430. package/src/tui/session-manage.ts +0 -95
  431. package/src/tui/sextant-attach.ts +0 -102
  432. package/src/tui/sextant-io.ts +0 -202
  433. package/src/tui/sextant-smoke.ts +0 -110
  434. package/src/tui/shell-cmd.ts +0 -158
  435. package/src/tui/smoke.ts +0 -72
  436. package/src/tui/staged-terminal.ts +0 -50
  437. package/src/tui/startup.ts +0 -12
  438. package/src/tui/theme.ts +0 -59
  439. package/src/tui/todo-label.ts +0 -7
  440. package/src/tui/trust-card.ts +0 -107
  441. package/src/tui/tui-commands.ts +0 -87
  442. package/tsconfig.json +0 -30
  443. package/vendor/pi-tui/LICENSE +0 -21
  444. package/vendor/pi-tui/PATCHES.md +0 -12
  445. package/vendor/pi-tui/PROVENANCE.md +0 -12
  446. package/vendor/pi-tui/README.upstream.md +0 -854
  447. package/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  448. package/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  449. package/vendor/pi-tui/src/alt-screen-search.ts +0 -158
  450. package/vendor/pi-tui/src/autocomplete.ts +0 -827
  451. package/vendor/pi-tui/src/components/alt-screen-flash.ts +0 -52
  452. package/vendor/pi-tui/src/components/box.ts +0 -138
  453. package/vendor/pi-tui/src/components/cancellable-loader.ts +0 -41
  454. package/vendor/pi-tui/src/components/editor.ts +0 -2364
  455. package/vendor/pi-tui/src/components/h-stack.ts +0 -45
  456. package/vendor/pi-tui/src/components/image.ts +0 -128
  457. package/vendor/pi-tui/src/components/input.ts +0 -448
  458. package/vendor/pi-tui/src/components/loader.ts +0 -93
  459. package/vendor/pi-tui/src/components/markdown.ts +0 -1016
  460. package/vendor/pi-tui/src/components/scroll-view.ts +0 -217
  461. package/vendor/pi-tui/src/components/select-list.ts +0 -230
  462. package/vendor/pi-tui/src/components/settings-list.ts +0 -277
  463. package/vendor/pi-tui/src/components/spacer.ts +0 -29
  464. package/vendor/pi-tui/src/components/stack.ts +0 -155
  465. package/vendor/pi-tui/src/components/text.ts +0 -108
  466. package/vendor/pi-tui/src/components/truncated-text.ts +0 -66
  467. package/vendor/pi-tui/src/components/v-stack.ts +0 -34
  468. package/vendor/pi-tui/src/editor-component.ts +0 -75
  469. package/vendor/pi-tui/src/fuzzy.ts +0 -138
  470. package/vendor/pi-tui/src/index.ts +0 -149
  471. package/vendor/pi-tui/src/keybindings.ts +0 -321
  472. package/vendor/pi-tui/src/keys.ts +0 -1402
  473. package/vendor/pi-tui/src/kill-ring.ts +0 -47
  474. package/vendor/pi-tui/src/latex.ts +0 -1381
  475. package/vendor/pi-tui/src/layout-node.ts +0 -52
  476. package/vendor/pi-tui/src/layout.ts +0 -411
  477. package/vendor/pi-tui/src/native-modifiers.ts +0 -60
  478. package/vendor/pi-tui/src/native-module-path.ts +0 -32
  479. package/vendor/pi-tui/src/stdin-buffer.ts +0 -445
  480. package/vendor/pi-tui/src/terminal-colors.ts +0 -74
  481. package/vendor/pi-tui/src/terminal-image.ts +0 -701
  482. package/vendor/pi-tui/src/terminal.ts +0 -554
  483. package/vendor/pi-tui/src/tui-alt-screen.ts +0 -1379
  484. package/vendor/pi-tui/src/tui-main-screen.ts +0 -655
  485. package/vendor/pi-tui/src/tui.ts +0 -1264
  486. package/vendor/pi-tui/src/undo-stack.ts +0 -29
  487. package/vendor/pi-tui/src/utils.ts +0 -1327
  488. package/vendor/pi-tui/src/word-navigation.ts +0 -118
  489. package/vendor/pi-tui/test/test-themes.ts +0 -39
  490. package/vendor/pi-tui/test/virtual-terminal.ts +0 -219
  491. /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
- }