rovecode 0.4.0-beta.3 → 0.4.0

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 (432) hide show
  1. package/README.md +57 -72
  2. package/THIRD_PARTY_NOTICES.md +0 -44
  3. package/bin/rovecode.ts +21 -0
  4. package/package.json +16 -38
  5. package/src/account/keys.ts +97 -0
  6. package/src/account/login.ts +158 -0
  7. package/src/account/provision.ts +47 -0
  8. package/src/account/store.ts +63 -0
  9. package/src/acp/server.ts +373 -0
  10. package/src/cli/account-cmd.ts +116 -0
  11. package/src/cli/connect.ts +244 -0
  12. package/src/cli/context-cmd.ts +199 -0
  13. package/src/cli/dispatch.ts +109 -0
  14. package/src/cli/doctor.ts +324 -0
  15. package/src/cli/export.ts +278 -0
  16. package/src/cli/help.ts +240 -0
  17. package/src/cli/is-tui-invocation.ts +8 -0
  18. package/src/cli/main.ts +599 -0
  19. package/src/cli/market-cmd.ts +658 -0
  20. package/src/cli/mcp-market-cmd.ts +299 -0
  21. package/src/cli/output.ts +382 -0
  22. package/src/cli/repl.ts +172 -0
  23. package/src/cli/resume.ts +32 -0
  24. package/src/cli/run-limits.ts +78 -0
  25. package/src/cli/runtime.ts +792 -0
  26. package/src/cli/setup.ts +187 -0
  27. package/src/cli/update-cmd.ts +78 -0
  28. package/src/cli/workflow-cmd.ts +100 -0
  29. package/src/coding/checkpoints.ts +270 -0
  30. package/src/coding/diff.ts +136 -0
  31. package/src/coding/files.ts +339 -0
  32. package/src/coding/hashline.ts +319 -0
  33. package/src/coding/lsp.ts +406 -0
  34. package/src/coding/repomap-cache.ts +99 -0
  35. package/src/coding/repomap-files.ts +110 -0
  36. package/src/coding/repomap.ts +392 -0
  37. package/src/core/compaction.ts +399 -0
  38. package/src/core/config.ts +289 -0
  39. package/src/core/context-report.ts +228 -0
  40. package/src/core/context.ts +60 -0
  41. package/src/core/count-remote.ts +107 -0
  42. package/src/core/execpolicy-rules.ts +196 -0
  43. package/src/core/execpolicy.ts +385 -0
  44. package/src/core/executor.ts +397 -0
  45. package/src/core/guardrails.ts +400 -0
  46. package/src/core/hooks.ts +398 -0
  47. package/src/core/images.ts +230 -0
  48. package/src/core/intro.ts +236 -0
  49. package/src/core/loop.ts +621 -0
  50. package/src/core/modes.ts +372 -0
  51. package/src/core/orchestrator.ts +207 -0
  52. package/src/core/reflection.ts +165 -0
  53. package/src/core/sandbox-config.ts +167 -0
  54. package/src/core/session-images.ts +73 -0
  55. package/src/core/session.ts +398 -0
  56. package/src/core/settings.ts +98 -0
  57. package/src/core/stuck-detector.ts +273 -0
  58. package/src/core/tasks.ts +374 -0
  59. package/src/core/token-scale.ts +108 -0
  60. package/src/core/tool-output-budget.ts +166 -0
  61. package/src/core/tools.ts +288 -0
  62. package/src/core/types.ts +330 -0
  63. package/src/core/update-check.ts +171 -0
  64. package/src/core/update.ts +158 -0
  65. package/src/core/usage.ts +204 -0
  66. package/src/core/validate.ts +121 -0
  67. package/src/core/verify-gate.ts +159 -0
  68. package/src/core/verify.ts +237 -0
  69. package/src/core/voice.ts +158 -0
  70. package/src/core/win-job.ts +183 -0
  71. package/src/design/audit.ts +797 -0
  72. package/src/design/direction.ts +190 -0
  73. package/src/design/rules.ts +157 -0
  74. package/src/eval/bench.ts +150 -0
  75. package/src/eval/gauntlet-runner.ts +218 -0
  76. package/src/eval/gauntlet.ts +226 -0
  77. package/src/eval/grader.ts +186 -0
  78. package/src/eval/record.ts +202 -0
  79. package/src/eval/redact.ts +141 -0
  80. package/src/eval/replay.ts +147 -0
  81. package/src/eval/trajectory.ts +373 -0
  82. package/src/index.ts +17 -0
  83. package/src/market/catalogs/mcp-docs.json +111 -0
  84. package/src/market/catalogs/plugins.json +111 -0
  85. package/src/market/catalogs/skills.json +478 -0
  86. package/src/market/clone.ts +72 -0
  87. package/src/market/context-cost.ts +121 -0
  88. package/src/market/digest.ts +106 -0
  89. package/src/market/index.ts +22 -0
  90. package/src/market/install.ts +578 -0
  91. package/src/market/manifest.ts +187 -0
  92. package/src/market/prereq.ts +145 -0
  93. package/src/market/registry.ts +363 -0
  94. package/src/market/resolve.ts +111 -0
  95. package/src/market/types.ts +236 -0
  96. package/src/market/validate.ts +227 -0
  97. package/src/mcp/client.ts +431 -0
  98. package/src/mcp/config.ts +239 -0
  99. package/src/mcp/local-package.ts +211 -0
  100. package/src/mcp/market-catalog.ts +84 -0
  101. package/src/mcp/market-install.ts +289 -0
  102. package/src/mcp/market.ts +0 -0
  103. package/src/mcp/tools.ts +131 -0
  104. package/src/mcp/trust.ts +49 -0
  105. package/src/memory/blocks.ts +175 -0
  106. package/src/memory/recall.ts +355 -0
  107. package/src/memory/store.ts +105 -0
  108. package/src/memory/tools.ts +99 -0
  109. package/src/plugins/cli.ts +123 -0
  110. package/src/plugins/discover.ts +108 -0
  111. package/src/plugins/index.ts +50 -0
  112. package/src/plugins/init.ts +140 -0
  113. package/src/plugins/install.ts +184 -0
  114. package/src/plugins/load.ts +149 -0
  115. package/src/plugins/manifest.ts +106 -0
  116. package/src/plugins/state.ts +83 -0
  117. package/src/providers/auth.ts +293 -0
  118. package/src/providers/cache.ts +223 -0
  119. package/src/providers/catalog-local.ts +160 -0
  120. package/src/providers/catalog.ts +408 -0
  121. package/src/providers/middleware-context.ts +86 -0
  122. package/src/providers/middleware.ts +373 -0
  123. package/src/providers/profile-glm53.ts +111 -0
  124. package/src/providers/profile-sonnet5-persona.ts +65 -0
  125. package/src/providers/profile-sonnet5-voice.ts +23 -0
  126. package/src/providers/profiles.ts +156 -0
  127. package/src/providers/provider-config.ts +311 -0
  128. package/src/providers/registry.ts +302 -0
  129. package/src/providers/response-validation.ts +80 -0
  130. package/src/providers/retry.ts +234 -0
  131. package/src/providers/router.ts +294 -0
  132. package/src/providers/sse.ts +26 -0
  133. package/src/providers/stream-errors.ts +117 -0
  134. package/src/providers/stream.ts +569 -0
  135. package/src/providers/thinking.ts +189 -0
  136. package/src/providers/wire-messages.ts +129 -0
  137. package/src/sdk/client.ts +225 -0
  138. package/src/sdk/index.ts +3 -0
  139. package/src/server/dashboard.ts +144 -0
  140. package/src/server/http.ts +343 -0
  141. package/src/server/openapi.ts +246 -0
  142. package/src/sextant/card-hits.ts +102 -0
  143. package/src/sextant/card-keys.ts +55 -0
  144. package/src/sextant/context-source.ts +157 -0
  145. package/src/sextant/draw-agents.ts +273 -0
  146. package/src/sextant/draw-code.ts +388 -0
  147. package/src/sextant/draw-context.ts +222 -0
  148. package/src/sextant/draw-frame.ts +164 -0
  149. package/src/sextant/draw-market.ts +573 -0
  150. package/src/sextant/draw-messages.ts +386 -0
  151. package/src/sextant/draw-pet.ts +230 -0
  152. package/src/sextant/draw-plan.ts +159 -0
  153. package/src/sextant/draw-tabs.ts +85 -0
  154. package/src/sextant/draw-util.ts +65 -0
  155. package/src/sextant/engine.ts +230 -0
  156. package/src/sextant/frame-hits.ts +25 -0
  157. package/src/sextant/frame.ts +101 -0
  158. package/src/sextant/git-status.ts +197 -0
  159. package/src/sextant/grid.ts +59 -0
  160. package/src/sextant/input.ts +119 -0
  161. package/src/sextant/keys.ts +488 -0
  162. package/src/sextant/layout.ts +86 -0
  163. package/src/sextant/local-commands.ts +156 -0
  164. package/src/sextant/market-source.ts +287 -0
  165. package/src/sextant/mentions.ts +141 -0
  166. package/src/sextant/message-hits.ts +26 -0
  167. package/src/sextant/model.ts +387 -0
  168. package/src/sextant/overlays.ts +451 -0
  169. package/src/sextant/panel-hits.ts +38 -0
  170. package/src/sextant/pet.ts +399 -0
  171. package/src/sextant/screen.ts +324 -0
  172. package/src/sextant/scroll-hits.ts +66 -0
  173. package/src/sextant/scrollbar.ts +82 -0
  174. package/src/sextant/selection.ts +123 -0
  175. package/src/sextant/sextant-bridge.ts +174 -0
  176. package/src/sextant/sextant-cards.ts +142 -0
  177. package/src/sextant/sextant-diff-base.ts +63 -0
  178. package/src/sextant/sextant-files.ts +154 -0
  179. package/src/sextant/sextant-frame-loop.ts +314 -0
  180. package/src/sextant/sextant-renderer.ts +478 -0
  181. package/src/sextant/sextant-repo.ts +131 -0
  182. package/src/sextant/theme.ts +66 -0
  183. package/src/sextant/tool-rows.ts +189 -0
  184. package/src/sextant/types.ts +473 -0
  185. package/src/skills/index.ts +306 -0
  186. package/src/skills/tools.ts +69 -0
  187. package/src/skills/versioned.ts +227 -0
  188. package/src/telemetry/otel.ts +353 -0
  189. package/src/telemetry/otlp.ts +68 -0
  190. package/src/tools/ask-user.ts +156 -0
  191. package/src/tools/design.ts +151 -0
  192. package/src/tools/evalcell.ts +338 -0
  193. package/src/tools/html-text.ts +139 -0
  194. package/src/tools/provider.ts +149 -0
  195. package/src/tools/task.ts +216 -0
  196. package/src/tools/todo.ts +320 -0
  197. package/src/tools/webfetch.ts +331 -0
  198. package/src/tui/app.ts +608 -0
  199. package/src/tui/attach.ts +127 -0
  200. package/src/tui/checkpoints-cmd.ts +70 -0
  201. package/src/tui/clipboard-image.ts +81 -0
  202. package/src/tui/commands.ts +277 -0
  203. package/src/tui/cost.ts +108 -0
  204. package/src/tui/info-cmd.ts +144 -0
  205. package/src/tui/mcp-cmd.ts +128 -0
  206. package/src/tui/modes-cmd.ts +45 -0
  207. package/src/tui/overlays.ts +97 -0
  208. package/src/tui/pi-renderer.ts +424 -0
  209. package/src/tui/providers-cmd.ts +366 -0
  210. package/src/tui/renderer.ts +101 -0
  211. package/src/tui/replay-marker.ts +29 -0
  212. package/src/tui/session-cmd.ts +146 -0
  213. package/src/tui/sextant-attach.ts +68 -0
  214. package/src/tui/sextant-io.ts +184 -0
  215. package/src/tui/sextant-smoke.ts +110 -0
  216. package/src/tui/smoke.ts +72 -0
  217. package/src/tui/theme.ts +59 -0
  218. package/src/tui/todo-label.ts +7 -0
  219. package/src/workflow/engine.ts +266 -0
  220. package/tsconfig.json +30 -0
  221. package/vendor/pi-tui/LICENSE +21 -0
  222. package/vendor/pi-tui/PATCHES.md +12 -0
  223. package/vendor/pi-tui/PROVENANCE.md +12 -0
  224. package/vendor/pi-tui/README.upstream.md +854 -0
  225. package/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  226. package/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  227. package/vendor/pi-tui/src/alt-screen-search.ts +158 -0
  228. package/vendor/pi-tui/src/autocomplete.ts +827 -0
  229. package/vendor/pi-tui/src/components/alt-screen-flash.ts +52 -0
  230. package/vendor/pi-tui/src/components/box.ts +138 -0
  231. package/vendor/pi-tui/src/components/cancellable-loader.ts +41 -0
  232. package/vendor/pi-tui/src/components/editor.ts +2364 -0
  233. package/vendor/pi-tui/src/components/h-stack.ts +45 -0
  234. package/vendor/pi-tui/src/components/image.ts +128 -0
  235. package/vendor/pi-tui/src/components/input.ts +448 -0
  236. package/vendor/pi-tui/src/components/loader.ts +93 -0
  237. package/vendor/pi-tui/src/components/markdown.ts +1016 -0
  238. package/vendor/pi-tui/src/components/scroll-view.ts +217 -0
  239. package/vendor/pi-tui/src/components/select-list.ts +230 -0
  240. package/vendor/pi-tui/src/components/settings-list.ts +277 -0
  241. package/vendor/pi-tui/src/components/spacer.ts +29 -0
  242. package/vendor/pi-tui/src/components/stack.ts +155 -0
  243. package/vendor/pi-tui/src/components/text.ts +108 -0
  244. package/vendor/pi-tui/src/components/truncated-text.ts +66 -0
  245. package/vendor/pi-tui/src/components/v-stack.ts +34 -0
  246. package/vendor/pi-tui/src/editor-component.ts +75 -0
  247. package/vendor/pi-tui/src/fuzzy.ts +138 -0
  248. package/vendor/pi-tui/src/index.ts +149 -0
  249. package/vendor/pi-tui/src/keybindings.ts +321 -0
  250. package/vendor/pi-tui/src/keys.ts +1402 -0
  251. package/vendor/pi-tui/src/kill-ring.ts +47 -0
  252. package/vendor/pi-tui/src/latex.ts +1381 -0
  253. package/vendor/pi-tui/src/layout-node.ts +52 -0
  254. package/vendor/pi-tui/src/layout.ts +411 -0
  255. package/vendor/pi-tui/src/native-modifiers.ts +60 -0
  256. package/vendor/pi-tui/src/native-module-path.ts +32 -0
  257. package/vendor/pi-tui/src/stdin-buffer.ts +445 -0
  258. package/vendor/pi-tui/src/terminal-colors.ts +74 -0
  259. package/vendor/pi-tui/src/terminal-image.ts +701 -0
  260. package/vendor/pi-tui/src/terminal.ts +554 -0
  261. package/vendor/pi-tui/src/tui-alt-screen.ts +1379 -0
  262. package/vendor/pi-tui/src/tui-main-screen.ts +655 -0
  263. package/vendor/pi-tui/src/tui.ts +1264 -0
  264. package/vendor/pi-tui/src/undo-stack.ts +29 -0
  265. package/vendor/pi-tui/src/utils.ts +1327 -0
  266. package/vendor/pi-tui/src/word-navigation.ts +118 -0
  267. package/vendor/pi-tui/test/test-themes.ts +39 -0
  268. package/vendor/pi-tui/test/virtual-terminal.ts +219 -0
  269. package/CHANGELOG.md +0 -527
  270. package/bin/rovecode.js +0 -24
  271. package/dist/cli/app-j6gn14w3.js +0 -2
  272. package/dist/cli/ask-user-cwstt8fz.js +0 -2
  273. package/dist/cli/auth-login-9bbp9915.js +0 -2
  274. package/dist/cli/auth-m8p9grty.js +0 -2
  275. package/dist/cli/bench-16zqdms5.js +0 -9
  276. package/dist/cli/catalog-1xchffa4.js +0 -2
  277. package/dist/cli/cli-1n1zb64f.js +0 -2
  278. package/dist/cli/client-2t9gjkck.js +0 -2
  279. package/dist/cli/commands-exafvm2b.js +0 -2
  280. package/dist/cli/connect-6zde0kn3.js +0 -2
  281. package/dist/cli/context-cmd-5t43wgqt.js +0 -2
  282. package/dist/cli/context-report-kt01pw8y.js +0 -2
  283. package/dist/cli/count-remote-ap7x3vh6.js +0 -2
  284. package/dist/cli/design-ne5zszyh.js +0 -2
  285. package/dist/cli/dispatch-2r5myxye.js +0 -2
  286. package/dist/cli/doctor-ws4fh4tn.js +0 -3
  287. package/dist/cli/executor-bdrjn634.js +0 -2
  288. package/dist/cli/export-1mxb9g5p.js +0 -2
  289. package/dist/cli/files-g104xghh.js +0 -2
  290. package/dist/cli/gauntlet-07xrjpj7.js +0 -2
  291. package/dist/cli/gauntlet-runner-xvy64436.js +0 -10
  292. package/dist/cli/gauntlet-wave3-jm91yt5w.js +0 -5
  293. package/dist/cli/gauntlet-wave4-r13py7p1.js +0 -14
  294. package/dist/cli/hashline-znvrat11.js +0 -2
  295. package/dist/cli/http-xafw6fsh.js +0 -143
  296. package/dist/cli/index-1sgjm25y.js +0 -2
  297. package/dist/cli/init-g2m0tn4m.js +0 -51
  298. package/dist/cli/install-avaqjjqq.js +0 -2
  299. package/dist/cli/loop-mmpfft01.js +0 -2
  300. package/dist/cli/main-0904f6ps.js +0 -5
  301. package/dist/cli/main-0ab9fc26.js +0 -9
  302. package/dist/cli/main-0jys2ccn.js +0 -3
  303. package/dist/cli/main-0mtcdbs7.js +0 -3
  304. package/dist/cli/main-0z1w2zsg.js +0 -3
  305. package/dist/cli/main-1dchs7xv.js +0 -18
  306. package/dist/cli/main-1ereejm1.js +0 -3
  307. package/dist/cli/main-1k1kw6b5.js +0 -3
  308. package/dist/cli/main-27y4sm2k.js +0 -38
  309. package/dist/cli/main-2wwjex5j.js +0 -58
  310. package/dist/cli/main-2yeveeve.js +0 -6
  311. package/dist/cli/main-2yfck9b5.js +0 -3
  312. package/dist/cli/main-2zmzgkwh.js +0 -3
  313. package/dist/cli/main-351pz3z7.js +0 -7
  314. package/dist/cli/main-3gjqfh7a.js +0 -6
  315. package/dist/cli/main-3nf3kgve.js +0 -3
  316. package/dist/cli/main-3pjrb2hd.js +0 -3
  317. package/dist/cli/main-3rxcvgna.js +0 -19
  318. package/dist/cli/main-4b3jgy66.js +0 -19
  319. package/dist/cli/main-4wndhjdc.js +0 -7
  320. package/dist/cli/main-4xcmvxnk.js +0 -3
  321. package/dist/cli/main-5tbz0wbz.js +0 -4
  322. package/dist/cli/main-5ywnwthm.js +0 -3
  323. package/dist/cli/main-6b62vkz0.js +0 -14
  324. package/dist/cli/main-6dnk69vp.js +0 -3
  325. package/dist/cli/main-6genrmhs.js +0 -136
  326. package/dist/cli/main-73g7eff4.js +0 -15
  327. package/dist/cli/main-7c5thhjd.js +0 -5
  328. package/dist/cli/main-7rn6bqje.js +0 -3
  329. package/dist/cli/main-80haw7qk.js +0 -4
  330. package/dist/cli/main-875s60s2.js +0 -4
  331. package/dist/cli/main-8kjxbpw4.js +0 -8
  332. package/dist/cli/main-90ds1z4e.js +0 -10
  333. package/dist/cli/main-9etavkew.js +0 -3
  334. package/dist/cli/main-a9njrkk1.js +0 -3
  335. package/dist/cli/main-aecrjq2d.js +0 -12
  336. package/dist/cli/main-ck9asesq.js +0 -9
  337. package/dist/cli/main-cta9racd.js +0 -4
  338. package/dist/cli/main-ddv7j2ag.js +0 -3
  339. package/dist/cli/main-dfreez27.js +0 -10
  340. package/dist/cli/main-f7rw7des.js +0 -3
  341. package/dist/cli/main-ggcn7rd7.js +0 -5
  342. package/dist/cli/main-gzkmycnv.js +0 -3
  343. package/dist/cli/main-hq51jg8v.js +0 -18
  344. package/dist/cli/main-jft389w9.js +0 -8
  345. package/dist/cli/main-k1eqkg83.js +0 -3
  346. package/dist/cli/main-k2y8a2aw.js +0 -9
  347. package/dist/cli/main-kcpbykxz.js +0 -4
  348. package/dist/cli/main-kd488vje.js +0 -22
  349. package/dist/cli/main-kh32yvgk.js +0 -5
  350. package/dist/cli/main-kqxnqjnv.js +0 -25
  351. package/dist/cli/main-kyn0xnsg.js +0 -3
  352. package/dist/cli/main-m1kk6fp5.js +0 -21
  353. package/dist/cli/main-mv40pcr2.js +0 -4
  354. package/dist/cli/main-n0t3973w.js +0 -3
  355. package/dist/cli/main-nqveez48.js +0 -4
  356. package/dist/cli/main-pknhvrmj.js +0 -3
  357. package/dist/cli/main-pn1w7a7j.js +0 -3
  358. package/dist/cli/main-prxxs70n.js +0 -4
  359. package/dist/cli/main-q3vsesf9.js +0 -3
  360. package/dist/cli/main-qsevpgsv.js +0 -3
  361. package/dist/cli/main-rdgdw24b.js +0 -25
  362. package/dist/cli/main-rfth4tbm.js +0 -16
  363. package/dist/cli/main-rg0wn0xf.js +0 -5
  364. package/dist/cli/main-sdmxhtv8.js +0 -4
  365. package/dist/cli/main-skbp13js.js +0 -18
  366. package/dist/cli/main-t4xnd213.js +0 -7
  367. package/dist/cli/main-vqak588n.js +0 -4
  368. package/dist/cli/main-w2n1303f.js +0 -9
  369. package/dist/cli/main-wbrdspr2.js +0 -5
  370. package/dist/cli/main-wsrg79c1.js +0 -7
  371. package/dist/cli/main-x4r0fne4.js +0 -5
  372. package/dist/cli/main-xea2f3tn.js +0 -6
  373. package/dist/cli/main-xg704a3c.js +0 -3
  374. package/dist/cli/main-xvnrabfp.js +0 -16
  375. package/dist/cli/main-xy53xf0r.js +0 -4
  376. package/dist/cli/main-y1fqy60y.js +0 -3
  377. package/dist/cli/main-yn8cd281.js +0 -34
  378. package/dist/cli/main-yr0ksc0h.js +0 -4
  379. package/dist/cli/main-z2ex2vyf.js +0 -4
  380. package/dist/cli/main-z3aayzvq.js +0 -3
  381. package/dist/cli/main-zaqh35jg.js +0 -3
  382. package/dist/cli/main-zc2e8e46.js +0 -4
  383. package/dist/cli/main-zzrfw6cf.js +0 -13
  384. package/dist/cli/main.js +0 -280
  385. package/dist/cli/market-cmd-e14kmx9n.js +0 -5
  386. package/dist/cli/mcp-login-wq7ktdek.js +0 -2
  387. package/dist/cli/mcp-market-cmd-9mg3jecy.js +0 -2
  388. package/dist/cli/notify-b7qc0cjb.js +0 -2
  389. package/dist/cli/oauth-z8whcgfx.js +0 -2
  390. package/dist/cli/output-b3ewj3ps.js +0 -16
  391. package/dist/cli/profiles-6mr5he5e.js +0 -2
  392. package/dist/cli/provider-config-g7j42q8x.js +0 -2
  393. package/dist/cli/provider-jr1y8vvm.js +0 -2
  394. package/dist/cli/registry-s8yk86g0.js +0 -2
  395. package/dist/cli/registry-t6p8d4mn.js +0 -2
  396. package/dist/cli/repl-bajwe1mh.js +0 -11
  397. package/dist/cli/resume-rwn9nz7y.js +0 -2
  398. package/dist/cli/run-flags-nah7ndpt.js +0 -2
  399. package/dist/cli/runtime-n7gafzhb.js +0 -2
  400. package/dist/cli/sandbox-config-emdy18x4.js +0 -2
  401. package/dist/cli/server-b0nvs2bn.js +0 -5
  402. package/dist/cli/session-arg-y75wd4kj.js +0 -2
  403. package/dist/cli/session-j62evmjq.js +0 -2
  404. package/dist/cli/sessions-cmd-tsnwz0ns.js +0 -7
  405. package/dist/cli/settings-df10wfez.js +0 -2
  406. package/dist/cli/setup-jzvv72fg.js +0 -2
  407. package/dist/cli/sextant-smoke-37m81ke6.js +0 -5
  408. package/dist/cli/skills-cmd-gjxnxnhx.js +0 -2
  409. package/dist/cli/smoke-p7748apt.js +0 -8
  410. package/dist/cli/start-chat-s4st3mm0.js +0 -12
  411. package/dist/cli/stream-gmeyewds.js +0 -2
  412. package/dist/cli/task-gh0kkp3n.js +0 -2
  413. package/dist/cli/tasks-z1kfpe8e.js +0 -2
  414. package/dist/cli/thinking-0eqkrz6t.js +0 -2
  415. package/dist/cli/todo-5brcrt9m.js +0 -2
  416. package/dist/cli/tools-7pzm0vj9.js +0 -2
  417. package/dist/cli/tools-s635p6s8.js +0 -2
  418. package/dist/cli/trust-cmd-cjav8zgm.js +0 -2
  419. package/dist/cli/update-check-pt31bm2f.js +0 -2
  420. package/dist/cli/update-cmd-tk131s9t.js +0 -2
  421. package/dist/cli/voice-56nabd8d.js +0 -2
  422. package/dist/cli/webfetch-xd8q596m.js +0 -2
  423. package/dist/cli/websearch-5hkf98k1.js +0 -2
  424. package/dist/cli/workflow-cmd-cy3cvzjp.js +0 -4
  425. package/dist/cli/workspace-q10g5z3e.js +0 -2
  426. package/dist/lib/index.js +0 -62
  427. package/dist/lib/models-index.json +0 -1
  428. package/dist/lib/plugins.js +0 -55
  429. package/dist/lib/providers.js +0 -17
  430. package/dist/lib/public-api.js +0 -20
  431. package/dist/lib/sdk.js +0 -360
  432. /package/{dist/cli → src/providers}/models-index.json +0 -0
