rovecode 0.3.3 → 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 -48
  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 +83 -72
  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
package/src/core/hooks.ts DELETED
@@ -1,411 +0,0 @@
1
- /** Hooks v2 (port #29): a small TYPED, file-loadable hook set — ≤10 hooks, versioned (ADR-013:
2
- * "no 1.8k-line extension API surface — small typed hook set, versioned").
3
- *
4
- * Contract (HOOKS_API_VERSION 1): nine hooks, every one optional, sync or async —
5
- * pre_run(ctx) · post_run(ctx, {status, summary}) · pre_tool(ctx, call) → void | {deny}
6
- * post_tool(ctx, call, result) → void | {output?} · approval(ctx, req) → void | "allow" | "deny"
7
- * compaction(ctx, event) · session_open(ctx) · session_close(ctx) · on_event(ctx, ev)
8
- * ctx = {cwd, sessionId, runId?} and nothing else (no registry/store handles) — the surface stays
9
- * tiny on purpose; a tenth hook is the budget's last slot, not a target.
10
- *
11
- * Loading: `.rovecode/hooks.ts` or `.rovecode/hooks.js` in the project plus `~/.rovecode/hooks.{ts,js}`
12
- * (ROVECODE_HOME idiom, providers/auth.ts rovecodeHome), each `export default { version: 1, hooks: {…} }`,
13
- * imported with a plain `await import()` — Bun runs TypeScript natively, so there is no build step
14
- * and no loader dependency. User scope runs first, project second. Wrong/missing version → skipped
15
- * with a warning (version gate); a failing import → warning, never a throw. Loaded ONCE per
16
- * process (Bun's module cache; a `?query` does not bust it on 1.3.14): restart rovecode to pick up
17
- * edits — the projectContext rule. ROVECODE_NO_HOOKS=1 skips the files (programmatic add() still works).
18
- *
19
- * Running: every hook call is timeout-bounded (ROVECODE_HOOK_TIMEOUT_MS, default 5000, on a REF'D
20
- * timer) and isolated — a throwing or hanging hook records one bounded warning note and the run
21
- * continues as if the hook had returned void. Results are validated and bounded (deny reason
22
- * ≤ MAX_DENY_REASON_CHARS; post_tool may grow the tool's output by ≤ MAX_POST_TOOL_GROWTH_CHARS).
23
- * pre_tool / post_tool ride core/tools.ts dispatch at the existing hook seams; approval rides the
24
- * ApprovalFn chain as approver() (composed in cli/runtime.ts buildCfg — see Authority); the
25
- * run-level hooks ride the event stream via observer() in core/loop.ts agentLoop (pre_run /
26
- * compaction / post_run awaited in order, on_event a fire-and-forget tap — port #39 OTel builds on
27
- * it); session_open/close are the runtime's lifetime (cli/runtime.ts + the surfaces' close paths).
28
- *
29
- * Authority: POLICY WINS. Permission rules (deny-default, last-match) are evaluated BEFORE pre_tool,
30
- * so a hook never sees — and can never "un-deny" — a rule-rejected call; pre_tool can only deny,
31
- * and its deny applies in every mode including yolo (a hook is the user's own stricter layer). The
32
- * approval hook stands in for the HUMAN — literally: approver(human) is an ApprovalFn that buildCfg
33
- * composes INSIDE execPolicyApprover, so the order is permission rules → execpolicy argv
34
- * classification (port #9: forbidden → deny before any hook or human; allow-listed → runs, nobody
35
- * asked) → approval hook → human. Consulted only on a policy "prompt" with nothing cached; "allow"
36
- * is a one-shot yes (never cached), "deny" denies (the chain's deny shape, as execpolicy's), void →
37
- * the human, or fail closed when there is none (headless). A hook "allow" is exactly as strong as
38
- * the human's "once" — never stronger than policy or execpolicy. A failing/timed-out hook cannot
39
- * deny (fail-open to policy, which already ran).
40
- *
41
- * TRUST (gated since 2026-09-07, core/trust.ts): hooks are code executed in-process with the user's
42
- * privileges — the worst thing a checkout can carry, because there is no spawn to classify and no
43
- * execpolicy to consult. The HOME file is the user's own and always loads. The PROJECT file loads only
44
- * when this machine approved its current bytes (`rovecode trust`; the same digest store as project
45
- * mcp.json and plugins); the gate is asked BEFORE the import, on the whole file, so a refusal is never a
46
- * partial load — one warning names the file and stays off. .rovecode/commands/*.md are prompt text, not
47
- * executables: a different class of gate, not this one.
48
- *
49
- * Sources (pattern references, no code copied; both MIT — covered by the generic MIT credit in the
50
- * THIRD_PARTY_NOTICES.md preamble, no per-port entry as MIT sources are credited in module headers):
51
- * - pi @ 853a80d packages/coding-agent/src/core/extensions — loader.ts:498-510 imports the
52
- * extension module (jiti there) and reads its default export; runner.ts:851-884 emit() runs
53
- * handlers in registration order with a per-handler try/catch → emitError (isolation);
54
- * runner.ts:982-1004 emitToolCall: the first `block` result wins; types.ts:1125-1131
55
- * ToolCallEventResult {block, reason}; types.ts:1144-1149 ToolResultEventResult replaces the
56
- * result content; types.ts:1247 a handler may be sync or async.
57
- * - opencode @ ebece6e packages/opencode/src/plugin — loader.ts:135-145 load() is a plain
58
- * `await import(entry)` returning {ok:false, error} instead of throwing; loader.ts:203-236 failed
59
- * entries are dropped and the successful order preserved; index.ts:284-297 trigger() runs each
60
- * plugin's hook sequentially and later hooks see earlier mutations; index.ts:226-243 a plugin
61
- * that fails to apply is logged and skipped.
62
- * Deviations: an explicit `version` gate (upstream file plugins have none — opencode's compatibility
63
- * check is npm-only, loader.ts:123-131); a per-call timeout (neither upstream bounds a hook);
64
- * validated + bounded results; nine hooks instead of pi's ~45 events / opencode's open Hooks map. */
65
-
66
- import { existsSync } from "node:fs";
67
- import { join, resolve } from "node:path";
68
- import { pathToFileURL } from "node:url";
69
- import type { ApprovalFn, ApprovalRequest, RunEvent, ToolOutput } from "./types.ts";
70
- import { rovecodeHome } from "../providers/auth.ts";
71
- import { isTrustedFile, untrustedFileNote } from "./trust.ts";
72
-
73
- export const HOOKS_API_VERSION = 1;
74
- export const DEFAULT_HOOK_TIMEOUT_MS = 5000;
75
- export const MAX_DENY_REASON_CHARS = 400;
76
- export const MAX_POST_TOOL_GROWTH_CHARS = 16_000;
77
- const MAX_WARNINGS = 50;
78
- const MAX_WARNING_CHARS = 300;
79
-
80
- // ---------- contract ----------
81
-
82
- export interface HookCtx { cwd: string; sessionId: string; runId?: string }
83
- export interface HookToolCall { id: string; tool: string; args: unknown }
84
- export interface RunResult { status: "done" | "stopped" | "error" | "budget"; summary: string }
85
- export type CompactionEvent = Extract<RunEvent, { type: "compaction" }>;
86
- type Hook<A extends unknown[], R = void> = (ctx: HookCtx, ...args: A) => Promise<R | void> | R | void;
87
-
88
- /** The whole surface. Adding a member here is an API version bump (HOOKS_API_VERSION). */
89
- export interface HookSet {
90
- /** run start, after run_start is produced (ctx.runId set) and before the first model turn */
91
- pre_run?: Hook<[]>;
92
- /** run end, awaited BEFORE run_end reaches the consumer (so it lands before a cmdRun exit); a run whose
93
- * CONSUMER closed the generator first (serve disconnect, ACP cancel, TUI Esc) gets it once as {stopped, "run aborted"} */
94
- post_run?: Hook<[result: RunResult]>;
95
- /** before a tool executes, after policy allowed it; {deny} fails the call with this reason */
96
- pre_tool?: Hook<[call: HookToolCall], { deny: string }>;
97
- /** after a tool ran; {output} replaces what the model sees (growth-bounded), {} = unchanged */
98
- post_tool?: Hook<[call: HookToolCall, result: ToolOutput], { output?: string }>;
99
- /** policy said prompt, execpolicy left it to a human, nothing is cached: pre-answer instead of the
100
- * human ("allow" one-shot / "deny"), or void to ask them — ctx is the runtime's (no runId) */
101
- approval?: Hook<[req: ApprovalRequest], "allow" | "deny">;
102
- /** after a history compaction (the event as yielded: strategy, trigger, token counts) */
103
- compaction?: Hook<[event: CompactionEvent]>;
104
- /** once per runtime, after the hook files loaded */
105
- session_open?: Hook<[]>;
106
- /** once per runtime, at surface teardown, after in-flight on_event taps settled */
107
- session_close?: Hook<[]>;
108
- /** every RunEvent, in order, fire-and-forget (not awaited — keep it cheap; port #39 OTel tap) */
109
- on_event?: Hook<[ev: RunEvent]>;
110
- }
111
- export type HookName = keyof HookSet;
112
- /** exhaustive by construction (`satisfies Record<HookName, 0>` rejects a missing or extra key) */
113
- export const HOOK_NAMES = Object.keys({
114
- pre_run: 0, post_run: 0, pre_tool: 0, post_tool: 0, approval: 0, compaction: 0, session_open: 0, session_close: 0, on_event: 0,
115
- } satisfies Record<HookName, 0>) as HookName[];
116
- /** shape of a hooks file's default export */
117
- export interface HookModule { version: number; hooks: HookSet }
118
- export type HookArgs<K extends HookName> = Parameters<NonNullable<HookSet[K]>>;
119
- /** what a hook may decide (its non-void return), validated + bounded by the runner */
120
- export type HookDecision<K extends HookName> = Exclude<Awaited<ReturnType<NonNullable<HookSet[K]>>>, void>;
121
-
122
- // ---------- loader ----------
123
-
124
- export interface LoadedHooks { hooks: HookSet[]; sources: string[]; warnings: string[] }
125
-
126
- /** Load `<home>/hooks.{ts,js}` (user, first) and `<cwd>/.rovecode/hooks.{ts,js}` (project, second).
127
- * Never throws: every failure is a warning line naming the file. hooks[i] came from sources[i]. */
128
- export async function loadHooks(cwd: string, opts: { home?: string; timeoutMs?: number; trusted?: (file: string) => boolean } = {}): Promise<LoadedHooks> {
129
- const out: LoadedHooks = { hooks: [], sources: [], warnings: [] };
130
- if (process.env.ROVECODE_NO_HOOKS === "1") return out;
131
- const home = opts.home ?? rovecodeHome();
132
- // THE TRUST GATE (core/trust.ts, 2026-09-07): the project file is code from a checkout, imported in-process with the
133
- // person's privileges — asked BEFORE the import, on the whole file, so a refusal is never a partial load. The home
134
- // file is the person's own and is never gated.
135
- const trusted = opts.trusted ?? ((file: string) => isTrustedFile(home, file));
136
- const seen = new Set<string>();
137
- for (const [scope, dir] of [["user", home], ["project", join(cwd, ".rovecode")]] as const) {
138
- const file = pickHookFile(dir, out.warnings);
139
- if (file === null || seen.has(resolve(file))) continue; // home inside cwd/.rovecode: one load
140
- seen.add(resolve(file));
141
- if (scope === "project" && !trusted(file)) {
142
- out.warnings.push(untrustedFileNote(file, "its hooks stay off (they would run code from this repo in-process, with your privileges)"));
143
- continue;
144
- }
145
- const set = await importHookSet(file, opts.timeoutMs ?? hookTimeoutMs(), out.warnings);
146
- if (set) { out.hooks.push(set); out.sources.push(file); }
147
- }
148
- return out;
149
- }
150
-
151
- function pickHookFile(dir: string, warnings: string[]): string | null {
152
- const ts = join(dir, "hooks.ts"), js = join(dir, "hooks.js");
153
- const hasTs = existsSync(ts), hasJs = existsSync(js);
154
- if (hasTs && hasJs) warnings.push(`${js}: ignored — ${ts} takes precedence`);
155
- return hasTs ? ts : hasJs ? js : null;
156
- }
157
-
158
- async function importHookSet(file: string, timeoutMs: number, warnings: string[]): Promise<HookSet | null> {
159
- let mod: unknown;
160
- try {
161
- mod = await withTimeout(import(pathToFileURL(file).href), timeoutMs);
162
- } catch (e) {
163
- warnings.push(`${file}: failed to load — ${errText(e)}`);
164
- return null;
165
- }
166
- if (mod === TIMED_OUT) { warnings.push(`${file}: load timed out after ${timeoutMs}ms (top-level await?) — skipped`); return null; }
167
- return validateModule(file, isRecord(mod) ? mod["default"] : undefined, warnings);
168
- }
169
-
170
- /** default export → HookSet; the version gate lives here. Unknown/non-function members are
171
- * dropped with a warning, the rest kept (a typo must not silently disable the whole file). */
172
- function validateModule(file: string, dflt: unknown, warnings: string[]): HookSet | null {
173
- if (!isRecord(dflt)) { warnings.push(`${file}: default export must be { version: ${HOOKS_API_VERSION}, hooks: {…} } — skipped`); return null; }
174
- if (dflt["version"] !== HOOKS_API_VERSION) {
175
- const v = dflt["version"]; // a string "1" is shown quoted, never disguised as the supported number
176
- const shown = v === undefined ? "missing" : typeof v === "string" ? JSON.stringify(v) : String(v);
177
- warnings.push(`${file}: hooks API version ${shown} is not supported (this rovecode speaks ${HOOKS_API_VERSION}) — skipped`);
178
- return null;
179
- }
180
- if (!isRecord(dflt["hooks"])) { warnings.push(`${file}: "hooks" must be an object of hook functions — skipped`); return null; }
181
- const set: Record<string, unknown> = {};
182
- for (const [name, fn] of Object.entries(dflt["hooks"])) {
183
- if (!(HOOK_NAMES as string[]).includes(name)) { warnings.push(`${file}: unknown hook "${name}" ignored (known: ${HOOK_NAMES.join(", ")})`); continue; }
184
- if (typeof fn !== "function") { warnings.push(`${file}: hook "${name}" is not a function — ignored`); continue; }
185
- set[name] = fn;
186
- }
187
- return set as HookSet;
188
- }
189
-
190
- /** ROVECODE_HOOK_TIMEOUT_MS: blank/invalid/< 1 → default. */
191
- export function hookTimeoutMs(env: Record<string, string | undefined> = process.env): number {
192
- const v = Number(env["ROVECODE_HOOK_TIMEOUT_MS"] ?? "");
193
- return Number.isFinite(v) && v >= 1 ? Math.floor(v) : DEFAULT_HOOK_TIMEOUT_MS;
194
- }
195
-
196
- // ---------- runner ----------
197
-
198
- /** the loop's per-run view; close() = the run ended WITHOUT a run_end (consumer-closed generator) */
199
- export interface RunObserver { observe(ev: RunEvent): Promise<void>; close(): Promise<void> }
200
- export interface HookRunnerOptions { timeoutMs?: number; onWarning?: (note: string) => void }
201
-
202
- /** Holds the hook sets of one runtime and runs them: sets in attach order, per call a ref'd timeout
203
- * + try/catch isolation, results validated. Decision hooks: the first decisive result wins
204
- * (pi emitToolCall), except post_tool which chains (each set sees the previous output). */
205
- export class HookRunner {
206
- /** bounded log of load/runtime notes (also streamed to onWarning) */
207
- readonly warnings: string[] = [];
208
- readonly timeoutMs: number;
209
- private readonly entries: { source: string; set: HookSet }[] = [];
210
- private loading: Promise<void> | null = null;
211
- private opened = false;
212
- private closing: Promise<void> | null = null;
213
- private readonly pending = new Set<Promise<void>>();
214
- private listener: ((note: string) => void) | undefined;
215
-
216
- constructor(private readonly base: HookCtx, opts: HookRunnerOptions = {}) {
217
- this.timeoutMs = opts.timeoutMs ?? hookTimeoutMs();
218
- this.listener = opts.onWarning;
219
- }
220
-
221
- /** attach a set programmatically (port #39 OTel) — before the first run; session_open is open()'s */
222
- add(set: HookSet, source = "programmatic"): void { this.entries.push({ source, set }); }
223
- get size(): number { return this.entries.length; }
224
- has(name: HookName): boolean { return this.entries.some((e) => typeof e.set[name] === "function"); }
225
- /** settles when the hook files are attached and session_open has run; run() waits for it */
226
- get ready(): Promise<void> { return this.loading ?? Promise.resolve(); }
227
-
228
- /** load the hook files (background import) then fire session_open ONCE; idempotent */
229
- open(cwd = this.base.cwd, opts: { home?: string } = {}): Promise<void> {
230
- if (this.opened) return this.ready;
231
- this.opened = true;
232
- const load = loadHooks(cwd, { ...opts, timeoutMs: this.timeoutMs }).then(async (loaded) => {
233
- loaded.hooks.forEach((set, i) => this.add(set, loaded.sources[i] ?? "file"));
234
- for (const w of loaded.warnings) this.warn(w);
235
- await this.dispatch("session_open", [this.base]);
236
- }).then(() => { this.loading = null; });
237
- this.loading = load;
238
- return load;
239
- }
240
-
241
- /** run one hook across all sets (waits for open() first, so no caller can race the load) */
242
- async run<K extends HookName>(name: K, ...args: HookArgs<K>): Promise<HookDecision<K> | undefined> {
243
- if (this.loading) await this.loading;
244
- return this.dispatch(name, args);
245
- }
246
-
247
- private async dispatch<K extends HookName>(name: K, args: HookArgs<K>): Promise<HookDecision<K> | undefined> {
248
- let decision: HookDecision<K> | undefined;
249
- for (const entry of this.entries) {
250
- const fn = entry.set[name];
251
- if (typeof fn !== "function") continue;
252
- const raw = await this.invoke(entry.source, name, fn as (...a: unknown[]) => unknown, args);
253
- const d = normalize(name, raw, args, (w) => this.warn(`${entry.source}: ${w}`));
254
- if (d === undefined) continue;
255
- if (name !== "post_tool") return d; // first decisive result wins
256
- decision = d; // post_tool chains: the next set sees this set's output
257
- const cur = args as unknown as [HookCtx, HookToolCall, ToolOutput];
258
- const next = (d as { output?: string }).output;
259
- if (next !== undefined) cur[2] = { ...cur[2], output: next };
260
- }
261
- return decision;
262
- }
263
-
264
- /** one hook function: sync throw, async rejection and timeout all become `undefined` + a note */
265
- private invoke(source: string, name: HookName, fn: (...a: unknown[]) => unknown, args: readonly unknown[]): Promise<unknown> {
266
- let p: Promise<unknown>;
267
- try { p = Promise.resolve(fn(...args)); }
268
- catch (e) { this.warn(`${source}: ${name} hook threw: ${errText(e)} — ignored, run continues`); return Promise.resolve(undefined); }
269
- return withTimeout(p, this.timeoutMs).then(
270
- (v) => {
271
- if (v !== TIMED_OUT) return v;
272
- this.warn(`${source}: ${name} hook timed out after ${this.timeoutMs}ms — ignored, run continues`);
273
- return undefined;
274
- },
275
- (e) => { this.warn(`${source}: ${name} hook threw: ${errText(e)} — ignored, run continues`); return undefined; },
276
- );
277
- }
278
-
279
- /** per-run view for the loop: maps the event stream onto pre_run / compaction / post_run (awaited, in
280
- * order) and taps on_event for every event (fire-and-forget). close() is the loop's teardown seam
281
- * (fix-wave 4, #39 MED-1): a consumer that .return()s the generator before run_end still ended the
282
- * run, so post_run fires ONCE with the abort shape — after a pre_run only, never after a yielded
283
- * run_end — while on_event gets nothing synthesized (it mirrors the consumer's stream exactly). */
284
- observer(base: { cwd: string; sessionId: string }): RunObserver {
285
- let ctx: HookCtx = { cwd: base.cwd, sessionId: base.sessionId };
286
- let ended = false;
287
- return {
288
- observe: async (ev) => {
289
- if (ev.type === "run_start") ctx = { ...ctx, runId: ev.runId };
290
- this.tap(ctx, ev);
291
- if (ev.type === "run_start") await this.run("pre_run", ctx);
292
- else if (ev.type === "compaction") await this.run("compaction", ctx, ev);
293
- else if (ev.type === "run_end") { ended = true; await this.run("post_run", ctx, { status: ev.status, summary: ev.summary }); }
294
- },
295
- close: async () => {
296
- if (ended || ctx.runId === undefined) return;
297
- ended = true; await this.run("post_run", ctx, { status: "stopped", summary: "run aborted" });
298
- },
299
- };
300
- }
301
-
302
- /** The approval hook as an ApprovalFn for the approver chain — cli/runtime.ts buildCfg composes
303
- * execPolicyApprover(hooks.approver(human)), so this runs only for prompt-classified calls that
304
- * execpolicy did not settle (header: Authority). "allow" → "once" (dispatch never caches once),
305
- * "deny" → "deny", void → the human; no human (headless) → fail closed like execpolicy's prompt
306
- * arm. ctx is the runtime's {cwd, sessionId}: the chain is composed per config, before any run. */
307
- approver(human?: ApprovalFn): ApprovalFn {
308
- return async (req) => {
309
- const pre = await this.run("approval", this.base, { ...req, args: cloneForHook(req.args), revisedArgs: cloneForHook(req.revisedArgs) });
310
- if (pre === "allow") return "once";
311
- if (pre === "deny") return "deny";
312
- return human ? human(req) : "deny";
313
- };
314
- }
315
-
316
- private tap(ctx: HookCtx, ev: RunEvent): void {
317
- if (!this.loading && !this.has("on_event")) return; // zero cost without an on_event hook
318
- const p: Promise<void> = this.run("on_event", ctx, ev).then(() => undefined, () => undefined);
319
- this.pending.add(p);
320
- void p.then(() => { this.pending.delete(p); });
321
- }
322
-
323
- /** wait for in-flight on_event taps (each bounded by the timeout) */
324
- async settle(): Promise<void> { await Promise.all([...this.pending]); }
325
-
326
- /** fire session_close ONCE (after the load and in-flight taps settle); later calls share the promise */
327
- close(): Promise<void> {
328
- this.closing ??= (async () => {
329
- if (this.loading) await this.loading;
330
- await this.settle();
331
- await this.dispatch("session_close", [this.base]);
332
- })();
333
- return this.closing;
334
- }
335
-
336
- /** subscribe a surface (cmdRun → stderr, TUI → note); buffered notes are replayed first */
337
- onWarning(fn: (note: string) => void): void { this.listener = fn; for (const w of this.warnings) fn(w); }
338
- drainWarnings(): string[] { return this.warnings.splice(0); }
339
- private warn(note: string): void {
340
- const n = clip(note, MAX_WARNING_CHARS);
341
- this.warnings.push(n);
342
- if (this.warnings.length > MAX_WARNINGS) this.warnings.shift();
343
- this.listener?.(n);
344
- }
345
- }
346
-
347
- // ---------- result validation + helpers ----------
348
-
349
- /** Hooks get COPIES of a call's args and a tool's result: a hook that mutates its argument must not
350
- * re-aim a call policy already evaluated, rewrite the persisted tool_call, or dodge the post_tool
351
- * growth bound (which applies to the RETURNED {output} only). JSON-derived values clone; anything
352
- * structuredClone rejects (never off the wire) passes through as is rather than failing the call. */
353
- export function cloneForHook<T>(v: T): T { try { return structuredClone(v); } catch { return v; } }
354
-
355
- function normalize<K extends HookName>(name: K, raw: unknown, args: HookArgs<K>, warn: (w: string) => void): HookDecision<K> | undefined {
356
- if (raw === undefined || raw === null) return undefined;
357
- switch (name) {
358
- case "pre_tool": {
359
- const deny = isRecord(raw) ? raw["deny"] : undefined;
360
- if (typeof deny === "string" && deny.trim().length > 0) return { deny: clip(deny.trim(), MAX_DENY_REASON_CHARS) } as HookDecision<K>;
361
- break;
362
- }
363
- case "approval":
364
- if (raw === "allow" || raw === "deny") return raw as HookDecision<K>;
365
- break;
366
- case "post_tool": {
367
- if (!isRecord(raw)) break;
368
- if (raw["output"] === undefined) return undefined; // {} = leave the output alone
369
- if (typeof raw["output"] !== "string") break;
370
- const original = (args as unknown as [HookCtx, HookToolCall, ToolOutput])[2].output;
371
- return { output: boundGrowth(raw["output"], original.length) } as HookDecision<K>;
372
- }
373
- default:
374
- return undefined; // void hooks: a stray return value is not an error
375
- }
376
- warn(`${name} hook returned an invalid result (${describe(raw)}) — ignored`);
377
- return undefined;
378
- }
379
-
380
- /** a hook may replace the output outright, but may not GROW it past the original by more than the cap */
381
- function boundGrowth(text: string, originalLen: number): string {
382
- const cap = originalLen + MAX_POST_TOOL_GROWTH_CHARS;
383
- if (text.length <= cap) return text;
384
- return text.slice(0, cap) + `\n… [post_tool output truncated: hooks may add at most ${MAX_POST_TOOL_GROWTH_CHARS} chars]`;
385
- }
386
-
387
- const TIMED_OUT: unique symbol = Symbol("rovecode.hook.timeout");
388
- /** resolves TIMED_OUT after ms on a REF'D timer (Bun unrefs AbortSignal.timeout — providers/retry.ts
389
- * sleepMs / tools/webfetch.ts idiom); the original promise's later settle is ignored */
390
- function withTimeout<T>(p: Promise<T>, ms: number): Promise<T | typeof TIMED_OUT> {
391
- return new Promise((resolvePromise, rejectPromise) => {
392
- const timer = setTimeout(() => resolvePromise(TIMED_OUT), ms);
393
- (timer as unknown as { ref?: () => void }).ref?.();
394
- p.then((v) => { clearTimeout(timer); resolvePromise(v); }, (e: unknown) => { clearTimeout(timer); rejectPromise(e); });
395
- });
396
- }
397
-
398
- function isRecord(v: unknown): v is Record<string, unknown> { return typeof v === "object" && v !== null && !Array.isArray(v); }
399
- function clip(s: string, max: number): string { return s.length <= max ? s : s.slice(0, max) + "…"; }
400
- /** Bun's BuildMessage/ResolveMessage are not Error instances but carry .message */
401
- function errText(e: unknown): string {
402
- if (e instanceof Error) return e.message;
403
- if (isRecord(e) && typeof e["message"] === "string") return e["message"];
404
- return String(e);
405
- }
406
- function describe(v: unknown): string {
407
- if (typeof v === "string") return JSON.stringify(clip(v, 40));
408
- if (Array.isArray(v)) return "array";
409
- if (isRecord(v)) return `object with keys ${Object.keys(v).slice(0, 5).join(",") || "(none)"}`;
410
- return typeof v;
411
- }
@@ -1,230 +0,0 @@
1
- /** Image attachments (port #34): file → sniff → caps → ImagePart; the wire blocks both provider
2
- * adapters emit; the text stand-in for models without vision.
3
- *
4
- * Ported from opencode @ ebece6e (MIT), packages/opencode/src unless noted:
5
- * - util/media.ts:15-26 sniffAttachmentMime — the MIME comes from magic bytes, never the file
6
- * extension (png 89 50 4E 47.., jpeg FF D8 FF, gif "GIF8", webp "RIFF"…"WEBP"); bmp/pdf are
7
- * recognised there but not accepted here (neither wire protocol takes them as images);
8
- * - image/image.ts:10 MAX_BASE64_BYTES = 5 MiB (their cap is on the base64 form and they resize
9
- * past it with photon; here the cap is on the decoded file, ROVECODE_IMAGE_MAX_BYTES, and an
10
- * oversize image is an error the user fixes — never a silent re-encode);
11
- * - session/prompt.ts:66-71 SUPPORTED_MCP_RESOURCE_ATTACHMENT_MIMES — the same four types;
12
- * - session/message-v2.ts:213-217 `[Attached <mime>: <filename>]` text stand-in for stripped
13
- * media and provider/transform.ts:409-441 unsupportedParts (image → text when the model's
14
- * input modalities lack "image") — the placeholder below is rovecode's own wording.
15
- * Pattern reference only, no code copied: cline @ 8eb5f3d sdk/packages/shared/src/llms/media.ts
16
- * SUPPORTED_IMAGE_MEDIA_TYPES (:73-78), DEFAULT_MAX_IMAGE_BASE64_BYTES 5 MiB (:80),
17
- * IMAGE_UNSUPPORTED_PLACEHOLDER (:6-12: the stored history keeps the real image, only the
18
- * request is substituted); apps/vscode/src/shared/messages/content.ts:140-153 base64 source ↔
19
- * `data:<mime>;base64,<data>` URL.
20
- * Wire shapes: Anthropic `{type:"image", source:{type:"base64", media_type, data}}`; OpenAI
21
- * `{type:"image_url", image_url:{url:"data:<mime>;base64,<data>", detail}}`.
22
- * Dimensions are read from container headers only (PNG IHDR, GIF logical screen, JPEG SOFn,
23
- * WebP VP8/VP8L/VP8X) — no decoding; a header we cannot parse just leaves width/height unset. */
24
-
25
- import { readFileSync, statSync } from "node:fs";
26
- import { basename, isAbsolute } from "node:path";
27
- import type { ImageMime, ImagePart } from "./types.ts";
28
-
29
- /** decoded-file cap per image (bytes); ROVECODE_IMAGE_MAX_BYTES overrides */
30
- export const IMAGE_MAX_BYTES = 5 * 1024 * 1024;
31
- export const IMAGE_MAX_BYTES_ENV = "ROVECODE_IMAGE_MAX_BYTES";
32
- /** cap per user message — the TUI's /attach queue and the ACP prompt path both check it */
33
- export const MAX_IMAGES_PER_MESSAGE = 8;
34
- export const IMAGE_MIMES: readonly ImageMime[] = ["image/png", "image/jpeg", "image/gif", "image/webp"];
35
-
36
- export interface ImageLoadOptions {
37
- /** per-image byte cap; default ROVECODE_IMAGE_MAX_BYTES, else IMAGE_MAX_BYTES */
38
- maxBytes?: number;
39
- /** env the cap is read from (tests); default process.env */
40
- env?: Record<string, string | undefined>;
41
- /** display name; loadImageAttachment uses the file's basename */
42
- name?: string;
43
- }
44
-
45
- export type ImageLoadResult = ImagePart | { error: string };
46
-
47
- /** The effective per-image cap: a positive integer ROVECODE_IMAGE_MAX_BYTES wins, else the default. */
48
- export function imageMaxBytes(env: Record<string, string | undefined> = process.env): number {
49
- const raw = env[IMAGE_MAX_BYTES_ENV]?.trim();
50
- if (!raw) return IMAGE_MAX_BYTES;
51
- const n = Number(raw);
52
- return Number.isInteger(n) && n > 0 ? n : IMAGE_MAX_BYTES;
53
- }
54
-
55
- // ---------- sniffing + dimensions ----------
56
-
57
- const startsWith = (b: Uint8Array, at: number, prefix: number[]): boolean =>
58
- b.length >= at + prefix.length && prefix.every((v, i) => b[at + i] === v);
59
-
60
- /** MIME by magic bytes (opencode media.ts:15-26 minus bmp/pdf); undefined = not an image we accept. */
61
- export function sniffImageMime(bytes: Uint8Array): ImageMime | undefined {
62
- if (startsWith(bytes, 0, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) return "image/png";
63
- if (startsWith(bytes, 0, [0xff, 0xd8, 0xff])) return "image/jpeg";
64
- if (startsWith(bytes, 0, [0x47, 0x49, 0x46, 0x38])) return "image/gif";
65
- if (startsWith(bytes, 0, [0x52, 0x49, 0x46, 0x46]) && startsWith(bytes, 8, [0x57, 0x45, 0x42, 0x50])) return "image/webp";
66
- return undefined;
67
- }
68
-
69
- const be16 = (b: Uint8Array, i: number): number => (b[i]! << 8) | b[i + 1]!;
70
- const be32 = (b: Uint8Array, i: number): number => ((b[i]! << 24) | (b[i + 1]! << 16) | (b[i + 2]! << 8) | b[i + 3]!) >>> 0;
71
- const le16 = (b: Uint8Array, i: number): number => b[i]! | (b[i + 1]! << 8);
72
- const le24 = (b: Uint8Array, i: number): number => b[i]! | (b[i + 1]! << 8) | (b[i + 2]! << 16);
73
-
74
- /** Header-only width/height; undefined when the header is truncated or unrecognised. */
75
- export function imageDimensions(bytes: Uint8Array, mime: ImageMime): { width: number; height: number } | undefined {
76
- const dims = (width: number, height: number) => (width > 0 && height > 0 ? { width, height } : undefined);
77
- switch (mime) {
78
- case "image/png": // IHDR is always the first chunk: length(4) "IHDR"(4) width(4) height(4)
79
- return bytes.length >= 24 && startsWith(bytes, 12, [0x49, 0x48, 0x44, 0x52]) ? dims(be32(bytes, 16), be32(bytes, 20)) : undefined;
80
- case "image/gif": // logical screen descriptor right after "GIF89a"/"GIF87a"
81
- return bytes.length >= 10 ? dims(le16(bytes, 6), le16(bytes, 8)) : undefined;
82
- case "image/jpeg": return jpegDimensions(bytes);
83
- case "image/webp": return webpDimensions(bytes);
84
- }
85
- }
86
-
87
- /** Walk the marker segments to the first SOFn (C0-CF except C4 DHT, C8 JPG, CC DAC): height, width. */
88
- function jpegDimensions(b: Uint8Array): { width: number; height: number } | undefined {
89
- let i = 2;
90
- while (i + 3 < b.length) {
91
- if (b[i] !== 0xff) return undefined;
92
- const marker = b[i + 1]!;
93
- if (marker === 0xff) { i += 1; continue; } // fill byte
94
- if (marker === 0xd8 || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) { i += 2; continue; } // standalone
95
- if (marker === 0xd9 || marker === 0xda) return undefined; // EOI / SOS before any SOF
96
- if (marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc) {
97
- return i + 8 < b.length ? { height: be16(b, i + 5), width: be16(b, i + 7) } : undefined;
98
- }
99
- i += 2 + be16(b, i + 2);
100
- }
101
- return undefined;
102
- }
103
-
104
- /** First chunk after "WEBP": VP8 (lossy frame header), VP8L (lossless 14-bit fields), VP8X (extended canvas). */
105
- function webpDimensions(b: Uint8Array): { width: number; height: number } | undefined {
106
- if (b.length < 30) return undefined;
107
- const fourcc = String.fromCharCode(b[12]!, b[13]!, b[14]!, b[15]!);
108
- if (fourcc === "VP8 ") return { width: le16(b, 26) & 0x3fff, height: le16(b, 28) & 0x3fff };
109
- if (fourcc === "VP8L" && b[20] === 0x2f) {
110
- return { width: 1 + (b[21]! | ((b[22]! & 0x3f) << 8)), height: 1 + ((b[22]! >> 6) | (b[23]! << 2) | ((b[24]! & 0x0f) << 10)) };
111
- }
112
- if (fourcc === "VP8X") return { width: 1 + le24(b, 24), height: 1 + le24(b, 27) };
113
- return undefined;
114
- }
115
-
116
- // ---------- loading ----------
117
-
118
- /** `70 B`, `5121 KB` — the unit both the caps' errors and the placeholder use */
119
- const fmtBytes = (n: number): string => (n < 1024 ? `${n} B` : `${Math.ceil(n / 1024)} KB`);
120
-
121
- /** Bytes already in memory → ImagePart (base64 inline). Rejects by magic bytes and by the cap. */
122
- export function imageFromBytes(bytes: Uint8Array, opts: ImageLoadOptions = {}): ImageLoadResult {
123
- const label = opts.name ?? "image";
124
- const mime = sniffImageMime(bytes);
125
- if (!mime) {
126
- const head = [...bytes.subarray(0, 4)].map((x) => x.toString(16).padStart(2, "0")).join(" ");
127
- return { error: `${label}: not a png/jpeg/gif/webp image (magic bytes: ${head || "empty file"})` };
128
- }
129
- const max = opts.maxBytes ?? imageMaxBytes(opts.env);
130
- if (bytes.byteLength > max) return { error: `${label}: ${fmtBytes(bytes.byteLength)} is over the ${fmtBytes(max)} per-image cap (${IMAGE_MAX_BYTES_ENV})` };
131
- const part: ImagePart = { kind: "image", mime, bytes: Buffer.from(bytes).toString("base64") };
132
- const d = imageDimensions(bytes, mime);
133
- if (d) { part.width = d.width; part.height = d.height; }
134
- if (opts.name !== undefined) part.name = opts.name;
135
- return part;
136
- }
137
-
138
- /** A file on disk → ImagePart. Size is checked from stat BEFORE reading (a 2 GB "image" is
139
- * rejected without touching its bytes); the type comes from the bytes, never the extension.
140
- * Never throws: every failure is `{ error }`. */
141
- export function loadImageAttachment(filePath: string, opts: ImageLoadOptions = {}): ImageLoadResult {
142
- const name = opts.name ?? basename(filePath);
143
- let size: number;
144
- try {
145
- const st = statSync(filePath);
146
- if (!st.isFile()) return { error: `${name}: not a file` };
147
- size = st.size;
148
- } catch (e) {
149
- return { error: `${name}: cannot read (${e instanceof Error ? e.message : String(e)})` };
150
- }
151
- const max = opts.maxBytes ?? imageMaxBytes(opts.env);
152
- if (size > max) return { error: `${name}: ${fmtBytes(size)} is over the ${fmtBytes(max)} per-image cap (${IMAGE_MAX_BYTES_ENV})` };
153
- try {
154
- return imageFromBytes(readFileSync(filePath), { ...opts, maxBytes: max, name });
155
- } catch (e) {
156
- return { error: `${name}: cannot read (${e instanceof Error ? e.message : String(e)})` };
157
- }
158
- }
159
-
160
- /** Transport form (ACP image blocks, HTTP bodies): base64 + declared mime → ImagePart. The bytes
161
- * decide the type; a declared mime that disagrees is an error (cline media.ts media_type_mismatch). */
162
- export function imageFromBase64(data: string, declaredMime: string | undefined, opts: ImageLoadOptions = {}): ImageLoadResult {
163
- const bytes = Buffer.from(data, "base64");
164
- const res = imageFromBytes(bytes, opts);
165
- if ("error" in res) return res;
166
- if (declaredMime !== undefined && declaredMime !== res.mime) return { error: `${opts.name ?? "image"}: declared ${declaredMime} but the bytes are ${res.mime}` };
167
- return res;
168
- }
169
-
170
- /** Per-message image cap: the error to show when `count` images would ride on one message. */
171
- export function checkImageCount(count: number, max = MAX_IMAGES_PER_MESSAGE): string | undefined {
172
- return count > max ? `at most ${max} images per message (${count} attached)` : undefined;
173
- }
174
-
175
- // ---------- reading back (wire + display) ----------
176
-
177
- /** The base64 payload: inline bytes, else the sidecar file (absolute path — the session store
178
- * resolves relative ones on load). undefined when unreadable — callers fall back to text. */
179
- export function imageData(part: ImagePart): string | undefined {
180
- if (part.bytes !== undefined) return part.bytes;
181
- if (part.path === undefined || !isAbsolute(part.path)) return undefined;
182
- try { return readFileSync(part.path).toString("base64"); } catch { return undefined; }
183
- }
184
-
185
- /** Decoded byte size when knowable without reading a sidecar (inline: from the base64 length).
186
- * Same path rule as imageData(): only an absolute (store-hydrated) path is stat'ed — a persisted
187
- * relative path must never touch the process cwd (session.ts F2 confinement). */
188
- export function imageByteSize(part: ImagePart): number | undefined {
189
- if (part.bytes !== undefined) {
190
- const pad = part.bytes.endsWith("==") ? 2 : part.bytes.endsWith("=") ? 1 : 0;
191
- return Math.floor((part.bytes.length * 3) / 4) - pad;
192
- }
193
- if (part.path === undefined || !isAbsolute(part.path)) return undefined;
194
- try { return statSync(part.path).size; } catch { return undefined; }
195
- }
196
-
197
- export function imageExt(mime: ImageMime): "png" | "jpg" | "gif" | "webp" {
198
- return mime === "image/png" ? "png" : mime === "image/jpeg" ? "jpg" : mime === "image/gif" ? "gif" : "webp";
199
- }
200
-
201
- /** `name, WxH, N KB` — whatever is known, in that order. */
202
- export function describeImage(part: ImagePart): string {
203
- const bits = [part.name ?? part.mime];
204
- if (part.width !== undefined && part.height !== undefined) bits.push(`${part.width}x${part.height}`);
205
- const size = imageByteSize(part);
206
- if (size !== undefined) bits.push(fmtBytes(size));
207
- return bits.join(", ");
208
- }
209
-
210
- /** Text the adapters send in place of an image the model cannot see (or a sidecar that is gone). */
211
- export function imagePlaceholder(part: ImagePart, reason = "model has no vision"): string {
212
- return `[image: ${describeImage(part)} — ${reason}]`;
213
- }
214
-
215
- /** Transcript chip for a user message's attachment. */
216
- export function imageChip(part: ImagePart): string {
217
- return `[image: ${part.name ?? part.mime}]`;
218
- }
219
-
220
- /** Anthropic Messages content block; undefined when the bytes are unavailable. */
221
- export function anthropicImageBlock(part: ImagePart): Record<string, unknown> | undefined {
222
- const data = imageData(part);
223
- return data === undefined ? undefined : { type: "image", source: { type: "base64", media_type: part.mime, data } };
224
- }
225
-
226
- /** OpenAI chat-completions content part (data URL); undefined when the bytes are unavailable. */
227
- export function openaiImageBlock(part: ImagePart, detail: "auto" | "low" | "high" = "auto"): Record<string, unknown> | undefined {
228
- const data = imageData(part);
229
- return data === undefined ? undefined : { type: "image_url", image_url: { url: `data:${part.mime};base64,${data}`, detail } };
230
- }