rovecode 0.3.3 → 0.4.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (495) hide show
  1. package/CHANGELOG.md +527 -0
  2. package/README.md +46 -48
  3. package/bin/rovecode.js +24 -0
  4. package/dist/cli/app-j6gn14w3.js +2 -0
  5. package/dist/cli/ask-user-cwstt8fz.js +2 -0
  6. package/dist/cli/auth-login-9bbp9915.js +2 -0
  7. package/dist/cli/auth-m8p9grty.js +2 -0
  8. package/dist/cli/bench-16zqdms5.js +9 -0
  9. package/dist/cli/catalog-1xchffa4.js +2 -0
  10. package/dist/cli/cli-1n1zb64f.js +2 -0
  11. package/dist/cli/client-2t9gjkck.js +2 -0
  12. package/dist/cli/commands-exafvm2b.js +2 -0
  13. package/dist/cli/connect-6zde0kn3.js +2 -0
  14. package/dist/cli/context-cmd-5t43wgqt.js +2 -0
  15. package/dist/cli/context-report-kt01pw8y.js +2 -0
  16. package/dist/cli/count-remote-ap7x3vh6.js +2 -0
  17. package/dist/cli/design-ne5zszyh.js +2 -0
  18. package/dist/cli/dispatch-2r5myxye.js +2 -0
  19. package/dist/cli/doctor-ws4fh4tn.js +3 -0
  20. package/dist/cli/executor-bdrjn634.js +2 -0
  21. package/dist/cli/export-1mxb9g5p.js +2 -0
  22. package/dist/cli/files-g104xghh.js +2 -0
  23. package/dist/cli/gauntlet-07xrjpj7.js +2 -0
  24. package/dist/cli/gauntlet-runner-xvy64436.js +10 -0
  25. package/dist/cli/gauntlet-wave3-jm91yt5w.js +5 -0
  26. package/dist/cli/gauntlet-wave4-r13py7p1.js +14 -0
  27. package/dist/cli/hashline-znvrat11.js +2 -0
  28. package/dist/cli/http-xafw6fsh.js +143 -0
  29. package/dist/cli/index-1sgjm25y.js +2 -0
  30. package/dist/cli/init-g2m0tn4m.js +51 -0
  31. package/dist/cli/install-avaqjjqq.js +2 -0
  32. package/dist/cli/loop-mmpfft01.js +2 -0
  33. package/dist/cli/main-0904f6ps.js +5 -0
  34. package/dist/cli/main-0ab9fc26.js +9 -0
  35. package/dist/cli/main-0jys2ccn.js +3 -0
  36. package/dist/cli/main-0mtcdbs7.js +3 -0
  37. package/dist/cli/main-0z1w2zsg.js +3 -0
  38. package/dist/cli/main-1dchs7xv.js +18 -0
  39. package/dist/cli/main-1ereejm1.js +3 -0
  40. package/dist/cli/main-1k1kw6b5.js +3 -0
  41. package/dist/cli/main-27y4sm2k.js +38 -0
  42. package/dist/cli/main-2wwjex5j.js +58 -0
  43. package/dist/cli/main-2yeveeve.js +6 -0
  44. package/dist/cli/main-2yfck9b5.js +3 -0
  45. package/dist/cli/main-2zmzgkwh.js +3 -0
  46. package/dist/cli/main-351pz3z7.js +7 -0
  47. package/dist/cli/main-3gjqfh7a.js +6 -0
  48. package/dist/cli/main-3nf3kgve.js +3 -0
  49. package/dist/cli/main-3pjrb2hd.js +3 -0
  50. package/dist/cli/main-3rxcvgna.js +19 -0
  51. package/dist/cli/main-4b3jgy66.js +19 -0
  52. package/dist/cli/main-4wndhjdc.js +7 -0
  53. package/dist/cli/main-4xcmvxnk.js +3 -0
  54. package/dist/cli/main-5tbz0wbz.js +4 -0
  55. package/dist/cli/main-5ywnwthm.js +3 -0
  56. package/dist/cli/main-6b62vkz0.js +14 -0
  57. package/dist/cli/main-6dnk69vp.js +3 -0
  58. package/dist/cli/main-6genrmhs.js +136 -0
  59. package/dist/cli/main-73g7eff4.js +15 -0
  60. package/dist/cli/main-7c5thhjd.js +5 -0
  61. package/dist/cli/main-7rn6bqje.js +3 -0
  62. package/dist/cli/main-80haw7qk.js +4 -0
  63. package/dist/cli/main-875s60s2.js +4 -0
  64. package/dist/cli/main-8kjxbpw4.js +8 -0
  65. package/dist/cli/main-90ds1z4e.js +10 -0
  66. package/dist/cli/main-9etavkew.js +3 -0
  67. package/dist/cli/main-a9njrkk1.js +3 -0
  68. package/dist/cli/main-aecrjq2d.js +12 -0
  69. package/dist/cli/main-ck9asesq.js +9 -0
  70. package/dist/cli/main-cta9racd.js +4 -0
  71. package/dist/cli/main-ddv7j2ag.js +3 -0
  72. package/dist/cli/main-dfreez27.js +10 -0
  73. package/dist/cli/main-f7rw7des.js +3 -0
  74. package/dist/cli/main-ggcn7rd7.js +5 -0
  75. package/dist/cli/main-gzkmycnv.js +3 -0
  76. package/dist/cli/main-hq51jg8v.js +18 -0
  77. package/dist/cli/main-jft389w9.js +8 -0
  78. package/dist/cli/main-k1eqkg83.js +3 -0
  79. package/dist/cli/main-k2y8a2aw.js +9 -0
  80. package/dist/cli/main-kcpbykxz.js +4 -0
  81. package/dist/cli/main-kd488vje.js +22 -0
  82. package/dist/cli/main-kh32yvgk.js +5 -0
  83. package/dist/cli/main-kqxnqjnv.js +25 -0
  84. package/dist/cli/main-kyn0xnsg.js +3 -0
  85. package/dist/cli/main-m1kk6fp5.js +21 -0
  86. package/dist/cli/main-mv40pcr2.js +4 -0
  87. package/dist/cli/main-n0t3973w.js +3 -0
  88. package/dist/cli/main-nqveez48.js +4 -0
  89. package/dist/cli/main-pknhvrmj.js +3 -0
  90. package/dist/cli/main-pn1w7a7j.js +3 -0
  91. package/dist/cli/main-prxxs70n.js +4 -0
  92. package/dist/cli/main-q3vsesf9.js +3 -0
  93. package/dist/cli/main-qsevpgsv.js +3 -0
  94. package/dist/cli/main-rdgdw24b.js +25 -0
  95. package/dist/cli/main-rfth4tbm.js +16 -0
  96. package/dist/cli/main-rg0wn0xf.js +5 -0
  97. package/dist/cli/main-sdmxhtv8.js +4 -0
  98. package/dist/cli/main-skbp13js.js +18 -0
  99. package/dist/cli/main-t4xnd213.js +7 -0
  100. package/dist/cli/main-vqak588n.js +4 -0
  101. package/dist/cli/main-w2n1303f.js +9 -0
  102. package/dist/cli/main-wbrdspr2.js +5 -0
  103. package/dist/cli/main-wsrg79c1.js +7 -0
  104. package/dist/cli/main-x4r0fne4.js +5 -0
  105. package/dist/cli/main-xea2f3tn.js +6 -0
  106. package/dist/cli/main-xg704a3c.js +3 -0
  107. package/dist/cli/main-xvnrabfp.js +16 -0
  108. package/dist/cli/main-xy53xf0r.js +4 -0
  109. package/dist/cli/main-y1fqy60y.js +3 -0
  110. package/dist/cli/main-yn8cd281.js +34 -0
  111. package/dist/cli/main-yr0ksc0h.js +4 -0
  112. package/dist/cli/main-z2ex2vyf.js +4 -0
  113. package/dist/cli/main-z3aayzvq.js +3 -0
  114. package/dist/cli/main-zaqh35jg.js +3 -0
  115. package/dist/cli/main-zc2e8e46.js +4 -0
  116. package/dist/cli/main-zzrfw6cf.js +13 -0
  117. package/dist/cli/main.js +280 -0
  118. package/dist/cli/market-cmd-e14kmx9n.js +5 -0
  119. package/dist/cli/mcp-login-wq7ktdek.js +2 -0
  120. package/dist/cli/mcp-market-cmd-9mg3jecy.js +2 -0
  121. package/dist/cli/notify-b7qc0cjb.js +2 -0
  122. package/dist/cli/oauth-z8whcgfx.js +2 -0
  123. package/dist/cli/output-b3ewj3ps.js +16 -0
  124. package/dist/cli/profiles-6mr5he5e.js +2 -0
  125. package/dist/cli/provider-config-g7j42q8x.js +2 -0
  126. package/dist/cli/provider-jr1y8vvm.js +2 -0
  127. package/dist/cli/registry-s8yk86g0.js +2 -0
  128. package/dist/cli/registry-t6p8d4mn.js +2 -0
  129. package/dist/cli/repl-bajwe1mh.js +11 -0
  130. package/dist/cli/resume-rwn9nz7y.js +2 -0
  131. package/dist/cli/run-flags-nah7ndpt.js +2 -0
  132. package/dist/cli/runtime-n7gafzhb.js +2 -0
  133. package/dist/cli/sandbox-config-emdy18x4.js +2 -0
  134. package/dist/cli/server-b0nvs2bn.js +5 -0
  135. package/dist/cli/session-arg-y75wd4kj.js +2 -0
  136. package/dist/cli/session-j62evmjq.js +2 -0
  137. package/dist/cli/sessions-cmd-tsnwz0ns.js +7 -0
  138. package/dist/cli/settings-df10wfez.js +2 -0
  139. package/dist/cli/setup-jzvv72fg.js +2 -0
  140. package/dist/cli/sextant-smoke-37m81ke6.js +5 -0
  141. package/dist/cli/skills-cmd-gjxnxnhx.js +2 -0
  142. package/dist/cli/smoke-p7748apt.js +8 -0
  143. package/dist/cli/start-chat-s4st3mm0.js +12 -0
  144. package/dist/cli/stream-gmeyewds.js +2 -0
  145. package/dist/cli/task-gh0kkp3n.js +2 -0
  146. package/dist/cli/tasks-z1kfpe8e.js +2 -0
  147. package/dist/cli/thinking-0eqkrz6t.js +2 -0
  148. package/dist/cli/todo-5brcrt9m.js +2 -0
  149. package/dist/cli/tools-7pzm0vj9.js +2 -0
  150. package/dist/cli/tools-s635p6s8.js +2 -0
  151. package/dist/cli/trust-cmd-cjav8zgm.js +2 -0
  152. package/dist/cli/update-check-pt31bm2f.js +2 -0
  153. package/dist/cli/update-cmd-tk131s9t.js +2 -0
  154. package/dist/cli/voice-56nabd8d.js +2 -0
  155. package/dist/cli/webfetch-xd8q596m.js +2 -0
  156. package/dist/cli/websearch-5hkf98k1.js +2 -0
  157. package/dist/cli/workflow-cmd-cy3cvzjp.js +4 -0
  158. package/dist/cli/workspace-q10g5z3e.js +2 -0
  159. package/dist/lib/index.js +62 -0
  160. package/dist/lib/models-index.json +1 -0
  161. package/dist/lib/plugins.js +55 -0
  162. package/dist/lib/providers.js +17 -0
  163. package/dist/lib/public-api.js +20 -0
  164. package/dist/lib/sdk.js +360 -0
  165. package/package.json +84 -72
  166. package/bin/rovecode.ts +0 -21
  167. package/src/acp/server.ts +0 -374
  168. package/src/cli/auth-login.ts +0 -122
  169. package/src/cli/connect.ts +0 -244
  170. package/src/cli/context-cmd.ts +0 -199
  171. package/src/cli/dispatch.ts +0 -82
  172. package/src/cli/doctor.ts +0 -362
  173. package/src/cli/export.ts +0 -276
  174. package/src/cli/help.ts +0 -293
  175. package/src/cli/is-tui-invocation.ts +0 -8
  176. package/src/cli/main.ts +0 -583
  177. package/src/cli/market-cmd.ts +0 -658
  178. package/src/cli/mcp-login.ts +0 -141
  179. package/src/cli/mcp-market-cmd.ts +0 -302
  180. package/src/cli/output.ts +0 -382
  181. package/src/cli/repl.ts +0 -250
  182. package/src/cli/repomap-root.ts +0 -14
  183. package/src/cli/resume.ts +0 -57
  184. package/src/cli/run-flags.ts +0 -43
  185. package/src/cli/run-limits.ts +0 -78
  186. package/src/cli/runtime.ts +0 -931
  187. package/src/cli/session-arg.ts +0 -30
  188. package/src/cli/sessions-cmd.ts +0 -145
  189. package/src/cli/setup.ts +0 -153
  190. package/src/cli/skills-cmd.ts +0 -194
  191. package/src/cli/start-chat.ts +0 -65
  192. package/src/cli/trust-cmd.ts +0 -52
  193. package/src/coding/bash.ts +0 -148
  194. package/src/coding/checkpoints.ts +0 -327
  195. package/src/coding/diff.ts +0 -138
  196. package/src/coding/files.ts +0 -341
  197. package/src/coding/hashline.ts +0 -274
  198. package/src/coding/lsp-gate.ts +0 -254
  199. package/src/coding/lsp-servers.ts +0 -147
  200. package/src/coding/lsp.ts +0 -283
  201. package/src/coding/repomap-cache.ts +0 -99
  202. package/src/coding/repomap-files.ts +0 -192
  203. package/src/coding/repomap.ts +0 -481
  204. package/src/core/agents.ts +0 -255
  205. package/src/core/compaction.ts +0 -259
  206. package/src/core/config.ts +0 -289
  207. package/src/core/context-report.ts +0 -228
  208. package/src/core/context.ts +0 -60
  209. package/src/core/count-remote.ts +0 -107
  210. package/src/core/execpolicy-rules.ts +0 -196
  211. package/src/core/execpolicy.ts +0 -385
  212. package/src/core/executor.ts +0 -454
  213. package/src/core/guardrails.ts +0 -400
  214. package/src/core/hooks.ts +0 -411
  215. package/src/core/images.ts +0 -230
  216. package/src/core/intro.ts +0 -266
  217. package/src/core/loop.ts +0 -567
  218. package/src/core/modes.ts +0 -372
  219. package/src/core/orchestrator.ts +0 -245
  220. package/src/core/proc-group.ts +0 -48
  221. package/src/core/project-trust.ts +0 -98
  222. package/src/core/reflection.ts +0 -165
  223. package/src/core/sandbox-config.ts +0 -186
  224. package/src/core/session-id.ts +0 -24
  225. package/src/core/session-images.ts +0 -73
  226. package/src/core/session-ops.ts +0 -183
  227. package/src/core/session-text.ts +0 -29
  228. package/src/core/session.ts +0 -469
  229. package/src/core/settings.ts +0 -170
  230. package/src/core/tasks.ts +0 -646
  231. package/src/core/token-scale.ts +0 -108
  232. package/src/core/tools.ts +0 -309
  233. package/src/core/trust.ts +0 -104
  234. package/src/core/types.ts +0 -330
  235. package/src/core/update-check.ts +0 -171
  236. package/src/core/usage.ts +0 -204
  237. package/src/core/validate.ts +0 -121
  238. package/src/core/verify-gate.ts +0 -159
  239. package/src/core/verify.ts +0 -236
  240. package/src/core/voice.ts +0 -158
  241. package/src/core/win-job.ts +0 -183
  242. package/src/core/workspace.ts +0 -184
  243. package/src/design/audit.ts +0 -797
  244. package/src/design/direction.ts +0 -190
  245. package/src/design/rules.ts +0 -157
  246. package/src/eval/bench.ts +0 -150
  247. package/src/eval/gauntlet-runner.ts +0 -215
  248. package/src/eval/gauntlet-support.ts +0 -84
  249. package/src/eval/gauntlet-wave3.ts +0 -269
  250. package/src/eval/gauntlet-wave4.ts +0 -217
  251. package/src/eval/gauntlet.ts +0 -253
  252. package/src/index.ts +0 -17
  253. package/src/lanes/agy.ts +0 -95
  254. package/src/lanes/approval.ts +0 -24
  255. package/src/lanes/claude.ts +0 -129
  256. package/src/lanes/codex.ts +0 -127
  257. package/src/lanes/events.ts +0 -130
  258. package/src/lanes/job.ts +0 -142
  259. package/src/lanes/opencode.ts +0 -122
  260. package/src/lanes/process.ts +0 -184
  261. package/src/lanes/progress.ts +0 -183
  262. package/src/lanes/registry.ts +0 -178
  263. package/src/lanes/runner.ts +0 -124
  264. package/src/lanes/types.ts +0 -112
  265. package/src/market/catalogs/mcp-docs.json +0 -111
  266. package/src/market/catalogs/plugins.json +0 -111
  267. package/src/market/catalogs/skills.json +0 -478
  268. package/src/market/clone.ts +0 -72
  269. package/src/market/context-cost.ts +0 -121
  270. package/src/market/digest.ts +0 -106
  271. package/src/market/index.ts +0 -22
  272. package/src/market/install.ts +0 -578
  273. package/src/market/manifest.ts +0 -187
  274. package/src/market/prereq.ts +0 -145
  275. package/src/market/registry.ts +0 -363
  276. package/src/market/resolve.ts +0 -111
  277. package/src/market/types.ts +0 -236
  278. package/src/market/validate.ts +0 -227
  279. package/src/mcp/client.ts +0 -449
  280. package/src/mcp/config.ts +0 -252
  281. package/src/mcp/local-package.ts +0 -211
  282. package/src/mcp/market-catalog.ts +0 -84
  283. package/src/mcp/market-install.ts +0 -289
  284. package/src/mcp/market.ts +0 -362
  285. package/src/mcp/oauth.ts +0 -251
  286. package/src/mcp/prompts-resources.ts +0 -249
  287. package/src/mcp/shared.ts +0 -149
  288. package/src/mcp/status.ts +0 -67
  289. package/src/mcp/tools.ts +0 -275
  290. package/src/mcp/transport.ts +0 -122
  291. package/src/mcp/trust.ts +0 -25
  292. package/src/memory/blocks.ts +0 -278
  293. package/src/memory/recall.ts +0 -355
  294. package/src/memory/scope.ts +0 -182
  295. package/src/memory/store.ts +0 -105
  296. package/src/memory/tools.ts +0 -99
  297. package/src/plugins/cli.ts +0 -119
  298. package/src/plugins/discover.ts +0 -108
  299. package/src/plugins/index.ts +0 -50
  300. package/src/plugins/install.ts +0 -184
  301. package/src/plugins/load.ts +0 -124
  302. package/src/plugins/manifest.ts +0 -92
  303. package/src/plugins/state.ts +0 -83
  304. package/src/providers/auth.ts +0 -408
  305. package/src/providers/cache.ts +0 -223
  306. package/src/providers/catalog-local.ts +0 -160
  307. package/src/providers/catalog.ts +0 -421
  308. package/src/providers/middleware-context.ts +0 -86
  309. package/src/providers/middleware.ts +0 -373
  310. package/src/providers/model-list.ts +0 -23
  311. package/src/providers/oauth/common.ts +0 -105
  312. package/src/providers/oauth/device-code.ts +0 -107
  313. package/src/providers/oauth/github-copilot.ts +0 -146
  314. package/src/providers/oauth/loopback.ts +0 -158
  315. package/src/providers/oauth/openai.ts +0 -163
  316. package/src/providers/oauth/openrouter.ts +0 -89
  317. package/src/providers/oauth/pkce.ts +0 -45
  318. package/src/providers/oauth/registry.ts +0 -39
  319. package/src/providers/oauth/seam.ts +0 -89
  320. package/src/providers/profile-glm53.ts +0 -111
  321. package/src/providers/profile-sonnet5-persona.ts +0 -65
  322. package/src/providers/profile-sonnet5-voice.ts +0 -23
  323. package/src/providers/profiles.ts +0 -156
  324. package/src/providers/provider-config.ts +0 -311
  325. package/src/providers/registry.ts +0 -333
  326. package/src/providers/responses.ts +0 -209
  327. package/src/providers/retry.ts +0 -234
  328. package/src/providers/router.ts +0 -294
  329. package/src/providers/sse.ts +0 -26
  330. package/src/providers/stream-errors.ts +0 -117
  331. package/src/providers/stream.ts +0 -566
  332. package/src/providers/thinking.ts +0 -189
  333. package/src/providers/wire-messages.ts +0 -129
  334. package/src/providers/wire-responses.ts +0 -79
  335. package/src/providers/wire-select.ts +0 -53
  336. package/src/server/http.ts +0 -291
  337. package/src/server/openapi.ts +0 -246
  338. package/src/sextant/card-hits.ts +0 -102
  339. package/src/sextant/card-keys.ts +0 -55
  340. package/src/sextant/context-source.ts +0 -157
  341. package/src/sextant/crew-cards.ts +0 -350
  342. package/src/sextant/draw-agents.ts +0 -273
  343. package/src/sextant/draw-code.ts +0 -388
  344. package/src/sextant/draw-context.ts +0 -222
  345. package/src/sextant/draw-frame.ts +0 -164
  346. package/src/sextant/draw-market.ts +0 -573
  347. package/src/sextant/draw-messages.ts +0 -386
  348. package/src/sextant/draw-pet.ts +0 -230
  349. package/src/sextant/draw-plan.ts +0 -187
  350. package/src/sextant/draw-tabs.ts +0 -85
  351. package/src/sextant/draw-util.ts +0 -65
  352. package/src/sextant/draw-wizard.ts +0 -378
  353. package/src/sextant/engine.ts +0 -230
  354. package/src/sextant/frame-hits.ts +0 -25
  355. package/src/sextant/frame.ts +0 -101
  356. package/src/sextant/git-status.ts +0 -197
  357. package/src/sextant/grid.ts +0 -59
  358. package/src/sextant/input.ts +0 -119
  359. package/src/sextant/keys.ts +0 -521
  360. package/src/sextant/layout.ts +0 -86
  361. package/src/sextant/local-commands.ts +0 -169
  362. package/src/sextant/market-source.ts +0 -287
  363. package/src/sextant/mentions.ts +0 -200
  364. package/src/sextant/message-hits.ts +0 -26
  365. package/src/sextant/model.ts +0 -387
  366. package/src/sextant/overlays.ts +0 -456
  367. package/src/sextant/panel-hits.ts +0 -38
  368. package/src/sextant/pet.ts +0 -399
  369. package/src/sextant/screen.ts +0 -324
  370. package/src/sextant/scroll-hits.ts +0 -66
  371. package/src/sextant/scrollbar.ts +0 -82
  372. package/src/sextant/sextant-bridge.ts +0 -174
  373. package/src/sextant/sextant-cards.ts +0 -142
  374. package/src/sextant/sextant-diff-base.ts +0 -63
  375. package/src/sextant/sextant-files.ts +0 -154
  376. package/src/sextant/sextant-frame-loop.ts +0 -335
  377. package/src/sextant/sextant-renderer.ts +0 -574
  378. package/src/sextant/sextant-repo.ts +0 -140
  379. package/src/sextant/theme.ts +0 -66
  380. package/src/sextant/tool-rows.ts +0 -189
  381. package/src/sextant/types.ts +0 -493
  382. package/src/skills/index.ts +0 -387
  383. package/src/skills/pack.ts +0 -220
  384. package/src/skills/spec.ts +0 -162
  385. package/src/skills/tools.ts +0 -69
  386. package/src/skills/versioned.ts +0 -227
  387. package/src/telemetry/otel-export.ts +0 -122
  388. package/src/telemetry/otel-lanes.ts +0 -89
  389. package/src/telemetry/otel-logs.ts +0 -131
  390. package/src/telemetry/otel-metrics.ts +0 -136
  391. package/src/telemetry/otel.ts +0 -397
  392. package/src/telemetry/otlp.ts +0 -76
  393. package/src/tools/ask-user.ts +0 -156
  394. package/src/tools/bash-bg.ts +0 -94
  395. package/src/tools/bash-jobs.ts +0 -237
  396. package/src/tools/design.ts +0 -151
  397. package/src/tools/evalcell.ts +0 -338
  398. package/src/tools/html-text.ts +0 -139
  399. package/src/tools/provider.ts +0 -149
  400. package/src/tools/task.ts +0 -250
  401. package/src/tools/todo.ts +0 -320
  402. package/src/tools/webfetch.ts +0 -332
  403. package/src/tools/websearch.ts +0 -359
  404. package/src/tui/agents-cmd.ts +0 -41
  405. package/src/tui/app.ts +0 -749
  406. package/src/tui/attach.ts +0 -127
  407. package/src/tui/boot-notes.ts +0 -41
  408. package/src/tui/builtin-prompts.ts +0 -59
  409. package/src/tui/checkpoints-cmd.ts +0 -70
  410. package/src/tui/clipboard-image.ts +0 -81
  411. package/src/tui/clipboard.ts +0 -78
  412. package/src/tui/commands.ts +0 -283
  413. package/src/tui/config-view.ts +0 -53
  414. package/src/tui/context-cmds.ts +0 -282
  415. package/src/tui/cost.ts +0 -108
  416. package/src/tui/crash-guard.ts +0 -173
  417. package/src/tui/focus-terminal.ts +0 -34
  418. package/src/tui/git-cmds.ts +0 -273
  419. package/src/tui/git-plain.ts +0 -58
  420. package/src/tui/info-cmd.ts +0 -150
  421. package/src/tui/input-plain.ts +0 -76
  422. package/src/tui/mcp-cmd.ts +0 -128
  423. package/src/tui/memory-note.ts +0 -77
  424. package/src/tui/modes-cmd.ts +0 -45
  425. package/src/tui/notify-seq.ts +0 -100
  426. package/src/tui/notify.ts +0 -318
  427. package/src/tui/overlays.ts +0 -97
  428. package/src/tui/pi-renderer.ts +0 -428
  429. package/src/tui/providers-cmd.ts +0 -377
  430. package/src/tui/reasoning-view.ts +0 -56
  431. package/src/tui/renderer.ts +0 -128
  432. package/src/tui/replay-marker.ts +0 -29
  433. package/src/tui/session-cmd.ts +0 -148
  434. package/src/tui/session-manage.ts +0 -95
  435. package/src/tui/sextant-attach.ts +0 -102
  436. package/src/tui/sextant-io.ts +0 -202
  437. package/src/tui/sextant-smoke.ts +0 -110
  438. package/src/tui/shell-cmd.ts +0 -158
  439. package/src/tui/smoke.ts +0 -72
  440. package/src/tui/staged-terminal.ts +0 -50
  441. package/src/tui/startup.ts +0 -12
  442. package/src/tui/theme.ts +0 -59
  443. package/src/tui/todo-label.ts +0 -7
  444. package/src/tui/trust-card.ts +0 -107
  445. package/src/tui/tui-commands.ts +0 -87
  446. package/tsconfig.json +0 -30
  447. package/vendor/pi-tui/LICENSE +0 -21
  448. package/vendor/pi-tui/PATCHES.md +0 -12
  449. package/vendor/pi-tui/PROVENANCE.md +0 -12
  450. package/vendor/pi-tui/README.upstream.md +0 -854
  451. package/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  452. package/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  453. package/vendor/pi-tui/src/alt-screen-search.ts +0 -158
  454. package/vendor/pi-tui/src/autocomplete.ts +0 -827
  455. package/vendor/pi-tui/src/components/alt-screen-flash.ts +0 -52
  456. package/vendor/pi-tui/src/components/box.ts +0 -138
  457. package/vendor/pi-tui/src/components/cancellable-loader.ts +0 -41
  458. package/vendor/pi-tui/src/components/editor.ts +0 -2364
  459. package/vendor/pi-tui/src/components/h-stack.ts +0 -45
  460. package/vendor/pi-tui/src/components/image.ts +0 -128
  461. package/vendor/pi-tui/src/components/input.ts +0 -448
  462. package/vendor/pi-tui/src/components/loader.ts +0 -93
  463. package/vendor/pi-tui/src/components/markdown.ts +0 -1016
  464. package/vendor/pi-tui/src/components/scroll-view.ts +0 -217
  465. package/vendor/pi-tui/src/components/select-list.ts +0 -230
  466. package/vendor/pi-tui/src/components/settings-list.ts +0 -277
  467. package/vendor/pi-tui/src/components/spacer.ts +0 -29
  468. package/vendor/pi-tui/src/components/stack.ts +0 -155
  469. package/vendor/pi-tui/src/components/text.ts +0 -108
  470. package/vendor/pi-tui/src/components/truncated-text.ts +0 -66
  471. package/vendor/pi-tui/src/components/v-stack.ts +0 -34
  472. package/vendor/pi-tui/src/editor-component.ts +0 -75
  473. package/vendor/pi-tui/src/fuzzy.ts +0 -138
  474. package/vendor/pi-tui/src/index.ts +0 -149
  475. package/vendor/pi-tui/src/keybindings.ts +0 -321
  476. package/vendor/pi-tui/src/keys.ts +0 -1402
  477. package/vendor/pi-tui/src/kill-ring.ts +0 -47
  478. package/vendor/pi-tui/src/latex.ts +0 -1381
  479. package/vendor/pi-tui/src/layout-node.ts +0 -52
  480. package/vendor/pi-tui/src/layout.ts +0 -411
  481. package/vendor/pi-tui/src/native-modifiers.ts +0 -60
  482. package/vendor/pi-tui/src/native-module-path.ts +0 -32
  483. package/vendor/pi-tui/src/stdin-buffer.ts +0 -445
  484. package/vendor/pi-tui/src/terminal-colors.ts +0 -74
  485. package/vendor/pi-tui/src/terminal-image.ts +0 -701
  486. package/vendor/pi-tui/src/terminal.ts +0 -554
  487. package/vendor/pi-tui/src/tui-alt-screen.ts +0 -1379
  488. package/vendor/pi-tui/src/tui-main-screen.ts +0 -655
  489. package/vendor/pi-tui/src/tui.ts +0 -1264
  490. package/vendor/pi-tui/src/undo-stack.ts +0 -29
  491. package/vendor/pi-tui/src/utils.ts +0 -1327
  492. package/vendor/pi-tui/src/word-navigation.ts +0 -118
  493. package/vendor/pi-tui/test/test-themes.ts +0 -39
  494. package/vendor/pi-tui/test/virtual-terminal.ts +0 -219
  495. /package/{src/providers → dist/cli}/models-index.json +0 -0
package/src/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
- }