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,931 +0,0 @@
1
- /** Shared runtime construction for CLI surfaces (repl, run, tui): stores, tool
2
- * registration, skills/memory indexes, provider resolution, RunConfig defaults.
3
- * Extracted from repl.ts/main.ts so every surface builds the same agent. */
4
-
5
- import type { AgentDefinition, ApprovalFn, Message, ModelRef, PermissionLevel, RunConfig, StreamFn, ThinkingEffort, TokenUsage, Tool } from "../core/types.ts";
6
- import { parseEffort } from "../core/types.ts";
7
- import { SessionStore } from "../core/session.ts";
8
- import { ToolRegistry } from "../core/tools.ts";
9
- import { SkillStore } from "../skills/index.ts";
10
- import { createSkillTools, buildSkillsIndex } from "../skills/tools.ts";
11
- import { BlockStore } from "../memory/blocks.ts";
12
- import { openScopedMemory } from "../memory/scope.ts";
13
- import { memoryEditTool } from "../memory/tools.ts";
14
- import type { ProviderConfig } from "../providers/stream.ts";
15
- import { ProviderRegistry } from "../providers/registry.ts";
16
- import { providerEditTool, providerListTool } from "../tools/provider.ts";
17
- import { designAuditTool, designDirectionTool } from "../tools/design.ts";
18
- import { designPromptSection } from "../design/rules.ts";
19
- import { withToolCallParsing, toolPromptBlock } from "../providers/middleware.ts";
20
- import { ModelCatalog } from "../providers/catalog.ts";
21
- import { GLM_53_AGENT_CONTRACT, profileFor, profilePromptSection } from "../providers/profiles.ts";
22
- import { loadProjectContext, type ProjectContext } from "../core/config.ts";
23
- import { estimateTokens, type ContextChunk } from "../core/context.ts";
24
- import { parseCompactionStrategy } from "../core/compaction.ts";
25
- import { ToolGuard } from "../core/guardrails.ts";
26
- import { HookRunner } from "../core/hooks.ts";
27
- import { settingsTrustNotes } from "../core/settings.ts";
28
- import { createReflectionHooks, reflectionEnabled } from "../core/reflection.ts";
29
- import type { McpManager } from "../mcp/client.ts";
30
- import { activatePlugins, discoverPlugins, loadState as loadPluginState, type DiscoveredPlugin, type LoadedPlugin } from "../plugins/index.ts";
31
- import type { McpServerConfig } from "../mcp/config.ts";
32
- import { trustedPredicate } from "../mcp/trust.ts";
33
- import { positiveInt, positiveUsd, type RunLimits } from "./run-limits.ts";
34
- import { costUsdTiered } from "../core/usage.ts";
35
- import { ratesFor } from "../providers/catalog.ts";
36
- import { contextBudgetFor } from "../core/context-report.ts";
37
- import { tokenScaleFor } from "../core/token-scale.ts";
38
- import { laneApprover } from "../lanes/approval.ts";
39
- import { BashJobManager, installJobManager } from "../tools/bash-jobs.ts";
40
- import { bashJobTools } from "../tools/bash-bg.ts";
41
- import { isAdapterId } from "../lanes/types.ts";
42
- import { createWebSearchTool } from "../tools/websearch.ts";
43
- import type { LaneJobDeps } from "../lanes/job.ts";
44
- import { rovecodeHome } from "../providers/auth.ts";
45
- import { readTool, editTool, writeTool, bashTool } from "../coding/hashline.ts";
46
- import { globTool, grepTool, lsTool } from "../coding/files.ts";
47
- import { withLspGate, lspGateNote, lspAvailabilityNote } from "../coding/lsp.ts";
48
- import { EXTERNAL_ACTION, WorkspaceRoots, resolveRoots } from "../core/workspace.ts"; // --add-dir roots + the file.external boundary
49
- import type { buildRepoMapChunk as BuildRepoMapChunkFn, buildRepoMapChunkAsync as BuildRepoMapChunkAsyncFn } from "../coding/repomap.ts";
50
- import { anchorEntryId, Checkpoints, MUTATING_KINDS } from "../coding/checkpoints.ts";
51
- import { createRouter, parseModelRef, roleTableFromEnv, type Router } from "../providers/router.ts";
52
- import { agentRows, discoverAgents, modelProblemFor, restrictTools, type CustomAgent, type DiscoveredAgents } from "../core/agents.ts";
53
- import { describeGiveUp, describeRetry, retryOptionsFromEnv, withRetry } from "../providers/retry.ts";
54
- import { createEvalCellTool } from "../tools/evalcell.ts";
55
- import { webFetchTool } from "../tools/webfetch.ts";
56
- import { askUserTool, type AskFn } from "../tools/ask-user.ts";
57
- import { execPolicyApprover } from "../core/execpolicy.ts";
58
- import { recallTool } from "../memory/recall.ts";
59
- import { configureExecutor, type SpawnRunner } from "../core/executor.ts";
60
- import { loadSandboxConfig, unavailableRungError, type SandboxConfig } from "../core/sandbox-config.ts";
61
- import { loadTodos, planReminder, todoTools } from "../tools/todo.ts";
62
- import { noteVerifyCost, resolveForGate, runVerify, VERIFY_TIMEOUT_MS, type VerifyResolver } from "../core/verify-gate.ts";
63
- import { SteeringQueue } from "../core/loop.ts";
64
- import { TaskManager } from "../core/tasks.ts";
65
- import { createTaskTool, createTaskStatusTool } from "../tools/task.ts";
66
- import type { ChildContext, ChildRunnerDeps } from "../core/orchestrator.ts";
67
- import { existsSync, mkdirSync } from "node:fs";
68
- import { sep, join } from "node:path";
69
- import { shouldWarmRepoMap } from "./repomap-root.ts";
70
- import { randomUUID } from "node:crypto";
71
- import { noModelHint } from "../core/voice.ts";
72
-
73
- /** upper bound on the per-model max_tokens buildDef derives from the catalog: enough for a long page or
74
- * plan, not the 128K some models advertise — a runaway answer should stop before it costs that much */
75
- export const MAX_OUTPUT_CAP = 32_768;
76
-
77
- export interface RuntimeOptions {
78
- cwd?: string;
79
- sessionId?: string;
80
- /** override the provider-derived stream (tests); null forces "no stream" */
81
- stream?: StreamFn | null;
82
- /** port #27 test seam: process runner behind the executor rung (probe AND
83
- * commands); default Bun.spawn. Tests must never probe a real wsl.exe/docker. */
84
- spawnRunner?: SpawnRunner;
85
- /** test seam for the verify gate: which check a run that wrote files must pass. Default: core/verify.ts
86
- * resolveVerify through verify-gate.ts resolveForGate (settings, then a manifest's unambiguous check script,
87
- * never a guess). Returning null or no commands makes the gate report "not verified" instead of running. */
88
- verifyResolver?: VerifyResolver;
89
- /** `--add-dir <dir>` values (absolute, or relative to process.cwd()) — extra workspace roots beside the cwd
90
- * (core/workspace.ts): visible to every permission decision as `allow file.external <root>\*` (and `allow
91
- * file.write <root>\*` under accept-edits), named in the system prompt, each edit under one LSP-diagnosed against
92
- * it. Resolved before any side effect; a value that is not a directory or is a filesystem root throws
93
- * WorkspaceRootError (the surfaces print it and exit 2). No settings key, no env: a repo must never widen its own roots. */
94
- addDirs?: readonly string[];
95
- /** port #27 test seam: platform the rung probe assumes; default process.platform */
96
- platform?: NodeJS.Platform;
97
- /** port #47: what an external agentic-CLI lane needs — the env its knobs are read from and the spawn
98
- * seam (tests hand in a fake CLI; production spawns the real one). Omitted, lanes stay off, because
99
- * the allow-list is read from the environment and an unset list allows nothing. */
100
- lanes?: LaneJobDeps;
101
- }
102
-
103
- /** port #27: the rung this runtime asked the executor seam for, plus its probe. */
104
- export interface SandboxState extends SandboxConfig {
105
- /** settles once the rung is probed + installed behind getExecutor(); rejects
106
- * with SandboxConfigError (one line). Await it before the first tool call —
107
- * bootRuntime does; until then a non-direct rung is "desired, not yet met"
108
- * and bashTool would get the seam's RungUnavailableError, never lazy direct. */
109
- ready: Promise<void>;
110
- }
111
-
112
- export interface Runtime {
113
- cwd: string;
114
- sessionId: string;
115
- store: SessionStore;
116
- registry: ToolRegistry;
117
- skillStore: SkillStore;
118
- blockStore: BlockStore;
119
- /** the live provider registry (providers/registry.ts): providers.json (user + project) + stored
120
- * credentials + env, hot-reloaded on file change — `rovecode provider add`, `/provider …` in the
121
- * TUI and the agent's provider_edit tool all land here and serve the next model call, no restart */
122
- providers: ProviderRegistry;
123
- /** the default provider as a stream.ts config — LIVE (re-resolved on every read); null when none */
124
- provider: ProviderConfig | null;
125
- /** the registry's dispatching stream (router → retry → middleware → per-provider adapter) or the
126
- * opts.stream override; null only when opts.stream was explicitly null */
127
- stream: StreamFn | null;
128
- /** env ROVECODE_MODEL ?? providers.json `default` ?? the default provider's model ?? "" — LIVE */
129
- defaultModel: string;
130
- /** null when a run can start; else the one-line reason (no provider configured yet). Live: adding
131
- * a provider through the CLI, the TUI or the agent flips it back to null without a restart.
132
- * Always null when the runtime was built with an injected stream (tests, smoke). */
133
- noProviderReason(): string | null;
134
- /** interactive system prompt incl. skills index + memory index (indexes rebuilt per call). `cwdOverride`
135
- * names another directory in the identity sentence — the live gauntlet points the model at its scratch
136
- * workspace while everything else (indexes, profile override lookup) stays on the runtime's cwd */
137
- systemPrompt(cwdOverride?: string): string;
138
- /** the run's definition. `agent` (core/agents.ts): the CHILD definition for that custom agent — its body as the prompt
139
- * (empty → the runtime's), its allow-list, its mode — and it MUST NOT re-point the active model; `child`: a task
140
- * child's def, left without the non-native tool-calling block (orchestrator runChild renders it from the child's own
141
- * registry). Every surface calls buildDef(model) right before its agentLoop. */
142
- buildDef(model: ModelRef, opts?: { cwd?: string; agent?: CustomAgent; child?: boolean }): AgentDefinition;
143
- /** Build the repo map on the next turn of the event loop instead of inside the first buildDef. The TUI
144
- * calls this right after its first paint: the build took 720–850 ms in this repository (2026-09-06),
145
- * all of it on the first request's latency when it ran at submit time. A buildDef that arrives while
146
- * the timer is still pending gets a definition WITHOUT the map — the map is not in that prompt, the
147
- * request never waits — and every later run has it. Headless callers do not call this and keep the
148
- * synchronous build: a one-shot `run` wants the map in its only prompt. Idempotent. */
149
- warmRepoMap(): void;
150
- /** Cancel interactive warmup and wait for its file/git handles before the surface closes. */
151
- stopRepoMapWarmup(): Promise<void>;
152
- /** `true`/`false` still mean auto/ask — every existing caller keeps working */
153
- buildCfg(permission: PermissionLevel | boolean, approval?: ApprovalFn): RunConfig;
154
- /** swap the session-scoped memory store — rebinds the memory tool AND the prompt (port #2 fix) */
155
- setBlockStore(b: BlockStore): void;
156
- /** tool-loop guardrails (port #4), one per runtime, thread into LoopDeps.guard */
157
- guard: ToolGuard;
158
- /** port #32: the open todo list, re-sent once per turn — thread into LoopDeps.planReminder */
159
- planReminder: (history: readonly Message[]) => string | null;
160
- /** How hard the model thinks before answering. Stamped onto every ModelRef buildDef hands out, so
161
- * ONE setting reaches every surface (TUI, one-shot, serve, acp) without each threading a flag.
162
- * A ref that already names an effort keeps it. */
163
- effort: ThinkingEffort;
164
- setEffort(e: ThinkingEffort): void;
165
- /** the two ceilings on a run (cli/run-limits.ts): buildCfg reads them ahead of ROVECODE_MAX_TURNS /
166
- * ROVECODE_MAX_SECONDS and the 60-turn default — `rovecode run` sets its flags and headless default here */
167
- setRunLimits(limits: RunLimits): void;
168
- /** MCP server manager (port #3); null when no servers were configured at boot AND none has been
169
- * installed since — `reloadMcp` creates it on demand. */
170
- mcp: McpManager | null;
171
- /** Re-read the MCP files and bring the session in line with them, connecting anything new. What
172
- * `market install mcp:<id>` calls so a fresh server is usable in the session that installed it
173
- * rather than after a restart. Never throws: a server that will not connect comes back in `failed`
174
- * and simply stays unavailable, exactly as at boot. */
175
- reloadMcp(): Promise<{ added: string[]; removed: string[]; failed: { name: string; error: string }[]; skipped: string[] }>;
176
- /** port #8 config snapshot (AGENTS.md/CLAUDE.md/… harvested cwd-upward ONCE
177
- * at construction, for prompt-cache stability) incl. dropped/truncated
178
- * source stubs for /status. Mid-session config edits are intentionally not
179
- * picked up — restart rovecode (a new runtime) to refresh. */
180
- projectContext: ProjectContext;
181
- /** port #14: role→model router with fallback chains (env ROVECODE_MODEL_<ROLE>). */
182
- router: Router;
183
- /** port #14: fallback-advance notes accumulated since the last drain. */
184
- drainRouterNotes(): string[];
185
- /** live delivery of the same notes (retry "retrying in 4 s (2/4)", give-up, chain advance): buffered ones replay
186
- * first, then each new note arrives as it happens — the TUI shows a notice while the backoff waits, cmdRun prints a
187
- * stderr line. With a listener registered, drainRouterNotes has nothing left to drain. */
188
- onRouterNote(fn: (note: string) => void): void;
189
- /** port #11: shadow-git checkpoints for a session (lazy; null when git is absent
190
- * or ROVECODE_NO_CHECKPOINTS=1). Snapshots land automatically after mutating tools. */
191
- checkpointsFor(sessionId: string): Promise<Checkpoints | null>;
192
- /** port #11: point checkpoint entryId capture at the ACTIVE session store after
193
- * a TUI session switch (pairs with setBlockStore). */
194
- setSessionStore(s: SessionStore): void;
195
- /** port #27: executor rung selected by .rovecode/sandbox.json / ROVECODE_SANDBOX (+ probe) */
196
- sandbox: SandboxState;
197
- /** the workspace roots — canonical cwd + the `--add-dir` dirs (deduplicated, nested ones dropped with a note in
198
- * `roots.notes`, surfaced as plugin warnings); their ladder rules are already inside buildCfg's permissionRules */
199
- roots: WorkspaceRoots;
200
- /** port #33: bind (or unbind with undefined) the interactive asker behind the ask_user
201
- * tool — the TUI hands in its question overlay; headless surfaces (run/serve/acp) never
202
- * call this, so the tool fails closed for them. setBlockStore idiom: registered once,
203
- * dependency rebound late. */
204
- setAskUser(fn: AskFn | undefined): void;
205
- /** port #26: the ONE steering queue for this runtime's runs — hand it to agentLoop
206
- * (in place of a fresh SteeringQueue) so background-task completion notes reach the
207
- * parent's next turn. Surfaces with their own queue: rt.tasks.attach(queue). */
208
- steering: SteeringQueue;
209
- /** port #26: background subagents (bounded FIFO jobs over orchestrator runChild) */
210
- tasks: TaskManager;
211
- /** port #55: this runtime's background shell jobs — a surface calls dispose() at teardown so no
212
- * backgrounded command outlives the session that started it */
213
- bashJobs: BashJobManager;
214
- /** the custom subagent definitions loaded at boot (core/agents.ts: `.rovecode/agents/*.md` + `<home>/agents/*.md`);
215
- * `warnings` were also sent through the plugin-warning channel as `agents: …` lines */
216
- agents: DiscoveredAgents;
217
- /** port #29: typed hook set — `.rovecode/hooks.{ts,js}` (+ `~/.rovecode`, ROVECODE_HOME) loaded at construction
218
- * (background import; every run() waits for it, so no surface can race the load), session_open
219
- * fired once loaded. Thread into LoopDeps.hooks; attach more sets programmatically via hooks.add()
220
- * (port #39 OTel); surfaces call hooks.close() at teardown → session_close once. Load + runtime
221
- * notes (import failure, wrong version, timeout, throw) land in hooks.warnings / onWarning(). */
222
- hooks: HookRunner;
223
- /** plugins (src/plugins, docs/plugins.md): discovered synchronously at construction — manifests and
224
- * statuses only, no code run — so an ACTIVE plugin's skills, commands and MCP servers wire in with
225
- * their file-based twins; the entry modules (tools + hooks) import in the background and `ready`
226
- * joins them (bootRuntime awaits it, so no surface's first prompt can miss a plugin tool). A
227
- * PROJECT plugin stays `untrusted` — nothing of it loads — until `rovecode plugin trust`. */
228
- plugins: RuntimePlugins;
229
- }
230
-
231
- export interface RuntimePlugins {
232
- /** every plugin found at construction, with its status (`rovecode plugin list` shows the same) */
233
- found: readonly DiscoveredPlugin[];
234
- /** settles when the active entry modules are imported and their tools/hooks attached */
235
- ready: Promise<void>;
236
- /** the plugins as activated — empty until `ready` */
237
- readonly loaded: readonly LoadedPlugin[];
238
- /** discovery + activation notes; a listener gets the buffered ones first (hooks.onWarning idiom) */
239
- readonly warnings: readonly string[];
240
- onWarning(fn: (note: string) => void): void;
241
- }
242
-
243
- // lazy module helpers — loaded on first use so boot pays nothing for features that are not configured
244
- type OtelMod = typeof import("../telemetry/otel.ts");
245
- let _otelMod: OtelMod | null = null;
246
- function lazyOtel(): OtelMod | null {
247
- if (!process.env.ROVECODE_OTEL_ENDPOINT) return null;
248
- if (_otelMod === null) {
249
- // eslint-disable-next-line @typescript-eslint/no-require-imports
250
- _otelMod = require("../telemetry/otel.ts") as OtelMod;
251
- }
252
- return _otelMod;
253
- }
254
-
255
- type McpClientMod = typeof import("../mcp/client.ts");
256
- type McpToolsMod = typeof import("../mcp/tools.ts");
257
- let _mcpMod: { client: McpClientMod; tools: McpToolsMod } | null = null;
258
- function lazyMcp(): { client: McpClientMod; tools: McpToolsMod } {
259
- if (_mcpMod === null) {
260
- // eslint-disable-next-line @typescript-eslint/no-require-imports
261
- _mcpMod = {
262
- client: require("../mcp/client.ts") as McpClientMod,
263
- tools: require("../mcp/tools.ts") as McpToolsMod,
264
- };
265
- }
266
- return _mcpMod;
267
- }
268
-
269
- export function createRuntime(opts: RuntimeOptions = {}): Runtime {
270
- const cwd = opts.cwd ?? process.cwd();
271
- // port #27: sandbox rung selection comes FIRST — a config error throws before any
272
- // side effect (no sessions dir, no MCP children). The probe (wsl/docker trial
273
- // spawn, 500ms cap) runs concurrently with the rest of construction; its verdict
274
- // is `sandbox.ready`. The seam records the DESIRED rung synchronously (#10 G7),
275
- // so an unmet wsl/docker desire is loud at bashTool, never a silent direct.
276
- const sandboxCfg = loadSandboxConfig(cwd, process.env, { home: rovecodeHome() }); // an UNTRUSTED project sandbox.json contributes nothing (core/trust.ts); its note is pushed once pluginWarn exists
277
- const ready = configureExecutor(sandboxCfg.rung, {
278
- runner: opts.spawnRunner, dockerImage: sandboxCfg.dockerImage, platform: opts.platform,
279
- }).then(() => undefined, (e: unknown) => { throw unavailableRungError(sandboxCfg, e); });
280
- void ready.catch(() => {}); // verdict is read via bootRuntime / await — never an unhandled rejection
281
- const sandbox: SandboxState = { ...sandboxCfg, ready };
282
- // the `--add-dir` roots (core/workspace.ts): canonical, deduplicated (nested / in-cwd values dropped with a note); a
283
- // value that is not a directory or is a filesystem root throws WorkspaceRootError HERE — before the sessions mkdir
284
- // and every other side effect, the same startup-error class as a bad sandbox config (exit 2 on every surface)
285
- const roots = new WorkspaceRoots(cwd, resolveRoots(cwd, opts.addDirs ?? []));
286
- const sessionsDir = join(cwd, ".rovecode", "sessions");
287
- mkdirSync(sessionsDir, { recursive: true });
288
- const sessionId = opts.sessionId ?? randomUUID();
289
- const store = new SessionStore(sessionsDir, sessionId);
290
-
291
- // port #11: shadow-git checkpoints — one repo per session under .rovecode/checkpoints/,
292
- // snapshot after every SUCCESSFUL mutating tool call (kinds write/execute). Lazy
293
- // per-session init; git absent or ROVECODE_NO_CHECKPOINTS=1 → silently off.
294
- const cpBySession = new Map<string, Promise<Checkpoints | null>>();
295
- const checkpointsFor = (sid: string): Promise<Checkpoints | null> => {
296
- if (process.env.ROVECODE_NO_CHECKPOINTS === "1") return Promise.resolve(null);
297
- let p = cpBySession.get(sid);
298
- if (!p) { p = Checkpoints.init({ workspace: cwd, sessionId: sid }).then((c) => c, () => null); cpBySession.set(sid, p); }
299
- return p;
300
- };
301
- let activeStore = store; // TUI session switches re-point it via setSessionStore
302
- const withCheckpoint = (t: Tool): Tool => !MUTATING_KINDS.has(t.kind) ? t : {
303
- ...t,
304
- execute: async (a, c) => {
305
- const out = await t.execute(a, c);
306
- if (out.ok) {
307
- const cp = await checkpointsFor(c.sessionId);
308
- // conversation-restore anchor: last USER message (HIGH-2 — the tail entry is the
309
- // assistant message that ISSUED this very tool call; branching there strands its
310
- // tool_calls with no replies -> provider 400), when the active store IS this session
311
- const entryId = activeStore.id === c.sessionId ? anchorEntryId(activeStore.messages()) : undefined;
312
- await cp?.snapshot(t.schema.name, entryId).catch(() => {});
313
- }
314
- return out;
315
- },
316
- };
317
-
318
- const registry = new ToolRegistry();
319
- // plugins (src/plugins, docs/plugins.md): discovery runs no code — manifests, statuses, digests — so
320
- // it can happen here, synchronously, and the ACTIVE plugins' declarative halves (skills dirs,
321
- // command dirs, MCP servers) join their file-based twins below as if they had been in .rovecode/.
322
- // The entry modules import after construction (see the activation block after hooks.open).
323
- const pluginHome = rovecodeHome();
324
- const pluginState = loadPluginState(pluginHome); // one trust store for project plugins AND project MCP files
325
- const pluginsFound = discoverPlugins(cwd, { home: pluginHome, state: pluginState });
326
- // ONE startup-note channel, not a plugin channel. It began as the plugins' own and kept the name
327
- // while lsp, workspace roots, agents and mcp all started pushing into it — and every consumer was
328
- // prefixing "plugins: " on the way out, so an agent file's problem reached stderr as
329
- // "plugins: agents: explore.md: skipped …". Three sessions reported it in one night and none owned
330
- // it. Now each note carries its OWN subsystem word at the push site and consumers print it as-is.
331
- const pluginWarnings: string[] = pluginsFound.warnings.map((w) => `plugins: ${w}`);
332
- const pluginListeners: ((note: string) => void)[] = [];
333
- const pluginWarn = (note: string): void => { pluginWarnings.push(note); for (const l of pluginListeners) l(note); };
334
- const activePlugins = pluginsFound.plugins.filter((p) => p.status === "active"); // untrusted/disabled/broken contribute NOTHING
335
- // port #13: successful edits/writes get LSP diagnostics appended within a ≤2s
336
- // settle window (typescript-language-server on PATH; absent → silently off).
337
- const lspNote = (p: string): Promise<string> => lspGateNote(p, roots.rootOf(p) ?? cwd); // an edit under an added root is diagnosed against THAT root (its own tsconfig)
338
- // "absent → silently off" is right for the tool result and wrong for the person: say once, at boot, that the
339
- // diagnostics loop is not running here (TypeScript projects only — coding/lsp.ts lspAvailabilityNote)
340
- const lspGap = lspAvailabilityNote(cwd);
341
- if (lspGap !== null) pluginWarn(lspGap);
342
- // the roots, said once on the same channel (TUI warn note / headless stderr): dropped --add-dir values, and the limit
343
- // that comes with roots — checkpoints snapshot the cwd only (core/workspace.ts checkpointNote; also the doctor row)
344
- for (const n of roots.notes) pluginWarn(n);
345
- if (roots.dirs.length > 0) pluginWarn(roots.checkpointNote());
346
- registry.register(readTool, withCheckpoint(withLspGate(editTool, lspNote)), withCheckpoint(withLspGate(writeTool, lspNote)), withCheckpoint(bashTool));
347
- registry.register(globTool, grepTool, lsTool); // port #22: bounded, gitignore-aware search/list (kind read → file.read auto-allow; non-mutating, no checkpoint)
348
- registry.register(webFetchTool, createWebSearchTool({ apiKey: process.env.EXA_API_KEY })); // port #31 web_fetch + #56 web_search: kind network → net.fetch, PROMPT by default (rule below); SSRF-guarded, bounded; no checkpoint. The key is read HERE and nowhere else — the module never touches process.env, and every URL it echoes back has its query string dropped
349
- // a plugin's skills dir joins the store as one more root: a user plugin's as global, a project
350
- // plugin's as project (the same precedence its own files would have had)
351
- const skillStore = new SkillStore(cwd, { extraDirs: activePlugins.flatMap((p) => (p.skillsDir ? [{ dir: p.skillsDir, scope: p.scope === "project" ? "project" as const : "global" as const }] : [])) });
352
- skillStore.scan();
353
- registry.register(...createSkillTools(skillStore));
354
- // memory is PROJECT-scoped, not per-session (memory/scope.ts): MEMORY under <cwd>/.rovecode/memory, USER under the
355
- // user home. Until 2026-09-07 both lived in <sessions>/<id>/memory, so every fact died with the session id and the
356
- // USER block — preferences meant to follow the person across projects — was written inside the repository. The
357
- // one-time copy-forward of a legacy store happens here, before the boot snapshot, so a resumed session keeps its text.
358
- const scopedMemory = openScopedMemory({ cwd, sessionsDir, sessionId, home: pluginHome });
359
- let blocks = scopedMemory.blocks;
360
- if (scopedMemory.note !== null) for (const line of scopedMemory.note.split("\n")) pluginWarn(line);
361
- registry.register(memoryEditTool(blocks));
362
- // port #18: persistent eval cell — registered ONLY when ROVECODE_EVAL_CELL=1
363
- const evalCell = createEvalCellTool();
364
- if (evalCell) registry.register(withCheckpoint(evalCell));
365
- // port #17: cross-session recall (kind read → file.read gate; pure transcript search)
366
- registry.register(recallTool(sessionsDir));
367
- registry.register(...todoTools(sessionsDir)); // port #32: per-session todo list at <session>/todos.json (todo_write kind memory → memory.write allow; todo_read kind read)
368
-
369
- /** LoopDeps.planReminder: while a plan is open, re-send it as the LAST thing in the request. Read
370
- * from disk every turn, so the agent's own todo_write (and a /todos edit, and a second surface on
371
- * the same session) are all reflected. Skipped when the model just wrote the list — it is already
372
- * looking at that tool result, and a copy right under it teaches nothing. */
373
- const planReminderFor = (history: readonly Message[]): string | null => {
374
- const last = history.at(-1);
375
- if (last?.parts.some((p) => p.kind === "tool_result" && p.output.startsWith("todos:"))) return null;
376
- const dir = join(sessionsDir, store.id);
377
- try { return planReminder(loadTodos(dir).items); } catch { return null; } // a missing/corrupt list never blocks a turn
378
- };
379
- // providers: ONE live registry per runtime (providers/registry.ts) — providers.json (user + project),
380
- // stored credentials and env, re-read when a source file changes. provider_list is kind read (always
381
- // allowed); provider_edit is kind custom → tool.provider_edit, PROMPT under the gated rules below
382
- const providers = new ProviderRegistry(cwd);
383
- // custom subagent definitions (core/agents.ts): scanned ONCE at boot; every file that cannot be honoured is refused
384
- // here with a line naming it (never at run time inside a child) — a reserved name, a bad mode, a model on another
385
- // provider than the one this session streams over, a YAML list or an empty `tools:`. The lines ride the plugin-
386
- // warning channel like the LSP and roots notes (TUI warn note / headless stderr).
387
- const agents = discoverAgents(cwd, { validateModel: modelProblemFor(providers.defaultConfig()?.id ?? null) });
388
- for (const w of agents.warnings) pluginWarn(`agents: ${w}`);
389
- const agentRowsList = agentRows(agents);
390
- registry.register(providerListTool(providers), providerEditTool(providers));
391
- // design protocol (design/rules.ts): design_audit is kind read (free, never prompts -- checking your
392
- // own work must cost nothing); design_direction is kind custom -> tool.design_direction, PROMPT under
393
- // the gated rules, because it records the project's design identity and is asked once per project.
394
- registry.register(designAuditTool(), designDirectionTool());
395
- // port #33: ask_user on EVERY surface (kind read → auto-runs under gated/plan rules); only an
396
- // interactive surface binds an asker via setAskUser — unbound, the tool fails closed
397
- let askUser: AskFn | undefined;
398
- registry.register(askUserTool(() => askUser));
399
- const guard = new ToolGuard(); // port #4: loop signatures + duplicate-result stubs
400
- // port #29: hooks v2 — the runner exists synchronously (createRuntime stays sync); open() imports
401
- // .rovecode/hooks.{ts,js} (+ user scope) in the background and fires session_open; run() awaits it
402
- const hooks = new HookRunner({ cwd, sessionId });
403
- void hooks.open(cwd);
404
-
405
- // plugin entry modules: imported in the background like the hook files, joined by bootRuntime through
406
- // plugins.ready. Tools land on THIS registry (the same permission path as every built-in: kind → action)
407
- // and are refused loudly when the name is taken — a plugin cannot replace `bash`. Hooks join the runner
408
- // after the hook files (they miss session_open; pre_run is theirs). Activation failures are notes.
409
- let loadedPlugins: LoadedPlugin[] = [];
410
- const pluginsReady = activatePlugins(pluginsFound.plugins, { cwd, home: pluginHome }).then((a) => {
411
- for (const w of a.warnings) pluginWarn(`plugins: ${w}`);
412
- const taken = new Set(registry.list().map((t) => t.schema.name));
413
- for (const p of a.plugins) {
414
- if (p.status !== "active") continue;
415
- for (const t of p.tools) {
416
- if (taken.has(t.schema.name)) { pluginWarn(`plugins: plugin ${p.name}: tool "${t.schema.name}" is already registered — refused (a plugin cannot replace a built-in or another plugin's tool)`); continue; }
417
- taken.add(t.schema.name);
418
- registry.register(t);
419
- }
420
- if (p.hooks) hooks.add(p.hooks, `plugin:${p.name}`);
421
- }
422
- loadedPlugins = a.plugins;
423
- }, (e: unknown) => { pluginWarn(`plugins: activation failed — ${e instanceof Error ? e.message : String(e)}`); });
424
- const plugins: RuntimePlugins = {
425
- found: pluginsFound.plugins,
426
- ready: pluginsReady,
427
- get loaded() { return loadedPlugins; },
428
- warnings: pluginWarnings,
429
- onWarning(fn) { for (const w of pluginWarnings) fn(w); pluginListeners.push(fn); },
430
- };
431
-
432
- // port #3: MCP servers from .rovecode/mcp.json + harvested .mcp.json; two lazy tools only.
433
- // connect() is fire-and-forget; tool executes await first-connect before dispatching.
434
- // plugin MCP servers first, then the project's own files — .rovecode/mcp.json keeps the last word on a name
435
- const mcpByName = new Map<string, McpServerConfig>();
436
- for (const p of activePlugins) for (const c of p.mcp) {
437
- if (mcpByName.has(c.name)) { pluginWarn(`plugins: plugin ${p.name}: MCP server "${c.name}" is also declared by another plugin — first kept`); continue; }
438
- mcpByName.set(c.name, c);
439
- }
440
- // the user file (~/.rovecode/mcp.json — where `rovecode mcp add` writes) is the lowest of the three layers.
441
- // Project files (.rovecode/mcp.json, .mcp.json) pass the same trust gate as project plugins (mcp/trust.ts):
442
- // unapproved on this machine → nothing of theirs loads, one `mcp: …` note names the file and the command.
443
- const mcpWarnings: string[] = [];
444
- const hasMcpFiles = existsSync(join(pluginHome, "mcp.json"))
445
- || existsSync(join(cwd, ".rovecode", "mcp.json"))
446
- || existsSync(join(cwd, ".mcp.json"));
447
- if (hasMcpFiles) {
448
- for (const c of lazyMcp().client.loadMcpConfig(cwd, mcpWarnings, { home: pluginHome, trusted: trustedPredicate(pluginState) })) { if (mcpByName.has(c.name)) pluginWarn(`mcp: mcp.json server "${c.name}" overrides a plugin's entry of the same name`); mcpByName.set(c.name, c); }
449
- }
450
- for (const w of mcpWarnings) pluginWarn(`mcp: ${w}`);
451
- // the trust gate's other two boot lines (core/trust.ts): a project settings.json whose command-bearing keys were
452
- // dropped, and a project sandbox.json that was ignored — hooks.ts says its own through hooks.warnings
453
- for (const n of settingsTrustNotes(cwd, pluginHome)) pluginWarn(`trust: ${n}`);
454
- if (sandboxCfg.note !== undefined) pluginWarn(`trust: ${sandboxCfg.note}`);
455
- // Reading the three files is its own function because it happens twice: once here, and again whenever
456
- // something installs a server and wants it usable without a restart (reloadMcp below).
457
- // `warn` is an OUT parameter rather than a swallowed local: a server that is skipped — an unset ${NAME},
458
- // an unfilled <placeholder>, an untrusted project file — is the one thing the human most needs to hear
459
- // after installing something, and dropping the reason here is what made a failed install look like a
460
- // successful one that simply did nothing.
461
- const readMcpConfigs = (warn: string[] = []): McpServerConfig[] => {
462
- const byName = new Map<string, McpServerConfig>();
463
- for (const p of activePlugins) for (const c of p.mcp) if (!byName.has(c.name)) byName.set(c.name, c);
464
- const files = existsSync(join(pluginHome, "mcp.json"))
465
- || existsSync(join(cwd, ".rovecode", "mcp.json"))
466
- || existsSync(join(cwd, ".mcp.json"));
467
- // the trust gate is re-read too: a project file approved since boot starts counting from now on,
468
- // and one whose contents changed is untrusted again, exactly as it would be on a fresh start
469
- if (files) for (const c of lazyMcp().client.loadMcpConfig(cwd, warn, { home: pluginHome, trusted: trustedPredicate(loadPluginState(pluginHome)) })) byName.set(c.name, c);
470
- return [...byName.values()];
471
- };
472
-
473
- const mcpConfigs = [...mcpByName.values()];
474
- let mcp: McpManager | null = null;
475
- let mcpReady: Promise<void> = Promise.resolve();
476
- /** register mcp_list/mcp_call once; they dispatch by server name, so a new server needs no new tool */
477
- const registerMcpTools = (manager: McpManager): void => {
478
- for (const t of lazyMcp().tools.createMcpTools(manager)) {
479
- registry.register({ ...t, execute: async (a, c) => { await mcpReady; return t.execute(a, c); } });
480
- }
481
- };
482
- if (mcpConfigs.length > 0) {
483
- const mcpMod = lazyMcp();
484
- const manager = new mcpMod.client.McpManager(mcpConfigs);
485
- mcp = manager;
486
- // The connect starts on the NEXT turn of the event loop, not here: runTui is synchronous from
487
- // createRuntime through renderer.start(), which paints the first frame, and connect()'s first
488
- // step is loading the MCP SDK (~200 ms of module evaluation). Kicked off inline that load ran on
489
- // the first microtask — still ahead of the first paint. A zero timer puts it behind it. Nothing
490
- // at boot awaits mcpReady; mcp_list/mcp_call do (registerMcpTools), so a tool call may wait —
491
- // the terminal must not. Measured: two npx servers, createRuntime 254 ms → 30 ms.
492
- // The result is not discarded: a server the loader accepted but that never answers (a command that does not
493
- // exist, a package npx cannot resolve) is counted on the startup card as configured, and until it is named
494
- // here the only sign of it was a tool call that found nothing. Same channel as the loader's own "skipped"
495
- // lines (pluginWarn → the TUI's warn notes, stderr headless), same shape as reloadMcp's `failed`.
496
- mcpReady = new Promise<void>((resolve) => {
497
- setTimeout(() => {
498
- manager.connect().then(
499
- (r) => { for (const f of r.failed) pluginWarn(`mcp: server "${f.name}" did not connect — ${f.error}`); resolve(); },
500
- () => resolve(),
501
- );
502
- }, 0);
503
- });
504
- registerMcpTools(manager);
505
- }
506
-
507
- /** Pick up mcp.json changes in a live session — what `market install mcp:<id>` calls so the answer is
508
- * "ready" instead of "restart rovecode".
509
- *
510
- * Two cases, and the second is the one that matters most: when a session started with NO servers there
511
- * is no manager and `mcp_list`/`mcp_call` were never registered, so installing your first server used
512
- * to leave the model with no way to reach it at all. Here the manager is created and the two tools are
513
- * registered at that moment. Servers already connected are left alone (see McpManager.sync). */
514
- const reloadMcp = async (): Promise<{ added: string[]; removed: string[]; failed: { name: string; error: string }[]; skipped: string[] }> => {
515
- const skipped: string[] = [];
516
- const configs = readMcpConfigs(skipped);
517
- if (mcp === null) {
518
- if (configs.length === 0) return { added: [], removed: [], failed: [], skipped };
519
- const manager = new (lazyMcp().client.McpManager)(configs);
520
- mcp = manager;
521
- registerMcpTools(manager);
522
- const r = await manager.connect();
523
- mcpReady = Promise.resolve();
524
- return { added: configs.map((c) => c.name), removed: [], failed: r.failed, skipped };
525
- }
526
- const { added, removed } = await mcp.sync(configs);
527
- const r = added.length > 0 ? await mcp.connect() : { failed: [] as { name: string; error: string }[] };
528
- return { added, removed, failed: r.failed, skipped };
529
- };
530
-
531
- // boot-time view of the default provider — only the router's role table is pinned to it; every
532
- // other reader goes through the LIVE getters on the returned Runtime (provider / defaultModel)
533
- const bootDefault = providers.defaultRef();
534
- // port #14: role router + fallback chains (env ROVECODE_MODEL_DEFAULT/SMOL/PLAN/COMMIT/TASK,
535
- // comma-separated provider/model chains). The registry's dispatcher routes every candidate to
536
- // ITS OWN provider's endpoint, so a cross-provider chain really fails over.
537
- const routerNotes: string[] = [];
538
- const routerListeners: ((note: string) => void)[] = [];
539
- /** a note goes to every live listener at once (the TUI's notice, cmdRun's stderr line); with no listener it
540
- * waits in the buffer for drainRouterNotes — so a retry is visible WHILE it waits, not after the run */
541
- const pushRouterNote = (note: string): void => { if (routerListeners.length === 0) routerNotes.push(note); else for (const fn of routerListeners) fn(note); };
542
- const fallbackRef: ModelRef = { provider: bootDefault?.provider ?? "mock", model: bootDefault?.model || "default" };
543
- const router = createRouter({
544
- roles: roleTableFromEnv(fallbackRef),
545
- // MED-3: an explicitly configured default chain is the fallback pool even for models
546
- // outside it (requested model prepended as primary); the synthesized single-model
547
- // default (env unset) must NOT capture loose models — hence the env gate.
548
- looseFallback: (process.env.ROVECODE_MODEL_DEFAULT ?? "").trim().length > 0,
549
- onNote: (n) => pushRouterNote(
550
- `router: ${n.chain} ${n.from.provider}/${n.from.model} → ${n.to ? `${n.to.provider}/${n.to.model}` : "chain exhausted"} (${n.reason})`),
551
- });
552
- // port #7: provider streams get the non-native tool-call parser (strict-gated passthrough
553
- // for native turns); injected test streams stay untouched. Kill switch: ROVECODE_NO_TOOL_MIDDLEWARE=1
554
- // port #14: the router wraps OUTERMOST (chain advance re-drives the whole turn).
555
- // port #23: same-model retry sits INSIDE the router — backoff retries exhaust on candidate N
556
- // before the chain advances (ROVECODE_RETRY_MAX / ROVECODE_RETRY_BASE_MS; providers/retry.ts header).
557
- // The raw stream is the registry's DISPATCHER: it resolves model.provider on every call against the
558
- // live snapshot, so the wrapped stream below never needs rebuilding when providers change. With
559
- // nothing configured it yields a `config:` error turn (non-retryable) — surfaces consult
560
- // noProviderReason() first and cmdRun keeps its mock fallback.
561
- const rawStream = providers.stream();
562
- const middlewared = process.env.ROVECODE_NO_TOOL_MIDDLEWARE !== "1" ? withToolCallParsing(rawStream) : rawStream;
563
- // retries and give-ups surface as notes in the human's words (providers/retry.ts describeRetry/describeGiveUp):
564
- // "anthropic: overloaded — retrying in 4 s (2/4)" — live to onRouterNote listeners, else buffered for drainRouterNotes
565
- const stream = opts.stream !== undefined ? opts.stream : router.wrap(withRetry(middlewared, { ...retryOptionsFromEnv(), onRetry: (n) => pushRouterNote(describeRetry(n)), onGiveUp: (n) => pushRouterNote(describeGiveUp(n)) }));
566
- const catalog = new ModelCatalog(); // offline models.dev snapshot (port #6)
567
- // port #39: OTel span export rides the hook seam — attached ONLY when ROVECODE_OTEL_ENDPOINT is set (off:
568
- // nothing constructed, no on_event tap → zero cost); export failures surface through hooks.warnings
569
- const otelMod = lazyOtel();
570
- // held so the TaskManager below can be handed to it: lane spans need the manager, which does not
571
- // exist yet here, and the hook set is the only thing that knows how to open and close them
572
- let otelHooks: ReturnType<OtelMod["createOtelHooks"]> | null = null;
573
- if (otelMod) {
574
- const otel = otelMod.otelOptionsFromEnv();
575
- if (otel) {
576
- // #47 × #39: isAdapterId is what makes a task a LANE on the dashboard. The default is "nothing is
577
- // a lane" on purpose — with a looser predicate every ordinary subagent would be exported as an
578
- // external CLI, which is a lie a dashboard cannot be talked out of once it has drawn it.
579
- otelHooks = otelMod.createOtelHooks({ ...otel, pricing: catalog, messages: () => activeStore.messages(), isLane: isAdapterId });
580
- hooks.add(otelHooks, "otel");
581
- }
582
- }
583
-
584
- // port #8: harvest AGENTS.md / CLAUDE.md / .cursor / copilot instructions
585
- // cwd-UPWARD (OMP ancestor-walk pattern) ONCE per runtime — a snapshot, like
586
- // BlockStore, so the system prompt stays byte-stable for prompt caching
587
- // (MED-4). It reaches the model as an ADR-007 "config" chunk (priority 70)
588
- // via buildDef → assembleContext, never a second prompt-assembly path.
589
- const projectContext = loadProjectContext(cwd);
590
- const configText = `# Project context${projectContext.text}`;
591
- const configChunk: ContextChunk | null = projectContext.text
592
- ? { name: "config", text: configText, priority: 70, tokens: estimateTokens(configText) }
593
- : null;
594
-
595
- // port #12: repo-map fills the reserved ADR-007 chunk (priority 80, set by the
596
- // module: system>files>repo-map>skills/config>history). Built LAZILY at the
597
- // first buildDef() and memoized — createRuntime stays sync-cheap (`rovecode tools`,
598
- // ACP session setup pay nothing) and per-file tags persist under
599
- // .rovecode/cache/repomap.json, so warm launches skip extraction. Frozen after
600
- // the first build, like config, for prompt-cache stability. ROVECODE_NO_REPOMAP=1
601
- // disables; budget override via ROVECODE_REPOMAP_TOKENS (default 1024, aider's).
602
- let extraChunksMemo: ContextChunk[] | null = null;
603
- const extraChunks = (): ContextChunk[] => {
604
- if (extraChunksMemo !== null) return extraChunksMemo;
605
- let repoMapChunk: ContextChunk | null = null;
606
- if (process.env.ROVECODE_NO_REPOMAP !== "1") {
607
- const budget = Number(process.env.ROVECODE_REPOMAP_TOKENS ?? "") || 1024;
608
- try {
609
- // eslint-disable-next-line @typescript-eslint/no-require-imports
610
- const { buildRepoMapChunk } = require("../coding/repomap.ts") as { buildRepoMapChunk: typeof BuildRepoMapChunkFn };
611
- repoMapChunk = buildRepoMapChunk(cwd, budget);
612
- } catch { repoMapChunk = null; }
613
- }
614
- extraChunksMemo = [configChunk, repoMapChunk].filter((c): c is ContextChunk => c !== null);
615
- return extraChunksMemo;
616
- };
617
- // warmRepoMap (Runtime interface): the map builds behind the TUI's first frame — COOPERATIVELY
618
- // (repomap.ts buildRepoMapChunkAsync: awaited enumeration, sliced extraction, a yield per budget-search
619
- // step), so the frame loop keeps painting, reading keys and following resizes while it runs. The
620
- // synchronous build here used to block the event loop for 10 s in a 650-file repo with a cold tags cache
621
- // (a home directory: far longer) and the sextant sat frozen at its first reveal step. While the build is
622
- // pending, buildDef hands out the cheap chunks only — "the map is not in this prompt", never "the request
623
- // waits" — and does NOT memoize, so the warm build's result is the one that freezes the map.
624
- let warmPending = false;
625
- const warmAbort = new AbortController();
626
- let warmTask: Promise<void> | null = null;
627
- const stopRepoMapWarmup = async (): Promise<void> => { warmAbort.abort(); await warmTask; };
628
- const warmRepoMap = (): void => {
629
- if (extraChunksMemo !== null || warmPending || warmAbort.signal.aborted) return;
630
- warmPending = true;
631
- const settle = (repoMapChunk: ContextChunk | null): void => {
632
- warmPending = false;
633
- if (extraChunksMemo === null) extraChunksMemo = [configChunk, repoMapChunk].filter((c): c is ContextChunk => c !== null);
634
- };
635
- if (process.env.ROVECODE_NO_REPOMAP === "1" || !shouldWarmRepoMap(cwd)) { settle(null); return; }
636
- const budget = Number(process.env.ROVECODE_REPOMAP_TOKENS ?? "") || 1024;
637
- let build: Promise<ContextChunk | null>;
638
- try {
639
- // eslint-disable-next-line @typescript-eslint/no-require-imports
640
- const { buildRepoMapChunkAsync } = require("../coding/repomap.ts") as { buildRepoMapChunkAsync: typeof BuildRepoMapChunkAsyncFn };
641
- build = buildRepoMapChunkAsync(cwd, budget, { signal: warmAbort.signal });
642
- } catch { build = Promise.resolve(null); }
643
- // A background failure is a cache miss, not a reason to freeze the next interactive submit.
644
- // Headless runtimes that never warm still build synchronously in extraChunks().
645
- warmTask = build.then(settle, () => settle(null));
646
- };
647
- const chunksForDef = (): ContextChunk[] => extraChunksMemo ?? (warmPending ? [configChunk].filter((c): c is ContextChunk => c !== null) : extraChunks());
648
-
649
- const systemPrompt = (cwdOverride?: string): string => {
650
- const skillsIndex = buildSkillsIndex(skillStore);
651
- const memoryIndex = blocks.renderForPrompt();
652
- return `You are Rovecode, an interactive coding agent in ${cwdOverride ?? cwd}. Use read/edit/write/bash tools. Edits require line hashes from read output. Match the length of an answer to the task: a line for a lookup, the full thing for a plan, a design or a review — never pad, never truncate work that was asked for.${skillsIndex ? "\n\n# Skills\n" + skillsIndex : ""}${memoryIndex ? "\n\n# Memory\n" + memoryIndex : ""}${roots.promptLine()}`;
653
- };
654
-
655
- // ROVECODE_EFFORT is the boot default; /effort and --effort move it at runtime
656
- // default "auto": no thinking field on the wire, the provider's own default stands (Claude 5: adaptive,
657
- // high). The old default "off" sent an explicit `thinking: disabled` and switched off the reasoning the
658
- // model does by itself — most of "we are not getting the model's real performance" (Berkay, 2026-09-04).
659
- let effort: ThinkingEffort = parseEffort(process.env.ROVECODE_EFFORT) ?? "auto";
660
-
661
- /** models the catalog knows CANNOT do native tool calling get the senpi-format prompt block (port #7); unknown models
662
- * attempt native first. Force: ROVECODE_TOOL_MIDDLEWARE=1. One rule for the root def and for a child's (runChild). */
663
- const nonNativeFor = (m: ModelRef): boolean => catalog.lookup(m.provider, m.model)?.supportsTools === false || process.env.ROVECODE_TOOL_MIDDLEWARE === "1";
664
- const buildDef = (model: ModelRef, opts: { cwd?: string; agent?: CustomAgent; child?: boolean } = {}): AgentDefinition => {
665
- if (model.effort === undefined) model = { ...model, effort }; // one dial, every surface
666
- if (!opts.agent) activeModel = model; // port #26: children run the model of the run that started them — a custom agent's own model never re-points it
667
- // models the catalog knows CANNOT do native tool calling get the senpi-format
668
- // prompt block (port #7); unknown models attempt native first. Force: ROVECODE_TOOL_MIDDLEWARE=1
669
- const info = catalog.lookup(model.provider, model.model);
670
- // the answer's room comes from the catalog (models.dev maxOutput), capped: the old flat 4096 default
671
- // truncated long outputs — a whole page of UI, a long plan — mid-sentence, and the model was blamed
672
- if (model.maxTokens === undefined && info?.maxOutput) model = { ...model, maxTokens: Math.min(info.maxOutput, MAX_OUTPUT_CAP) };
673
- // the catalog's word on a reasoning mode rides with the ref: thinking.ts sends no dial to a model listed without one
674
- if (model.reasoning === undefined && info?.supportsReasoning !== undefined) model = { ...model, reasoning: info.supportsReasoning };
675
- const nonNative = nonNativeFor(model);
676
- // model profile (providers/profiles.ts): a per-family behavioral section rides AFTER the base prompt
677
- // and its indexes and BEFORE the tool-calling block — one string for the whole run (prompt cache);
678
- // .rovecode/profiles/<id>.md replaces the built-in text, ROVECODE_PROFILE=off drops it
679
- const profile = profileFor(model);
680
- // A model WITHOUT a profile gets the working agreement too (profile-glm53.ts names no model or vendor
681
- // — it is the harness's contract: read before edit, verify before "done", parallel calls, scope).
682
- // Until now only GLM received it and Claude/GPT got one sentence; that asymmetry cost quality.
683
- const section = profile === null ? GLM_53_AGENT_CONTRACT : profilePromptSection(profile, cwd); // "" = an empty override file: no section, no separator
684
- // design protocol (design/rules.ts): the ban list plus this project's recorded direction, read
685
- // once per run start like the profile so the system prefix stays byte-stable (prompt cache).
686
- // ROVECODE_DESIGN=off drops it for a run that has nothing to do with interfaces.
687
- const design = process.env.ROVECODE_DESIGN === "off" ? "" : designPromptSection(opts.cwd ?? cwd);
688
- // a custom definition's body IS its system prompt (core/agents.ts); an empty body inherits the runtime's whole prompt
689
- const base = opts.agent?.body || [systemPrompt(opts.cwd), section, design].filter((p) => p.length > 0).join("\n\n");
690
- return {
691
- name: opts.agent?.name ?? "main", model, tools: opts.agent ? [...opts.agent.tools] : ["*"], ...(opts.agent?.mode !== undefined ? { mode: opts.agent.mode } : {}),
692
- // a child def stays bare: orchestrator runChild appends the block from the CHILD's own registry, never the root's list
693
- systemPrompt: nonNative && !opts.child
694
- ? `${base}\n\n# Tool calling\n${toolPromptBlock(registry.list().map((t) => t.schema))}`
695
- : base,
696
- ...(chunksForDef().length > 0 ? { contextChunks: chunksForDef() } : {}),
697
- };
698
- };
699
- /** the workspace as a rule resource: every path resource is absolute (tools.ts describeResource),
700
- * so `<cwd><sep>*` is "inside this repository" and nothing else — a sibling directory whose name
701
- * merely STARTS with the cwd (…/repo-backup) does not match, because the separator is in the glob. */
702
- const insideCwd = `${cwd.replace(/[\/]$/, "")}${sep}*`;
703
-
704
- // run ceilings (cli/run-limits.ts): a surface's explicit limits, else the environment, else 60 turns and no
705
- // clock — the TUI and serve/acp get the env knobs for free, `rovecode run` adds its flags + a 20-minute default
706
- let runLimits: RunLimits = {};
707
- const buildCfg = (permission: PermissionLevel | boolean, approval?: ApprovalFn): RunConfig => {
708
- const level: PermissionLevel = permission === true ? "auto" : permission === false ? "ask" : permission;
709
- const yolo = level === "auto";
710
- const maxSeconds = runLimits.maxSeconds ?? positiveInt(process.env.ROVECODE_MAX_SECONDS);
711
- const maxCostUsd = runLimits.maxCostUsd ?? positiveUsd(process.env.ROVECODE_MAX_COST);
712
- // the same arithmetic /cost and the headless result use (tui/cost.ts, cli/output.ts): the catalog's price
713
- // for the model that served the turn, tiered by the prompt the turn actually carried; no price → undefined
714
- const priceUsd = (usage: TokenUsage, origin: ModelRef): number | undefined => {
715
- const info = catalog.lookup(origin.provider, origin.model);
716
- if (!info?.pricing) return undefined;
717
- const n = { input: usage.input, output: usage.output, cacheRead: usage.cacheRead ?? 0, cacheWrite: usage.cacheWrite ?? 0 };
718
- return costUsdTiered(n, ratesFor(info, n.input + n.cacheRead + n.cacheWrite));
719
- };
720
- // the finish check (core/loop.ts "done" exit): on by default, ROVECODE_FINISH_CHECK=0 is the escape hatch;
721
- // the todo state is read from disk at the exit so a list the model wrote this run is what gets reported
722
- const todoState = (): { open: number; total: number } | null => {
723
- try {
724
- const items = loadTodos(join(sessionsDir, activeStore.id)).items;
725
- return items.length === 0 ? null : { open: items.filter((i) => i.status !== "completed").length, total: items.length };
726
- } catch { return null; }
727
- };
728
- // the verify gate (core/verify-gate.ts): OFF unless ROVECODE_VERIFY=1. Measured 2026-09-06 (nimbus-6f): the
729
- // projects actually edited with rovecode have no check command at all (23 of 23 edits), and where one exists it
730
- // costs 3 s to 170 s — a gate that spends three minutes on a six-line CSS edit gets turned off and never comes
731
- // back. The default lives in this ONE comparison so flipping it later is editing this line. The check is
732
- // resolved per run (a key added to settings mid-session counts next run); ROVECODE_VERIFY_TIMEOUT=<seconds>
733
- // bounds one command, default 120.
734
- const verifyOn = process.env.ROVECODE_VERIFY === "1";
735
- const verifyTimeoutMs = (positiveInt(process.env.ROVECODE_VERIFY_TIMEOUT) ?? VERIFY_TIMEOUT_MS / 1000) * 1000;
736
- const verifyGate = (): RunConfig["verify"] => {
737
- const resolution = (opts.verifyResolver ?? resolveForGate)(cwd);
738
- return {
739
- resolution, timeoutMs: verifyTimeoutMs,
740
- run: async (signal) => { const o = await runVerify(resolution ?? { commands: [] }, cwd, { signal, timeoutMs: verifyTimeoutMs }); noteVerifyCost(cwd, o); return o; },
741
- };
742
- };
743
- return (activeCfg = {
744
- maxTurns: runLimits.maxTurns ?? positiveInt(process.env.ROVECODE_MAX_TURNS) ?? 60,
745
- ...(verifyOn ? { verify: verifyGate() } : {}),
746
- ...(maxSeconds !== undefined ? { maxSeconds } : {}),
747
- ...(maxCostUsd !== undefined ? { maxCostUsd, priceUsd } : {}),
748
- finishCheck: process.env.ROVECODE_FINISH_CHECK !== "0",
749
- todoState,
750
- // the history budget follows the model's window: a flat 200k spent a fifth of a 1M window and
751
- // overflowed a 128k one. ROVECODE_CONTEXT_BUDGET overrides; an unknown window keeps the old default.
752
- contextBudgetTokens: (() => {
753
- const ref = activeModel ?? fallbackRef;
754
- const cur = catalog.lookup(ref.provider, ref.model);
755
- return contextBudgetFor({
756
- ...(cur?.contextWindow !== undefined ? { window: cur.contextWindow } : {}),
757
- ...(cur?.maxOutput !== undefined ? { maxOutput: cur.maxOutput } : {}),
758
- ...(positiveInt(process.env.ROVECODE_CONTEXT_BUDGET) !== undefined ? { override: positiveInt(process.env.ROVECODE_CONTEXT_BUDGET) as number } : {}),
759
- // our estimator is not this model's tokenizer, so a budget taken at face value compacts too
760
- // late and the request that follows is rejected. charScale, not scale: what this budget is
761
- // compared against is estimateTokens (chars/4) in loop.ts and compaction.ts, never countTokens.
762
- scale: tokenScaleFor(ref).charScale,
763
- });
764
- })(),
765
- compactionThreshold: 0.8,
766
- compactionStrategy: parseCompactionStrategy(process.env.ROVECODE_COMPACTION) ?? "head-summarize", // port #25: ROVECODE_COMPACTION=head-summarize|keep-window|provider-native
767
- parallelTools: true,
768
- permissionRules: yolo
769
- ? [{ action: "*", resource: "*", effect: "allow" }]
770
- : [
771
- { action: "file.read", resource: "*", effect: "allow" },
772
- // the workspace boundary (core/workspace.ts; tools.ts dispatch step 2a): a file tool aimed OUTSIDE the cwd
773
- // asks once per directory, on a card that names the path, the cwd, the roots and the remedy. Inside the cwd
774
- // this rule is never consulted, so a person who stays in their project cannot tell it exists. The
775
- // `--add-dir` roots follow as allows, so nothing under a root reaches the card.
776
- { action: EXTERNAL_ACTION, resource: "*", effect: "prompt" },
777
- ...roots.rules(),
778
- { action: "memory.write", resource: "*", effect: "allow" },
779
- { action: "tool.skill_view", resource: "*", effect: "allow" },
780
- { action: "tool.skills_list", resource: "*", effect: "allow" },
781
- // mcp_list is kind:"read" → action "file.read"; the allow above already covers it
782
- { action: "file.write", resource: "*", effect: "prompt" },
783
- { action: "shell.exec", resource: "*", effect: "prompt" },
784
- { action: "spawn", resource: "*", effect: "prompt" },
785
- { action: "tool.mcp_call", resource: "*", effect: "prompt" },
786
- // provider_edit (tools/provider.ts) rewrites providers.json / the default model: ask first.
787
- // provider_list is kind read → covered by the file.read allow above
788
- { action: "tool.provider_edit", resource: "*", effect: "prompt" },
789
- // design_direction set writes .rovecode/design.json -- the once-per-project design identity,
790
- // and that ONE card is the point: it is where the human sees what is recorded for them.
791
- // `get` only reads that file, so it is allowed (last match wins): making the human approve
792
- // the read costs an interruption before every UI task AND trains them to allow the card
793
- // reflexively, which is the card that matters. The two modes are told apart by the tool's
794
- // own resource() (tools/design.ts), not by the tool name.
795
- { action: "tool.design_direction", resource: "*", effect: "prompt" },
796
- { action: "tool.design_direction", resource: "get", effect: "allow" },
797
- // port #31: resource = canonical host (lowercased, no trailing dot), so `allow
798
- // net.fetch <host>` auto-runs THAT host only; web_fetch stops at a redirect to
799
- // another host and reports it, so the new host gets its own decision here
800
- { action: "net.fetch", resource: "*", effect: "prompt" },
801
- // accept-edits: writing INSIDE the workspace stops asking. Placed last of the file.write
802
- // rules because the last match wins (tools.ts evaluatePermissions) — a write outside the
803
- // repo still hits the prompt rule above, and any deny rule a surface appends still wins.
804
- ...(level === "accept-edits" ? [{ action: "file.write", resource: insideCwd, effect: "allow" as const }, ...roots.acceptEditsRules()] : []), // a root is inside the workspace here too
805
- ],
806
- // port #9: execpolicy refines the PROMPT branch only (allow-listed argv →
807
- // "once", forbidden → deny before any human); rules above stay the outer gate.
808
- // The wrap is UNCONDITIONAL on gated configs (R2 #9 LOW-3): headless surfaces
809
- // (run/serve pass no approver) get allow-list auto-run + forbidden hard-stop,
810
- // and prompt-classified argv fails closed instead of "no approver connected".
811
- // yolo stays approver-free — its allow-all rules never reach the prompt branch.
812
- // port #29: the approval hook sits INSIDE the wrap, where the human would — a
813
- // forbidden argv never reaches a hook, an allow-listed one never asks (hooks.ts).
814
- // port #47: outermost, laneApprover turns a `task start` naming an external CLI lane into the card
815
- // text BEFORE it starts — the flags that lane will run with, named where a person can still say no
816
- approval: yolo ? undefined : laneApprover(execPolicyApprover(hooks.approver(approval)), opts.lanes?.env),
817
- });
818
- };
819
-
820
- // port #26: background subagents. Children run through orchestrator runChild (the ONE
821
- // agentLoop) with deps resolved at each start: the def/config of the run that STARTED
822
- // the task (buildDef/buildCfg record them — every surface calls both right before its
823
- // agentLoop, so a child inherits its parent's model and policy; deriveChildRules turns
824
- // prompt→deny). ONE SteeringQueue per runtime: surfaces hand it to agentLoop and
825
- // completion notes land in the parent's next turn (loop.ts:136). Children get the core
826
- // coding/search/skill tools (no MCP/memory/eval-cell/checkpoints in v1) plus nested
827
- // `task` (kind spawn, bound to THEIR depth + steering queue, so the depth cap governs
828
- // nesting) and `task_status` (kind read: a child collects ITS children's results without
829
- // a prompt nobody could answer — MED-2 split, tools/task.ts header).
830
- let activeCfg: RunConfig | null = null;
831
- let activeModel: ModelRef | null = null;
832
- const steering = new SteeringQueue();
833
- const childRegistry = (def: AgentDefinition, childCwd: string, child?: ChildContext): ToolRegistry => {
834
- const reg = new ToolRegistry();
835
- const table: Tool[] = [readTool, editTool, writeTool, bashTool, globTool, grepTool, lsTool, ...createSkillTools(skillStore), recallTool(sessionsDir)];
836
- // a nested `task` reports THIS registry's names (lazily — it is filled below) so a grandchild is clamped to them
837
- // `parentDir` is this child's own working tree: an external lane it starts must build its worktree
838
- // there and merge back there, or an isolated child's lane writes into the user's live tree past the
839
- // isolation the child itself is held to. ChildContext.dir is the same value runChild passed as cwd.
840
- if (child) table.push(createTaskTool(tasks, { parentDepth: child.depth, notify: child.steering, caller: child.taskId, owner: child.signal, agents: agentRowsList, parentDir: child.dir ?? childCwd, parentTools: () => new Set(reg.list().map((t) => t.schema.name)) }), createTaskStatusTool(tasks, { caller: child.taskId }));
841
- // the definition's allow-list is a FILTER over this table ∩ the STARTING registry's names — never wider (main's "*" keeps
842
- // the table). The starting registry is the root's for a root start, the spawning CHILD's for a nested one
843
- // (ChildContext.parentTools): a restricted agent that has `task` cannot hand `main` — or any definition — a wider set than its own.
844
- reg.register(...restrictTools(table, def.tools, child?.parentTools ?? new Set(registry.list().map((t) => t.schema.name))));
845
- return reg;
846
- };
847
- /** the definitions a task may start, built beside `main` from the SAME buildDef at each start: a definition inherits the
848
- * starting run's prompt/model unless it names its own ("provider/model" = the router grammar; a bare id runs on the
849
- * starting run's provider). `child: true` leaves them without the root's tool block (runChild renders the child's). */
850
- const defsFor = (parent: ModelRef): Map<string, AgentDefinition> => {
851
- const defs = new Map<string, AgentDefinition>([["main", buildDef(parent, { child: true })]]);
852
- for (const a of agents.agents) defs.set(a.name, buildDef(a.model !== undefined ? parseModelRef(a.model, parent.provider) : parent, { agent: a, child: true }));
853
- return defs;
854
- };
855
- const tasks = new TaskManager({
856
- deps: (): ChildRunnerDeps | null => stream ? {
857
- defs: defsFor(activeModel ?? fallbackRef),
858
- stream, registryFactory: childRegistry, rootDir: cwd, sessionsDir,
859
- toolPrompt: (m, tools) => (nonNativeFor(m) ? toolPromptBlock(tools) : ""), // the child's own restricted set, never the root's list
860
- baseConfig: activeCfg ?? buildCfg(false),
861
- hooks, // port #29: children run under the runtime's hooks (a veto cannot be dodged by delegation)
862
- } : null,
863
- lanes: opts.lanes, // port #47: external CLI lanes are jobs of this same manager
864
- });
865
- tasks.attach(steering);
866
- // #39: finished external lanes become their own spans (open on "running", closed on the terminal
867
- // transition). Off unless OTel is configured — with no endpoint nothing above was constructed.
868
- otelHooks?.observeTasks(tasks);
869
- // port #28: built-in reflection set (core/reflection.ts) — a failed edit/write (or an LSP-diagnosed one) nudges the model once via steering, capped per run (ROVECODE_REFLECTION_MAX); ROVECODE_REFLECTION=0 disables.
870
- // owns: the ACTIVE session's runs only — a task child (own store id, same hooks) must neither nudge nor sweep this queue (#26 MED-A)
871
- if (reflectionEnabled()) hooks.add(createReflectionHooks({ steering, owns: (c) => c.sessionId === activeStore.id }), "reflection");
872
- registry.register(createTaskTool(tasks, { parentDepth: 0, agents: agentRowsList }), createTaskStatusTool(tasks)); // task: kind spawn → gated rules prompt once per start, yolo allows; task_status: kind read → allowed everywhere
873
-
874
- // port #55: ONE background-shell-job manager per runtime, installed where bashTool can reach it
875
- // without every call site threading it. Notes land in the SAME steering queue task completions use,
876
- // so a finished job is drained before the next model call instead of interrupting a turn. It is
877
- // installed process-wide because bashTool is a module-level constant shared by every surface; a
878
- // second runtime replaces it, which is what a second runtime should mean.
879
- const bashJobs = new BashJobManager({ notify: steering });
880
- installJobManager(bashJobs);
881
- registry.register(...bashJobTools); // bash_list / bash_output (kind read) · bash_kill (kind execute)
882
-
883
- return {
884
- cwd, sessionId, store, registry, skillStore,
885
- get blockStore() { return blocks; },
886
- setBlockStore(b: BlockStore) { blocks = b; registry.register(memoryEditTool(b)); },
887
- guard, planReminder: planReminderFor, get mcp() { return mcp; }, reloadMcp, projectContext, router, roots, agents,
888
- get effort() { return effort; },
889
- setEffort(e: ThinkingEffort) { effort = e; },
890
- setRunLimits(l: RunLimits) { runLimits = l; },
891
- drainRouterNotes: () => routerNotes.splice(0),
892
- onRouterNote(fn) { for (const n of routerNotes.splice(0)) fn(n); routerListeners.push(fn); },
893
- checkpointsFor,
894
- setSessionStore(s: SessionStore) { activeStore = s; },
895
- sandbox,
896
- setAskUser(fn: AskFn | undefined) { askUser = fn; },
897
- hooks,
898
- plugins,
899
- providers,
900
- get provider() { return providers.defaultConfig(); },
901
- stream,
902
- get defaultModel() { return providers.defaultRef()?.model ?? process.env.ROVECODE_MODEL ?? ""; },
903
- noProviderReason: () => (opts.stream === undefined && !providers.configured() ? NO_PROVIDER_HINT : null),
904
- systemPrompt,
905
- buildDef, buildCfg, warmRepoMap, stopRepoMapWarmup,
906
- steering, tasks, bashJobs,
907
- };
908
- }
909
-
910
- /** The one sentence every surface shows when nothing is configured (Runtime.noProviderReason). */
911
- export const NO_PROVIDER_HINT = noModelHint("cli");
912
-
913
- /** port #27: construct + await the sandbox probe — the boot path for every
914
- * entrypoint that must fail CLEANLY at startup (run/repl/tui/acp/serve).
915
- * createRuntime stays sync (its many callers/tests build synchronously); this
916
- * is the one place the async verdict is joined. Throws SandboxConfigError
917
- * (one actionable line) for a bad config or a configured rung the machine
918
- * cannot provide; MCP children spawned during construction are reaped first,
919
- * so a failed boot leaves no processes behind. */
920
- export async function bootRuntime(opts: RuntimeOptions = {}): Promise<Runtime> {
921
- const rt = createRuntime(opts);
922
- try {
923
- await rt.sandbox.ready;
924
- await rt.hooks.ready; // port #29: hook files + session_open joined here too (notes recorded before the first prompt)
925
- await rt.plugins.ready; // plugin entry modules imported, their tools and hooks attached — before any surface's first prompt
926
- } catch (e) {
927
- await rt.mcp?.close().catch(() => {});
928
- throw e;
929
- }
930
- return rt;
931
- }