@@ -0,0 +1,204 @@
1
+ /** Token & cost accounting: o200k token counting, provider usage normalization, USD costing,
2
+ * and context-window health.
3
+ *
4
+ * Normalization convention (the part that makes cost math coherent across providers):
5
+ * `input` = tokens billed at the BASE input rate — i.e. excluding cache reads and cache writes.
6
+ * - Anthropic `/messages` usage already follows this convention: `input_tokens` excludes both
7
+ * `cache_read_input_tokens` (billed ~0.1x input) and `cache_creation_input_tokens` (~1.25x).
8
+ * - OpenAI `/chat/completions` does NOT: `prompt_tokens` INCLUDES
9
+ * `prompt_tokens_details.cached_tokens`, so we subtract the cached share (clamped at 0) or the
10
+ * cached tokens would be double-billed (once at the input rate, once at the cache-read rate).
11
+ * The cached-inclusive convention is detected by the PRESENCE of `prompt_tokens`, or of a
12
+ * *_details cached count when no Anthropic `cache_read_input_tokens` coexists — NOT by the
13
+ * absence of `input_tokens` (gateways like OpenRouter emit BOTH spellings in one payload, and
14
+ * keying on absence would skip the subtraction there and double-bill the cached share), and
15
+ * NOT by a bare *_details block (a payload that also carries `cache_read_input_tokens` has a
16
+ * cache-EXCLUSIVE base; subtracting again would double-subtract and clamp input to 0).
17
+ *
18
+ * tokenlens (v1.3.1) is used where it fits: `breakdownTokens` from tokenlens/helpers already
19
+ * recognizes both providers' field spellings (prompt_tokens/completion_tokens/
20
+ * prompt_tokens_details.cached_tokens as well as input_tokens/output_tokens/
21
+ * cache_read_input_tokens/cache_creation_input_tokens) and tolerates junk input. What it does
22
+ * NOT do is reconcile the OpenAI inclusive-input asymmetry above — that part is hand-mapped here.
23
+ */
24
+
25
+ import type { countTokens as CountTokensFn } from "gpt-tokenizer/encoding/o200k_base";
26
+ import { breakdownTokens } from "tokenlens/helpers";
27
+
28
+ // lazy loader — the o200k rank table is deferred until the first countTokens() call that needs it.
29
+ // Measured (scripts/probe-import.ts, 2026-09-06): loading it takes 385–520 ms and adds ~136 MB of
30
+ // resident memory, and 3.8 MB of the 8 MB CLI bundle is this table. So nothing on a session's boot
31
+ // path may call countTokens(): the sextant usage panel goes through countTokensIfLoaded() and the
32
+ // chars-based estimator instead (tui/cost.ts), and the table loads when a person asks for the exact
33
+ // figure — /cost, /context, `rovecode context`.
34
+ type GptTokenizerMod = { countTokens: typeof CountTokensFn };
35
+ let _gptMod: GptTokenizerMod | null = null;
36
+ function lazyTokenizer(): GptTokenizerMod {
37
+ if (_gptMod === null) {
38
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
39
+ _gptMod = require("gpt-tokenizer/encoding/o200k_base") as GptTokenizerMod;
40
+ }
41
+ return _gptMod;
42
+ }
43
+ /** the o200k table is resident (some caller already paid for it) */
44
+ export function tokenizerLoaded(): boolean { return _gptMod !== null; }
45
+
46
+ // ---------- pricing ----------
47
+
48
+ export interface PricingRow {
49
+ inputPerMTok?: number;
50
+ outputPerMTok?: number;
51
+ cacheReadPerMTok?: number;
52
+ cacheWritePerMTok?: number;
53
+ }
54
+
55
+ export interface NormalizedUsage {
56
+ /** tokens billed at the base input rate (cache reads/writes excluded) */
57
+ input: number;
58
+ output: number;
59
+ cacheRead: number;
60
+ cacheWrite: number;
61
+ }
62
+
63
+ // ---------- token counting ----------
64
+
65
+ /** Synchronous token count using gpt-tokenizer's o200k_base encoding.
66
+ * An ESTIMATE for non-OpenAI models (Anthropic's tokenizer is not public); good enough for
67
+ * context-budget arithmetic, not for billing reconciliation — use provider usage for that. */
68
+ export function countTokens(text: string): number {
69
+ if (text.length === 0) return 0; // a fresh session's empty transcript must not load a 136 MB table to learn it is empty
70
+ return lazyTokenizer().countTokens(text);
71
+ }
72
+
73
+ /** The exact o200k count when the table is already resident, else null — never loads it. For callers
74
+ * that run on a session's boot path or on every repaint and have a cheaper estimator to fall back on. */
75
+ export function countTokensIfLoaded(text: string): number | null {
76
+ if (text.length === 0) return 0;
77
+ return _gptMod === null ? null : _gptMod.countTokens(text);
78
+ }
79
+
80
+ // ---------- usage normalization ----------
81
+
82
+ /** Non-negative finite number, else 0. Keeps NaN/negatives out of cost arithmetic. */
83
+ function nz(v: unknown): number {
84
+ return typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0;
85
+ }
86
+
87
+ function isRec(v: unknown): v is Record<string, unknown> {
88
+ return typeof v === "object" && v !== null && !Array.isArray(v);
89
+ }
90
+
91
+ /** cached_tokens read straight from prompt_tokens_details/input_tokens_details — tokenlens
92
+ * recognizes the former but not the Responses-API latter, so this is the fallback source. */
93
+ function detailsCachedTokens(raw: unknown): number {
94
+ if (!isRec(raw)) return 0;
95
+ for (const k of ["prompt_tokens_details", "input_tokens_details"]) {
96
+ const d = raw[k];
97
+ if (isRec(d)) return nz(d["cached_tokens"]);
98
+ }
99
+ return 0;
100
+ }
101
+
102
+ /** Normalize a raw provider usage payload (OpenAI or Anthropic wire shape) to a single
103
+ * cost-coherent structure. Unrecognized/malformed input normalizes to all zeros. */
104
+ export function normalizeUsage(raw: unknown): NormalizedUsage {
105
+ const b = breakdownTokens(raw as Parameters<typeof breakdownTokens>[0]);
106
+ const cacheRead = nz(b.cacheReads) || detailsCachedTokens(raw);
107
+ const cacheWrite = nz(b.cacheWrites);
108
+ let input = nz(b.input);
109
+ // OpenAI-style cache-INCLUSIVE base, i.e. the reported input still contains the cached share
110
+ // and it must be subtracted (clamped) so `input` means "billed at the base rate". Detected by
111
+ // PRESENCE of prompt_tokens — never by absence of input_tokens, which OpenRouter emits
112
+ // alongside prompt_tokens — or by a *_details cached count with NO cache_read_input_tokens
113
+ // beside it: that Anthropic spelling marks the base as already cache-EXCLUSIVE, so a payload
114
+ // carrying both (gateway mirroring the cached share into a details block) must not be
115
+ // subtracted a second time — that would clamp input to 0 and under-report /cost.
116
+ const cachedInclusive = isRec(raw) && (
117
+ typeof raw["prompt_tokens"] === "number"
118
+ || (detailsCachedTokens(raw) > 0 && raw["cache_read_input_tokens"] === undefined)
119
+ );
120
+ if (cachedInclusive) input = Math.max(0, input - cacheRead);
121
+ return { input, output: nz(b.output), cacheRead, cacheWrite };
122
+ }
123
+
124
+ // ---------- cost ----------
125
+
126
+ /** USD cost of a normalized usage under a pricing row (rates are $ per 1M tokens).
127
+ * Returns undefined when a rate needed for a NONZERO component is missing — an honest "unknown"
128
+ * beats silently under-billing (e.g. pricing cache reads at 0) or over-billing (at the full
129
+ * input rate). Zero components never require a rate; an all-zero usage costs 0. */
130
+ /** A prompt-size-dependent rate table, structurally what providers/catalog.ts `ratesFor()` returns.
131
+ * Declared here rather than imported so this module stays free of the catalog (and of its snapshot):
132
+ * a caller that knows the model builds the breakdown, this one only multiplies. */
133
+ export interface TieredRates {
134
+ /** prompt tokens billed at `base` */
135
+ promptBase: number;
136
+ /** prompt tokens billed at `above` */
137
+ promptAbove: number;
138
+ base: PricingRow;
139
+ above?: PricingRow;
140
+ /** the rates for everything that is not the prompt on this request — both vendors that tier also
141
+ * select the OUTPUT price by prompt size */
142
+ request: PricingRow;
143
+ }
144
+
145
+ /** USD for one turn under a tiered rate table. The prompt's three components (base input, cache reads,
146
+ * cache writes) are split in the same proportion as `promptBase`/`promptAbove`, which is exact for the
147
+ * two shapes that exist today — flat (everything base) and per-request (everything above) — and a
148
+ * documented approximation for a marginal tier, where no shipping provider states how a partly cached
149
+ * prompt divides across the threshold. Same honesty rule as costUsd: a missing rate for a nonzero
150
+ * component returns undefined rather than a wrong number. */
151
+ export function costUsdTiered(u: NormalizedUsage, r: TieredRates): number | undefined {
152
+ const promptTotal = r.promptBase + r.promptAbove;
153
+ const aboveShare = promptTotal > 0 ? r.promptAbove / promptTotal : 0;
154
+ const above = r.above ?? r.base;
155
+ const split = (count: number): readonly (readonly [number, PricingRow])[] =>
156
+ aboveShare <= 0 ? [[count, r.base]]
157
+ : aboveShare >= 1 ? [[count, above]]
158
+ : [[count * (1 - aboveShare), r.base], [count * aboveShare, above]];
159
+
160
+ let total = 0;
161
+ for (const [count, row] of [
162
+ ...split(u.input).map(([c, p]) => [c, p.inputPerMTok] as const),
163
+ ...split(u.cacheRead).map(([c, p]) => [c, p.cacheReadPerMTok] as const),
164
+ ...split(u.cacheWrite).map(([c, p]) => [c, p.cacheWritePerMTok] as const),
165
+ [u.output, r.request.outputPerMTok] as const,
166
+ ]) {
167
+ const c = nz(count);
168
+ if (c === 0) continue;
169
+ if (typeof row !== "number" || !Number.isFinite(row)) return undefined;
170
+ total += (c / 1_000_000) * row;
171
+ }
172
+ return total;
173
+ }
174
+
175
+ export function costUsd(u: NormalizedUsage, p: PricingRow): number | undefined {
176
+ const parts: readonly (readonly [number, number | undefined])[] = [
177
+ [u.input, p.inputPerMTok],
178
+ [u.output, p.outputPerMTok],
179
+ [u.cacheRead, p.cacheReadPerMTok],
180
+ [u.cacheWrite, p.cacheWritePerMTok],
181
+ ];
182
+ let total = 0;
183
+ for (const [count, rate] of parts) {
184
+ const c = nz(count);
185
+ if (c === 0) continue;
186
+ if (typeof rate !== "number" || !Number.isFinite(rate)) return undefined;
187
+ total += (c / 1_000_000) * rate;
188
+ }
189
+ return total;
190
+ }
191
+
192
+ // ---------- context health ----------
193
+
194
+ export const NEAR_LIMIT_FRACTION = 0.8;
195
+
196
+ /** Fraction of the context window consumed. `nearLimit` trips at ≥ 0.8 — the compaction signal.
197
+ * The fraction is deliberately NOT clamped above 1 (callers may want the overflow magnitude);
198
+ * negative/NaN usedTokens counts as 0. A degenerate window (≤ 0 or non-finite) reports full
199
+ * (fraction 1, nearLimit true): compacting on broken config beats silently overflowing. */
200
+ export function contextHealth(usedTokens: number, contextWindow: number): { fraction: number; nearLimit: boolean } {
201
+ if (!Number.isFinite(contextWindow) || contextWindow <= 0) return { fraction: 1, nearLimit: true };
202
+ const fraction = nz(usedTokens) / contextWindow;
203
+ return { fraction, nearLimit: fraction >= NEAR_LIMIT_FRACTION };
204
+ }
@@ -0,0 +1,121 @@
1
+ /** Tool argument validation — the "validate" that ADR-005 names first in
2
+ * `validate → revise → policy → approve → sandbox → execute` and that nothing actually did.
3
+ *
4
+ * Until this file existed, `call.args` went from the model straight into `execute`, where every tool
5
+ * casts it: `const a = args as { path: string; content: string }`. A cast is not a check. A model
6
+ * that sent `content` as an object got Node's own message back —
7
+ *
8
+ * Error: The "data" argument must be of type string or an instance of Buffer, TypedArray, or DataView
9
+ *
10
+ * — which names no tool, no property, and no fix, so the model's next attempt is a guess. Worse are
11
+ * the cases that do not throw: a number where a string belongs stringifies somewhere downstream and
12
+ * the wrong thing happens quietly.
13
+ *
14
+ * This validates against the schema the tool already publishes (ToolSchema.args, the same object the
15
+ * provider is told about), so there is nothing new to maintain: a tool that documents its arguments
16
+ * is validated by that documentation. The message names the tool, the property, what was expected
17
+ * and what arrived — everything a model needs to fix the call on the next turn without a human.
18
+ *
19
+ * Deliberately a SUBSET of JSON Schema — type, required, enum, array item type, nested objects —
20
+ * because that is what the tool schemas in this repo use. An unknown keyword is ignored rather than
21
+ * rejected: a schema this validator does not fully understand must never make a working tool
22
+ * unusable. Unknown PROPERTIES are likewise allowed through: policy already re-aims resources from
23
+ * declared keys only (tools.ts describeResource), and a strict-mode rejection here would break every
24
+ * provider that decorates calls with its own metadata. */
25
+
26
+ export interface ValidationIssue {
27
+ /** dotted path from the argument root: "todos.3.status", "" for the root itself */
28
+ path: string;
29
+ message: string;
30
+ }
31
+
32
+ interface Schema {
33
+ type?: unknown;
34
+ properties?: unknown;
35
+ required?: unknown;
36
+ enum?: unknown;
37
+ items?: unknown;
38
+ }
39
+
40
+ const isRec = (v: unknown): v is Record<string, unknown> => typeof v === "object" && v !== null && !Array.isArray(v);
41
+
42
+ /** what a value IS, in the words the schema uses — so "expected string, got array" reads plainly */
43
+ export function jsonTypeOf(v: unknown): string {
44
+ if (v === null) return "null";
45
+ if (Array.isArray(v)) return "array";
46
+ const t = typeof v;
47
+ if (t === "number") return Number.isInteger(v) ? "integer" : "number";
48
+ return t === "object" || t === "boolean" || t === "string" ? t : t; // function/symbol/undefined pass through as themselves
49
+ }
50
+
51
+ /** JSON Schema's `type` accepts a string or a list of them */
52
+ function typeMatches(want: unknown, v: unknown): boolean {
53
+ if (Array.isArray(want)) return want.some((w) => typeMatches(w, v));
54
+ if (typeof want !== "string") return true; // no usable type keyword: nothing to check
55
+ const actual = jsonTypeOf(v);
56
+ if (want === "number") return actual === "number" || actual === "integer";
57
+ if (want === "integer") return actual === "integer";
58
+ if (want === "object") return isRec(v);
59
+ return actual === want;
60
+ }
61
+
62
+ function join(base: string, key: string | number): string {
63
+ return base === "" ? String(key) : `${base}.${key}`;
64
+ }
65
+
66
+ function check(schema: unknown, value: unknown, path: string, out: ValidationIssue[]): void {
67
+ if (!isRec(schema)) return;
68
+ const s = schema as Schema;
69
+
70
+ if (s.type !== undefined && !typeMatches(s.type, value)) {
71
+ const want = Array.isArray(s.type) ? s.type.join(" or ") : String(s.type);
72
+ out.push({ path, message: `expected ${want}, got ${jsonTypeOf(value)}` });
73
+ return; // one complaint per value: the nested checks below would only repeat it
74
+ }
75
+
76
+ if (Array.isArray(s.enum) && s.enum.length > 0 && !s.enum.includes(value as never)) {
77
+ out.push({ path, message: `expected one of ${s.enum.map((e) => JSON.stringify(e)).join(" | ")}, got ${JSON.stringify(value)}` });
78
+ return;
79
+ }
80
+
81
+ if (Array.isArray(value) && s.items !== undefined) {
82
+ value.forEach((item, i) => check(s.items, item, join(path, i), out));
83
+ return;
84
+ }
85
+
86
+ if (isRec(value)) {
87
+ if (Array.isArray(s.required)) {
88
+ for (const key of s.required) {
89
+ // present-but-undefined is missing: a provider that serializes an omitted argument as
90
+ // `{"path": undefined}` has not supplied it
91
+ if (typeof key === "string" && (!(key in value) || value[key] === undefined)) {
92
+ out.push({ path: join(path, key), message: "is required but was not provided" });
93
+ }
94
+ }
95
+ }
96
+ if (isRec(s.properties)) {
97
+ for (const [key, sub] of Object.entries(s.properties)) {
98
+ if (key in value && value[key] !== undefined) check(sub, value[key], join(path, key), out);
99
+ }
100
+ }
101
+ }
102
+ }
103
+
104
+ /** Every problem with these arguments, in schema order. Empty = usable. */
105
+ export function validateArgs(schema: unknown, args: unknown): ValidationIssue[] {
106
+ const out: ValidationIssue[] = [];
107
+ // a schema with no `type` still describes an object when it lists properties/required
108
+ const root = isRec(schema) && (schema as Schema).type === undefined && ((schema as Schema).properties !== undefined || (schema as Schema).required !== undefined)
109
+ ? { ...(schema as Record<string, unknown>), type: "object" }
110
+ : schema;
111
+ check(root, args, "", out);
112
+ return out;
113
+ }
114
+
115
+ /** The line the model reads. Names the tool and each bad property; the model's next attempt is a
116
+ * correction, not a guess. Bounded: a list of 50 malformed todos must not become a 50-line error. */
117
+ export function formatIssues(tool: string, issues: readonly ValidationIssue[], max = 5): string {
118
+ const shown = issues.slice(0, max).map((i) => (i.path === "" ? i.message : `${i.path} ${i.message}`));
119
+ const more = issues.length > shown.length ? ` (+${issues.length - shown.length} more)` : "";
120
+ return `Invalid arguments for ${tool}: ${shown.join("; ")}${more}`;
121
+ }
@@ -0,0 +1,159 @@
1
+ /** The verify gate: a run that changed files runs the project's own check before it is allowed to end "done".
2
+ *
3
+ * Why: "done" is the model's silence (loop.ts, the finish check). The finish check catches the model stopping
4
+ * right after a failure it could see. This catches the failure it could NOT see — the edit landed, the tests it
5
+ * never ran are red. The check's failing part goes back to the model ONCE, the same way the finish check's
6
+ * message does; then the run ends with working code or with an honest "check failed: …" on run_end.
7
+ *
8
+ * What is here: the runner (bounded by its own timeout, the run's abort, the executor's 10k clip), the
9
+ * failing-part extraction, the nudge text and the one-clause rendering. What is NOT here: deciding WHICH
10
+ * command runs. That is core/verify.ts (resolveVerify: configuration or an unambiguous script, never a guess),
11
+ * taken here as an injected function — this file never reads settings itself. No resolution = the gate does
12
+ * not run and run_end says "not verified: no check configured". Silence about an unverified change is the
13
+ * bug the finish check exists for; it is not reintroduced here.
14
+ *
15
+ * Blind spot, stated rather than hidden: the gate keys on RunOutstanding.writes (successful edit/write calls).
16
+ * A run that changed files only through `bash` (sed, git apply, a generator) has writes 0 and is not verified —
17
+ * and not reported either, because a bash call that changed nothing (ls, grep, cat) looks identical from here
18
+ * and "not verified" on every lookup would be the noise the finish check was careful to avoid. */
19
+
20
+ import type { RunOutstanding } from "./types.ts";
21
+ import { resolveVerify, recordVerifyTiming } from "./verify.ts";
22
+
23
+ /** what the resolver (core/verify.ts) answers, in the shape this file needs: the commands to run in order,
24
+ * uninterpreted (targeting — "typecheck plus the tests that import the changed files" — is the resolver's
25
+ * business, this runner never has to understand a command); what it saw and refused, each with its reason;
26
+ * and, when there is nothing to run, why. */
27
+ export interface VerifyResolution {
28
+ commands: string[];
29
+ refused?: string[];
30
+ /** where the commands came from ("settings", "inferred", "none") */
31
+ source?: string;
32
+ /** one sentence from the resolver: the file and key the answer came from, or why there is none */
33
+ reason?: string;
34
+ }
35
+ export type VerifyResolver = (cwd: string) => VerifyResolution | null;
36
+
37
+ /** the seam, one direction: this file asks core/verify.ts, never the other way round. `resolveVerify` reads
38
+ * settings and the project's manifests and decides; nothing here second-guesses it. */
39
+ export const resolveForGate: VerifyResolver = (cwd) => {
40
+ const plan = resolveVerify(cwd);
41
+ return { commands: plan.commands, refused: plan.refused, source: plan.source, reason: plan.reason };
42
+ };
43
+
44
+ /** what one check cost, for the doctor row and the "on by default?" question (core/verify.ts timing file) */
45
+ export function noteVerifyCost(cwd: string, o: VerifyOutcome): void {
46
+ try { if (o.ran > 0) recordVerifyTiming(cwd, o.command, o.ms); } catch { /* a timing file that cannot be written is not the run's problem */ }
47
+ }
48
+
49
+ export interface VerifyOutcome {
50
+ /** the command that decided the outcome: the failing one, or the last one when all passed */
51
+ command: string;
52
+ ok: boolean;
53
+ code: number;
54
+ timedOut: boolean;
55
+ ms: number;
56
+ /** the failing part of the output — never the whole log (failingPart); "" when ok */
57
+ failure: string;
58
+ /** how many of the resolved commands ran (a failure stops the list) */
59
+ ran: number;
60
+ }
61
+
62
+ /** run one command through the same executor `bash` uses; injectable for tests */
63
+ export type Exec = (cmd: string, cwd: string, signal: AbortSignal) => Promise<{ code: number; text: string }>;
64
+
65
+ export const VERIFY_TIMEOUT_MS = 120_000;
66
+ export const FAILURE_CHARS = 3_000;
67
+ const TAIL_LINES = 40;
68
+ const EARLIER_LINES = 20;
69
+
70
+ const defaultExec: Exec = async (cmd, cwd, signal) => {
71
+ const { getExecutor } = await import("./executor.ts");
72
+ return getExecutor().run(cmd, cwd, signal);
73
+ };
74
+
75
+ /** The part of a check's output that says what failed: the last 40 lines (test runners summarize at the end),
76
+ * preceded by up to 20 earlier lines that name a failure, clipped from the FRONT to `cap` chars so the summary
77
+ * survives. Never the whole log — 4 000 lines of test output back to the model is the same as no information. */
78
+ export function failingPart(text: string, cap = FAILURE_CHARS): string {
79
+ const lines = text.replace(/\r\n?/g, "\n").split("\n");
80
+ while (lines.length > 0 && lines.at(-1)!.trim() === "") lines.pop();
81
+ const tailStart = Math.max(0, lines.length - TAIL_LINES);
82
+ const earlier = lines.slice(0, tailStart).filter((l) => /\b(fail|failed|failing|error|not ok|assert(ion)?|exception|panic)\b|✗|×|✘/i.test(l));
83
+ const picked = earlier.length > EARLIER_LINES ? [...earlier.slice(0, EARLIER_LINES), `… ${earlier.length - EARLIER_LINES} more lines naming a failure`] : earlier;
84
+ const parts = [...picked];
85
+ if (picked.length > 0 && tailStart > 0) parts.push("…");
86
+ parts.push(...lines.slice(tailStart));
87
+ let out = parts.join("\n");
88
+ if (out.length > cap) out = `[… ${out.length - cap} chars clipped]\n${out.slice(out.length - cap)}`;
89
+ return out;
90
+ }
91
+
92
+ /** Run the resolved commands in order, stopping at the first failure. Bounded by `timeoutMs` (its own clock,
93
+ * combined with the run's abort). A timeout or an abort ends the command through the executor's kill path and
94
+ * is reported as ok:false — a timed-out check is a failed check, not a passed one. An executor that cannot run
95
+ * at all (no rung configured, bash missing) is a failure with that message, never a throw. */
96
+ export async function runVerify(
97
+ res: VerifyResolution, cwd: string,
98
+ opts: { signal?: AbortSignal; timeoutMs?: number; exec?: Exec; clock?: () => number } = {},
99
+ ): Promise<VerifyOutcome> {
100
+ const exec = opts.exec ?? defaultExec;
101
+ const clock = opts.clock ?? Date.now;
102
+ const timeoutMs = opts.timeoutMs ?? VERIFY_TIMEOUT_MS;
103
+ const started = clock();
104
+ let ran = 0;
105
+ let last = res.commands[0] ?? "";
106
+ for (const command of res.commands) {
107
+ last = command;
108
+ const ac = new AbortController();
109
+ let timedOut = false;
110
+ const onAbort = () => ac.abort();
111
+ if (opts.signal?.aborted) ac.abort(); else opts.signal?.addEventListener("abort", onAbort, { once: true });
112
+ const timer = setTimeout(() => { timedOut = true; ac.abort(); }, timeoutMs);
113
+ let r: { code: number; text: string };
114
+ try { r = await exec(command, cwd, ac.signal); }
115
+ catch (e) { r = { code: -1, text: `could not run the check: ${e instanceof Error ? e.message : String(e)}` }; }
116
+ finally { clearTimeout(timer); opts.signal?.removeEventListener("abort", onAbort); }
117
+ ran++;
118
+ if (timedOut) return { command, ok: false, code: r.code, timedOut: true, ms: clock() - started, failure: failingPart(r.text), ran };
119
+ if (r.code !== 0 || opts.signal?.aborted) return { command, ok: false, code: r.code, timedOut: false, ms: clock() - started, failure: failingPart(r.text), ran };
120
+ }
121
+ return { command: last, ok: true, code: 0, timedOut: false, ms: clock() - started, failure: "", ran };
122
+ }
123
+
124
+ /** The one continuation turn after a failed check. A harness message, not the user's; both answers — fix it, or
125
+ * say plainly why it cannot be made to pass — are acceptable, and the next silence ends the run either way. */
126
+ export function verifyNudgeText(o: VerifyOutcome, timeoutMs = VERIFY_TIMEOUT_MS): string {
127
+ const head = o.timedOut
128
+ ? `The project's check did not finish within ${Math.round(timeoutMs / 1000)}s and was stopped: \`${o.command}\``
129
+ : `The project's check failed (exit ${o.code}) after your changes: \`${o.command}\``;
130
+ return [
131
+ "<verify-check>",
132
+ "You stopped, but the work does not pass — this is a check by the harness, not a message from the user.",
133
+ head,
134
+ o.failure.trim() === "" ? "(the check produced no output)" : "Failing part of its output:\n" + o.failure,
135
+ "Fix what your changes broke and run the check yourself, or say plainly why it cannot be made to pass. This check runs once per run; your next reply ends it either way.",
136
+ "</verify-check>",
137
+ ].join("\n");
138
+ }
139
+
140
+ /** one clause for the surfaces ("done · check failed (bun test): 2 tests failed") */
141
+ export function verifyClause(v: NonNullable<RunOutstanding["verify"]>): string {
142
+ const refused = v.refused && v.refused.length > 0 ? ` · ${v.refused.length} check${v.refused.length === 1 ? "" : "s"} refused (${v.refused.join("; ")})` : "";
143
+ // a configured check can be a whole shell line; the clause names it, the transcript carries it
144
+ const cmd = "command" in v ? (v.command.length > 60 ? `${v.command.slice(0, 59)}…` : v.command) : "";
145
+ switch (v.state) {
146
+ case "unconfigured": return `not verified: ${v.reason ?? "no check configured"}${refused}`;
147
+ case "passed": return `check passed (${cmd})${refused}`;
148
+ case "timeout": return `check timed out after ${v.seconds}s (${cmd})${refused}`;
149
+ case "failed": return `check failed (${cmd}): ${(v.failure.split("\n").filter((l) => l.trim() !== "").at(-1) ?? "").slice(0, 120)}${refused}`;
150
+ }
151
+ }
152
+
153
+ /** first line for the surfaces while it runs / when it ends (the RunEvent "verify" detail) */
154
+ export function verifyDetail(o: VerifyOutcome): string {
155
+ if (o.ok) return `passed in ${(o.ms / 1000).toFixed(1)}s`;
156
+ if (o.timedOut) return `timed out after ${(o.ms / 1000).toFixed(0)}s`;
157
+ const lastLine = o.failure.split("\n").filter((l) => l.trim() !== "").at(-1) ?? "";
158
+ return `exit ${o.code}${lastLine ? ` — ${lastLine.slice(0, 120)}` : ""}`;
159
+ }