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/loop.ts DELETED
@@ -1,567 +0,0 @@
1
- /** The agent loop (ADR-003): one loop, generator-based, event-streaming.
2
- * Steering drained between tool batches; follow-ups drained at stop.
3
- * Errors cross the provider seam as stopReasons, never exceptions.
4
- * Cancellation (port #21): ONE AbortController per run — its signal reaches
5
- * the provider fetch, ToolContext.signal, and (by identity) child processes. */
6
-
7
- import { randomUUID } from "node:crypto";
8
- import type {
9
- AgentDefinition, Message, MessagePart, RunEvent, RunConfig, RunOutstanding, StreamEvent, StreamFn, VerifyState,
10
- ModelRef, ToolCallPart, AgentVars, ToolContext, ToolOutput, ToolSchema,
11
- StopReason, TokenUsage,
12
- } from "./types.ts";
13
- import { ToolRegistry, ABORTED_TOOL_RESULT, type ExtensionHooks } from "./tools.ts";
14
- import type { ToolGuard } from "./guardrails.ts";
15
- import { servedBy } from "../providers/router.ts";
16
- import { SessionStore } from "./session.ts";
17
- import { assembleContext, estimateTokens, type ContextChunk } from "./context.ts";
18
- import { compactionTrigger, planCompaction, applyCompaction, isContextOverflow, type CompactionCtx, type NativeCompactor } from "./compaction.ts";
19
- import { verifyClause, verifyDetail, verifyNudgeText } from "./verify-gate.ts";
20
-
21
- export interface LoopDeps {
22
- stream: StreamFn;
23
- registry: ToolRegistry;
24
- store: SessionStore;
25
- hooks?: ExtensionHooks;
26
- summarize?: (texts: string[]) => Promise<string>; // weak-model head summarizer
27
- /** port #25 provider-native compaction capability — set ONLY when the active provider does
28
- * server-side compaction (none of rovecode's adapters do today; compaction.ts header) */
29
- compactNative?: NativeCompactor;
30
- tools?: ToolSchema[];
31
- /** orchestrator seam: run a child agent; receives parent depth + 1 */
32
- childRunner?: (agent: string, goal: string, vars: AgentVars | undefined, depth: number) => Promise<{ ok: boolean; summary: string; usage: TokenUsage }>;
33
- /** tool-loop guardrails (port #4): loop signatures + duplicate-result stubs */
34
- guard?: ToolGuard;
35
- /** The plan reminder: called once per turn with the history that is about to be sent, it returns a
36
- * line the model should see again — the live todo list — or null. The result rides as ONE extra user
37
- * message on this request only: it is never appended to `history` and never persisted, so the plan
38
- * cannot pile up copies of itself in the transcript, and the system prefix (and its prompt cache)
39
- * is untouched. Without it a list written 20 turns ago is buried under tool results and the model
40
- * stops maintaining it. */
41
- planReminder?: (history: readonly Message[]) => string | null;
42
- /** ToolContext cwd for this run — surfaces with a session cwd (ACP, server)
43
- * pass it here; default is the agent process dir */
44
- cwd?: string;
45
- /** mid-turn cancellation (port #21): when this aborts, the run's OWN
46
- * controller aborts — the in-flight provider fetch dies, every
47
- * ToolContext.signal consumer (bash subprocess trees, MCP calls) is
48
- * cancelled, and the run ends with run_end status "stopped". Same seam as
49
- * `cwd`: every surface already constructs LoopDeps, so one optional field
50
- * keeps tui/repl/acp/serve uniform. The loop still owns the per-run
51
- * controller so bare consumers that only .return() the generator
52
- * (orchestrator children, cmdRun, gauntlet) keep in-flight-tool
53
- * cancellation without constructing anything. */
54
- signal?: AbortSignal;
55
- /** the run's clock for RunConfig.maxSeconds (default Date.now) — tests inject a fake one */
56
- clock?: () => number;
57
- }
58
-
59
- /** Synthesized output for a tool_call the abort left unanswered — ONE owner, tools.ts (dispatch
60
- * synthesizes the same text); re-exported here for the loop's consumers (surfaces, tests). */
61
- export { ABORTED_TOOL_RESULT };
62
-
63
- export class SteeringQueue {
64
- private queue: string[] = [];
65
- push(text: string): void { this.queue.push(text); }
66
- drainAll(): string[] { const q = this.queue; this.queue = []; return q; }
67
- drainOne(): string | undefined { return this.queue.shift(); }
68
- get size(): number { return this.queue.length; }
69
- }
70
-
71
- /** Deterministic toolCall part extraction + truncation safety (pi agent-loop.ts:344-380). */
72
- export function extractToolCalls(parts: MessagePart[], stopReason: string): { calls: ToolCallPart[]; truncated: boolean } {
73
- const calls = parts.filter((p): p is ToolCallPart => p.kind === "tool_call");
74
- return { calls, truncated: stopReason === "length" };
75
- }
76
-
77
- /** One controller per run (port #21 bar). agentLoop is a thin wrapper so the
78
- * controller's lifetime is EXACTLY the generator's: it follows deps.signal
79
- * while the run lives, and the finally aborts it on ANY settle — a returned/
80
- * thrown/finished run owns nothing that may keep running (fetch, tools,
81
- * subprocesses all hang off this one signal).
82
- * Upstream shape: opencode threads one AbortSignal per task into every model
83
- * call and tool (session/llm.ts:51,136,321; session/prompt.ts:323,329 with
84
- * onInterrupt → taskAbort.abort()); codex aborts the active turn task from
85
- * its Op loop (core/src/tasks/mod.rs:546-591 abort_turn_if_active). */
86
- export async function* agentLoop(
87
- def: AgentDefinition,
88
- goal: string,
89
- vars: AgentVars,
90
- cfg: RunConfig,
91
- deps: LoopDeps,
92
- steering: SteeringQueue,
93
- depth = 0,
94
- followUps?: SteeringQueue,
95
- ): AsyncGenerator<RunEvent> {
96
- const runAc = new AbortController();
97
- const follow = () => runAc.abort();
98
- if (deps.signal?.aborted) runAc.abort();
99
- else deps.signal?.addEventListener("abort", follow, { once: true });
100
- // port #29: run-level hooks ride the event stream (core/hooks.ts observer): pre_run / compaction /
101
- // post_run are awaited BEFORE the event reaches the consumer (post_run must land before a cmdRun
102
- // exit), on_event is a fire-and-forget tap; timeout + isolation live in the runner, never here
103
- const obs = deps.hooks?.observer?.({ cwd: deps.cwd ?? process.cwd(), sessionId: deps.store.id });
104
- try {
105
- for await (const ev of runLoop(def, goal, vars, cfg, deps, steering, depth, followUps, runAc)) {
106
- if (obs) await obs.observe(ev);
107
- yield ev;
108
- }
109
- } finally {
110
- deps.signal?.removeEventListener("abort", follow);
111
- runAc.abort();
112
- // fix-wave 4 (#39 MED-1): the CONSUMER closed the generator before run_end (serve disconnect,
113
- // ACP session/cancel, TUI Esc all .return() it) — the run is over, nobody is listening, and the
114
- // run-level hooks would never learn it (no OTel export, no reflection sweep). This same loop's
115
- // teardown tells the observer once; it fires post_run with the abort shape and yields nothing
116
- // (ADR-003: no second loop, no fabricated event — a yielded run_end makes close() a no-op).
117
- if (obs) await obs.close();
118
- }
119
- }
120
-
121
- const sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
122
-
123
- async function* runLoop(
124
- def: AgentDefinition,
125
- goal: string,
126
- vars: AgentVars,
127
- cfg: RunConfig,
128
- deps: LoopDeps,
129
- steering: SteeringQueue,
130
- depth: number,
131
- followUps: SteeringQueue | undefined,
132
- runAc: AbortController,
133
- ): AsyncGenerator<RunEvent> {
134
- const runId = randomUUID();
135
- yield { type: "run_start", runId, sessionId: deps.store.id, goal };
136
- const model: ModelRef = def.model ?? { provider: "mock", model: "default" };
137
- const history: Message[] = [...deps.store.messages()];
138
- const runStart = history.length; // the finish check reads this run's transcript only — an aborted call from the last run is not this run's failure
139
- const userMsg: Message = {
140
- id: randomUUID(), role: "user",
141
- parts: [{ kind: "text", text: goal }],
142
- parentId: history.at(-1)?.id ?? null, createdAt: Date.now(),
143
- };
144
- deps.store.append(userMsg);
145
- history.push(userMsg);
146
-
147
- const events: RunEvent[] = [];
148
- const emit = (e: RunEvent) => { events.push(e); };
149
- const flush = function* (): Generator<RunEvent> { yield* events.splice(0, events.length); };
150
-
151
- // guard resets once per USER turn — one agentLoop invocation ≈ one hermes
152
- // run_conversation (reset_for_turn at turn_context.py:700). The `turn` loop
153
- // below is model iterations; resetting there would wipe the streak and make
154
- // the guard inert. Follow-ups are new user turns (second reset below).
155
- deps.guard?.onTurn();
156
-
157
- // port #25 emergency compaction state: an overflow rejection (error-stop block) arms ONE
158
- // aggressive compaction + re-drive for the next iteration; at most one re-drive per run. The
159
- // armed value is the rejected turn's run_end summary — yielded verbatim when the compaction
160
- // turns out to be a no-op (nothing droppable: re-driving the identical request is pointless)
161
- let emergencyPending: string | null = null;
162
- let emergencyRedrives = 0;
163
- const clock = deps.clock ?? Date.now;
164
- const startedAt = clock();
165
- // running spend for RunConfig.maxCostUsd (checked at the turn boundary below)
166
- let spentUsd = 0, unpricedTurns = 0;
167
- // the finish check fires at most once per run (see the "done" exit below)
168
- let nudged = false;
169
- // the verify gate's memory: the write count the last check saw, and how it ended — a model that answers the
170
- // failed check with prose (no new write) gets that answer represented, not a second two-minute run
171
- let verifiedAtWrites = -1;
172
- let lastVerify: VerifyState | undefined;
173
-
174
- for (let turn = 1; turn <= cfg.maxTurns; turn++) {
175
- // --- abort check: an abort that landed during the previous batch (or before
176
- // turn 1) must not consume steering or touch the provider again
177
- if (runAc.signal.aborted) { yield { type: "run_end", status: "stopped", summary: "run aborted" }; return; }
178
- // --- wall clock (RunConfig.maxSeconds): a turn boundary, never mid-tool, so the run ends with every
179
- // result it already has and the same "budget" status the turn cap uses — a spiral of short verification
180
- // turns ends in a result object instead of an external kill
181
- if (cfg.maxSeconds !== undefined && (clock() - startedAt) / 1000 >= cfg.maxSeconds) {
182
- yield { type: "run_end", status: "budget", summary: `wall clock (${cfg.maxSeconds}s) reached after ${turn - 1} turn${turn === 2 ? "" : "s"}` };
183
- return;
184
- }
185
- // --- spend cap (RunConfig.maxCostUsd): the same boundary, the same status. Priced from each turn's own
186
- // usage as it lands (below), so the cap is compared against dollars actually spent, never an estimate.
187
- // Turns the catalog cannot price add nothing; the summary says how many, so "$0.40 spent" is never read
188
- // as "$0.40 in total" when two turns were unpriced.
189
- if (cfg.maxCostUsd !== undefined && spentUsd >= cfg.maxCostUsd) {
190
- const unpriced = unpricedTurns > 0 ? `; ${unpricedTurns} turn${unpricedTurns === 1 ? "" : "s"} unpriced` : "";
191
- yield { type: "run_end", status: "budget", summary: `cost cap ($${cfg.maxCostUsd.toFixed(2)}) reached after ${turn - 1} turn${turn === 2 ? "" : "s"} — $${spentUsd.toFixed(4)} spent${unpriced}` };
192
- return;
193
- }
194
-
195
- // --- steering drain point: before the model call ---
196
- for (const s of steering.drainAll()) {
197
- const sm: Message = { id: randomUUID(), role: "user", parts: [{ kind: "text", text: s }], parentId: history.at(-1)?.id ?? null, createdAt: Date.now() };
198
- deps.store.append(sm); history.push(sm);
199
- yield { type: "steer", text: s };
200
- }
201
-
202
- yield { type: "turn_start", turn };
203
-
204
- // --- context assembly + compaction (ADR-007; strategy seam + adaptive trigger: port #25) ---
205
- // counted over ALL parts (partsTokenText): tool calls/results dominate agentic histories,
206
- // and a text-only count would keep this trigger permanently below threshold
207
- const histTokens = history.reduce((n, m) => n + estimateTokens(partsTokenText(m.parts)), 0);
208
- // speculative: the estimate crossed budget × threshold, before this turn's provider call;
209
- // emergency: the previous turn was REJECTED as a context overflow (error-stop block) — plan
210
- // against the observed size and re-drive once (compaction.ts header: senpi/opencode cites)
211
- const trigger = compactionTrigger(histTokens, cfg, emergencyPending !== null);
212
- const overflowSummary = emergencyPending;
213
- emergencyPending = null; // consumed: one compaction per overflow
214
- if (trigger) {
215
- const cctx: CompactionCtx = { trigger, tokenText: (m) => partsTokenText(m.parts), summarize: deps.summarize, native: deps.compactNative, model, signal: runAc.signal };
216
- const plan = planCompaction(history, cfg, cctx);
217
- const out = plan ? await applyCompaction(history, plan, cfg, cctx) : null;
218
- if (out) {
219
- history.length = 0; history.push(...out.history);
220
- const ev: RunEvent = { type: "compaction", strategy: out.strategy, trigger, tokensBefore: histTokens, tokensAfter: history.reduce((n, m) => n + estimateTokens(partsTokenText(m.parts)), 0) };
221
- deps.store.appendEvent(ev); // real sessions carry the marker (export + replay), not just fixtures
222
- yield ev;
223
- } else if (overflowSummary !== null) {
224
- // emergency with nothing droppable (a fresh session whose single turn overflows): the
225
- // re-drive would repeat the rejected request byte-for-byte — end with the provider's error
226
- yield { type: "run_end", status: "error", summary: overflowSummary };
227
- return;
228
- }
229
- }
230
-
231
- const systemText = typeof def.systemPrompt === "function" ? def.systemPrompt(vars) : def.systemPrompt;
232
- const histNow = history.reduce((n, m) => n + estimateTokens(partsTokenText(m.parts)), 0);
233
- const chunks: ContextChunk[] = [
234
- { name: "system", text: systemText, priority: 100, tokens: estimateTokens(systemText) },
235
- ...(def.contextChunks ?? []), // port #8: e.g. harvested config (priority 70) — evicted before system
236
- { name: "history", text: "", priority: 50, tokens: histNow }, // marker; history passed directly below
237
- ];
238
- const asm = assembleContext(chunks, cfg.contextBudgetTokens);
239
- if (asm.dropped.length > 0) {
240
- const droppedTokens = asm.dropped.reduce((n, c) => n + c.tokens, 0);
241
- yield { type: "compaction", strategy: "context-drop", tokensBefore: asm.totalTokens + droppedTokens, tokensAfter: asm.totalTokens };
242
- }
243
- // the system message = kept non-history chunks in priority order (system first);
244
- // ONE prompt-assembly path — dropped chunks (e.g. config) never reach the wire
245
- const promptChunks = asm.chunks.filter((c) => c.name !== "history");
246
- const systemKept = promptChunks.length > 0;
247
- const promptText = promptChunks.map((c) => c.text).join("\n\n");
248
-
249
- // --- provider turn (never throws; errors are stopReasons) ---
250
- // the run signal rides into the StreamFn options: both wire adapters hand it
251
- // to fetch, so an abort kills the in-flight request itself (≤500ms bar)
252
- const msgId = randomUUID();
253
- let turnResult: TurnOutcome;
254
- const sysMsg: Message = { id: "sys", role: "system", parts: [{ kind: "text", text: promptText }], parentId: null, createdAt: 0 };
255
- try {
256
- // request-only: appended to what goes on the wire, never to `history` or the store
257
- const reminder = deps.planReminder?.(history) ?? null;
258
- const withReminder: Message[] = reminder === null
259
- ? history
260
- : [...history, { id: `reminder-${turn}`, role: "user", parts: [{ kind: "text", text: reminder }], parentId: history.at(-1)?.id ?? null, createdAt: Date.now() }];
261
- // deltas yield LIVE, one RunEvent each, while the provider streams. The old shape awaited the
262
- // whole turn and flushed the buffered message_updates afterwards — a generator cannot yield
263
- // from a callback — so a 15 s reasoning phase (claude-opus-5 at --effort high, measured) put
264
- // nothing on screen and read as a hang. Order is unchanged: every delta still precedes turn_end.
265
- // the run's deadline rides into the provider call so a retry backoff cannot overshoot the clock the turn boundary enforces
266
- const live = collectTurn(deps.stream, model, systemKept ? [sysMsg, ...withReminder] : withReminder, deps.tools, runAc.signal, cfg.maxSeconds !== undefined ? startedAt + cfg.maxSeconds * 1000 : undefined);
267
- let reasoning = ""; // the reasoning text stays here: only its estimated size leaves the loop
268
- for (;;) {
269
- const step = await live.next();
270
- if (step.done) { turnResult = step.value; break; }
271
- if (step.value.type === "text_delta") yield { type: "message_update", messageId: msgId, delta: step.value.text };
272
- else { reasoning += step.value.text; yield { type: "reasoning_update", messageId: msgId, tokens: estimateTokens(reasoning) }; }
273
- }
274
- } catch (e) {
275
- turnResult = { parts: [], stopReason: "error", usage: { input: 0, output: 0 }, error: e instanceof Error ? e.message : String(e) };
276
- }
277
- yield* flush();
278
- const { parts, stopReason, usage } = turnResult;
279
- // --- abort landed during the provider turn: the fetch is already dead (or the
280
- // stream reported "aborted" itself). Keep any partial text the adapter salvaged,
281
- // but a tool_call in an aborted turn will never execute — append a synthesized
282
- // failed result for each so the NEXT request carries no orphan tool_calls
283
- // (codex context_manager/normalize.rs:51-67; opencode message-v2.ts:349-360
284
- // "Anthropic/Claude APIs require every tool_use to have a corresponding tool_result").
285
- const aborted = runAc.signal.aborted || stopReason === "aborted";
286
- const assistant: Message = {
287
- id: msgId, role: "assistant", parts, parentId: history.at(-1)?.id ?? null,
288
- // origin = the model that SERVED the turn (router fallback may differ from the one
289
- // asked for; /cost prices per-message via origin — port #14 HIGH-2), else the request
290
- createdAt: Date.now(), origin: turnResult.origin ?? model, usage,
291
- };
292
- if (cfg.maxCostUsd !== undefined && (usage.input > 0 || usage.output > 0 || (usage.cacheRead ?? 0) > 0 || (usage.cacheWrite ?? 0) > 0)) {
293
- const c = cfg.priceUsd?.(usage, assistant.origin ?? model);
294
- if (c === undefined) unpricedTurns++; else spentUsd += c;
295
- }
296
- if (!aborted || parts.length > 0) { deps.store.append(assistant); history.push(assistant); }
297
- yield { type: "turn_end", turn, stopReason: aborted ? "aborted" : stopReason };
298
- if (aborted) {
299
- for (const p of parts) {
300
- if (p.kind === "tool_call") appendToolResult(deps.store, history, p.id, { ok: false, output: ABORTED_TOOL_RESULT });
301
- }
302
- yield { type: "run_end", status: "stopped", summary: "run aborted" };
303
- return;
304
- }
305
-
306
- // --- error stops: the run ends in 'error', never a fake 'done' ---
307
- if (stopReason === "error") {
308
- const errText = turnResult.error ?? "provider stream failed";
309
- const partial = partsText(parts);
310
- const summary = partial ? `${partial}\nerror: ${errText}` : `error: ${errText}`;
311
- // port #25: a context-overflow rejection arms an emergency compaction (next iteration's
312
- // compaction block) and re-drives ONCE per run — only while a turn is left to re-drive in
313
- // (on the last permitted turn the run ends HERE with the provider's text, not as "budget");
314
- // a second overflow ends the run below
315
- if (isContextOverflow(errText) && emergencyRedrives === 0 && turn < cfg.maxTurns) { emergencyRedrives++; emergencyPending = summary; continue; }
316
- yield { type: "run_end", status: "error", summary };
317
- return;
318
- }
319
-
320
- const { calls, truncated } = extractToolCalls(parts, stopReason);
321
- if (truncated) {
322
- // fail all calls unexecuted — args may be silently truncated JSON.
323
- // still append tool_result{ok:false} so the next provider request stays valid
324
- const detail = "response hit length limit; tool calls not executed";
325
- for (const c of calls) {
326
- yield { type: "tool_call_failed", callId: c.id, reason: "truncated", detail };
327
- appendToolResult(deps.store, history, c.id, { ok: false, output: detail });
328
- }
329
- continue;
330
- }
331
- if (calls.length === 0) {
332
- // --- follow-up drain point: a queued follow-up continues the run instead of ending it ---
333
- const follow = followUps ? followUps.drainAll() : [];
334
- if (follow.length > 0) {
335
- deps.guard?.onTurn(); // a follow-up is a new user turn (upstream: new run_conversation → reset)
336
- for (const f of follow) {
337
- const fm: Message = { id: randomUUID(), role: "user", parts: [{ kind: "text", text: f }], parentId: history.at(-1)?.id ?? null, createdAt: Date.now() };
338
- deps.store.append(fm); history.push(fm);
339
- yield { type: "steer", text: f };
340
- }
341
- continue;
342
- }
343
- // --- the "done" exit. This is the ONLY path to status "done", and until now its whole condition was
344
- // "this turn had no tool call" — the model's silence, ratified as completion. A write that failed, an
345
- // ask_user nobody answered, a run that changed nothing: all ended here as "done", exit 0, and no
346
- // surface could tell them from finished work (found 2026-09-06: "Done — I created src/a.ts" after the
347
- // write was rejected). Two things now happen here, and they are separate:
348
- // 1. REPRESENT: run_end carries `outstanding` — what the transcript says was left (assessOutstanding).
349
- // Unconditional, so a surface can always say "done · 1 tool call failed in the last turn".
350
- // 2. NUDGE, once per run, on evidence the transcripts actually contain (a failed tool call in the last
351
- // turn, an unanswered question — never "no files changed", which is what every answered question
352
- // looks like): one user-role turn naming exactly what is open and asking the model to finish it or
353
- // say why it is not needed. Both are acceptable answers. The next silence is accepted whatever it
354
- // says: a loop that will not stop is worse than one that stops early. The nudge is a turn like any
355
- // other — the turn, clock and cost ceilings above apply to it, so a spent budget ends as "budget".
356
- const outstanding = assessOutstanding(history.slice(runStart), cfg.todoState?.() ?? null, nudged);
357
- if (cfg.finishCheck !== false && !nudged && (outstanding.failed.length > 0 || outstanding.unansweredAsk)) {
358
- nudged = true;
359
- const text = finishCheckText(outstanding);
360
- const fm: Message = { id: randomUUID(), role: "user", parts: [{ kind: "text", text }], parentId: history.at(-1)?.id ?? null, createdAt: Date.now() };
361
- deps.store.append(fm); history.push(fm);
362
- yield { type: "steer", text };
363
- continue;
364
- }
365
- // --- the verify gate (core/verify-gate.ts): a run that wrote files runs the project's own check before it
366
- // may end "done". Composition with the finish check above is one flag, not two: whichever fires first takes
367
- // the run's single extra turn; if the finish check already fired, the gate still RUNS and represents, but
368
- // never asks again. Nothing configured → nothing runs, and run_end says so ("not verified") rather than
369
- // letting an unchecked change look finished. Nothing written → the gate is not consulted at all.
370
- if (cfg.verify !== undefined && outstanding.writes > 0) {
371
- const gate = cfg.verify;
372
- const refused = gate.resolution?.refused && gate.resolution.refused.length > 0 ? { refused: gate.resolution.refused } : {};
373
- if (gate.resolution === null || gate.resolution.commands.length === 0) {
374
- outstanding.verify = { state: "unconfigured", ...(gate.resolution?.reason ? { reason: gate.resolution.reason } : {}), ...refused };
375
- } else if (outstanding.writes === verifiedAtWrites && lastVerify !== undefined) {
376
- outstanding.verify = lastVerify; // nothing written since that check: its verdict stands, it is not re-run
377
- } else {
378
- yield { type: "verify", command: gate.resolution.commands.join(" && "), state: "running" };
379
- const r = await gate.run(runAc.signal);
380
- if (runAc.signal.aborted) { yield { type: "run_end", status: "stopped", summary: "run aborted" }; return; }
381
- verifiedAtWrites = outstanding.writes;
382
- const state: VerifyState = r.ok ? { state: "passed", command: r.command, ms: r.ms, ...refused }
383
- : r.timedOut ? { state: "timeout", command: r.command, seconds: Math.round(gate.timeoutMs / 1000), ...refused }
384
- : { state: "failed", command: r.command, code: r.code, failure: r.failure, ...refused };
385
- lastVerify = state;
386
- yield { type: "verify", command: r.command, state: state.state, ms: r.ms, detail: verifyDetail(r) };
387
- if (!r.ok && !nudged) {
388
- nudged = true;
389
- const text = verifyNudgeText(r, gate.timeoutMs);
390
- const fm: Message = { id: randomUUID(), role: "user", parts: [{ kind: "text", text }], parentId: history.at(-1)?.id ?? null, createdAt: Date.now() };
391
- deps.store.append(fm); history.push(fm);
392
- yield { type: "steer", text };
393
- continue;
394
- }
395
- outstanding.verify = state;
396
- }
397
- }
398
- // attached only when there IS something to say: a run that answered a question (no files, no failures) ends
399
- // byte-identical to before — "no files changed" is what every answered question looks like, not a finding
400
- const notable = outstanding.failed.length > 0 || outstanding.unansweredAsk || (outstanding.todosOpen ?? 0) > 0 || outstanding.nudged || outstanding.verify !== undefined;
401
- yield { type: "run_end", status: "done", summary: partsText(parts), ...(notable ? { outstanding } : {}) };
402
- return;
403
- }
404
-
405
- // --- tool execution: ctx.signal IS the run controller's signal, so the ONE
406
- // per-run controller reaches child processes by identity (executor G4 seam)
407
- const ctx: ToolContext = {
408
- sessionId: deps.store.id, cwd: deps.cwd ?? process.cwd(), signal: runAc.signal, runId,
409
- spawn: undefined, permissions: { effect: "allow" },
410
- };
411
- const runChild = deps.childRunner;
412
- if (runChild) {
413
- ctx.spawn = async (req) => {
414
- const r = await runChild(req.agent, req.goal, req.vars, depth + 1);
415
- return { agent: req.agent, ok: r.ok, summary: r.summary, usage: r.usage };
416
- };
417
- }
418
- let results = new Map<string, ToolOutput>();
419
- let batchSettled = false;
420
- try {
421
- const batch = deps.registry.dispatchBatch(calls, ctx, deps.hooks, cfg.permissionRules, cfg.approval, emit, cfg.parallelTools, deps.guard);
422
- // stream batch events while tools run; each yield is also a suspension
423
- // point where a consumer .return()/.throw() lands. A mid-batch abort
424
- // breaks the pump so the run settles even if a tool ignores its signal.
425
- let settled = false;
426
- void batch.then(() => { settled = true; }, () => { settled = true; });
427
- while (!settled && !runAc.signal.aborted) {
428
- await Promise.race([batch, sleep(5)]);
429
- if (events.length > 0) yield* flush();
430
- }
431
- // aborted mid-batch: give the killed tools one bounded beat to report their
432
- // real (error) outputs before results are synthesized — opencode waits the
433
- // same way (session/processor.ts:571-575, 250ms) before marking calls interrupted
434
- if (!settled && runAc.signal.aborted) await Promise.race([batch, sleep(250)]);
435
- if (settled) { results = await batch; batchSettled = true; }
436
- if (events.length > 0) yield* flush();
437
- } finally {
438
- // Every exit — normal completion, consumer .return()/.throw() at a pump
439
- // yield, or a mid-batch abort — leaves the store WIRE-WELL-FORMED: the
440
- // assistant message carrying these tool_calls is already appended, so each
441
- // call gets a tool_result here. Ones the batch never delivered are
442
- // synthesized as failed (upstream policy: codex normalize.rs:51-67,
443
- // opencode message-v2.ts:349-360 + processor.ts:576-593).
444
- if (!batchSettled) runAc.abort(); // consumer left mid-batch: kill in-flight tools + subprocess trees
445
- for (const c of calls) {
446
- appendToolResult(deps.store, history, c.id, results.get(c.id) ?? { ok: false, output: batchSettled ? "missing result" : ABORTED_TOOL_RESULT });
447
- }
448
- }
449
- if (runAc.signal.aborted) { yield { type: "run_end", status: "stopped", summary: "run aborted" }; return; }
450
- }
451
- yield { type: "run_end", status: "budget", summary: `max turns (${cfg.maxTurns}) reached` };
452
- }
453
-
454
- /** What the transcript says the run left behind, read at the "done" exit. `failed`/`unansweredAsk` look at
455
- * the LAST turn that had tool calls — the one right before the final text — because that is the failure
456
- * the model walked away from; an earlier failure it went on to retry is not outstanding. `writes` counts
457
- * successful edit/write calls over the whole run. Pure over the history: no disk, no model. */
458
- export function assessOutstanding(history: readonly Message[], todos: { open: number; total: number } | null, nudged: boolean): RunOutstanding {
459
- type Call = Extract<MessagePart, { kind: "tool_call" }>;
460
- const results = new Map<string, { ok: boolean; output: string }>();
461
- for (const m of history) if (m.role === "tool") for (const p of m.parts) if (p.kind === "tool_result") results.set(p.callId, { ok: p.ok, output: p.output });
462
- const describe = (c: Call, output: string): string => `${c.tool}: ${(output.split("\n")[0] ?? "").slice(0, 160)}`;
463
- const pathOf = (c: Call): string | null => { const a = c.args as { path?: unknown; file_path?: unknown } | null; const v = a?.path ?? a?.file_path; return typeof v === "string" ? v : null; };
464
- let writes = 0;
465
- let lastCalls: Call[] = [];
466
- // a file the model tried to change and never managed to: a failed edit/write to a path with no later successful
467
- // edit/write to the same path. Found in the scripted proof — write fails, todo_write marks the item completed,
468
- // "Done": the failure was one turn before the last, masked by bookkeeping, and "last turn only" let it through
469
- const unrecovered = new Map<string, string>();
470
- for (const m of history) {
471
- if (m.role !== "assistant") continue;
472
- const calls = m.parts.filter((p): p is Call => p.kind === "tool_call");
473
- if (calls.length === 0) continue;
474
- lastCalls = calls;
475
- for (const c of calls) {
476
- if (c.tool !== "edit" && c.tool !== "write") continue;
477
- const r = results.get(c.id); const path = pathOf(c) ?? c.id;
478
- if (r?.ok === true) { writes++; unrecovered.delete(path); }
479
- else if (r && !harnessVerdict(r.output)) unrecovered.set(path, describe(c, r.output));
480
- }
481
- }
482
- const failed = [...unrecovered.values()];
483
- let unansweredAsk = false;
484
- for (const c of lastCalls) {
485
- const r = results.get(c.id);
486
- if (r === undefined || r.ok) continue;
487
- if (c.tool === "ask_user") { unansweredAsk = true; continue; }
488
- if (harnessVerdict(r.output)) continue;
489
- const d = describe(c, r.output);
490
- if (!failed.includes(d)) failed.push(d);
491
- }
492
- return { failed, unansweredAsk, writes, ...(todos && todos.total > 0 ? { todosOpen: todos.open, todosTotal: todos.total } : {}), nudged };
493
- }
494
- /** ok:false results that are the harness's or the user's decision, not the model's failure: a permission denial,
495
- * the loop guard's stub (it TOLD the model to stop repeating), an abort. Nudging "continue" past any of these would
496
- * argue with the thing that stopped the call. */
497
- function harnessVerdict(output: string): boolean {
498
- return output.startsWith("Permission denied") || output.includes("loop guard: blocked") || output.startsWith(ABORTED_TOOL_RESULT);
499
- }
500
- export function finishCheckText(o: RunOutstanding): string {
501
- const lines = ["<finish-check>", "You stopped, but this run is not in a finished state — this is a check by the harness, not a message from the user."];
502
- for (const f of o.failed) lines.push(`- a tool call failed and nothing after it recovered from that: ${f}`);
503
- if (o.unansweredAsk) lines.push("- your question to the user was not answered (there is no one to answer it in this run); decide with your best judgment instead of waiting");
504
- if ((o.todosOpen ?? 0) > 0) lines.push(`- your own todo list still has ${o.todosOpen} of ${o.todosTotal} items open`);
505
- lines.push("Either continue and finish the work now, or say plainly what is left undone and why it is not needed. This check runs once per run; your next reply ends it either way.", "</finish-check>");
506
- return lines.join("\n");
507
- }
508
-
509
- /** The one clause a surface appends to "done" — null when there is nothing a person needs to act on.
510
- * "no files changed" is deliberately NOT here: every answered question looks like that, and a warning that
511
- * fires on "what MCPs are configured" trains people to stop reading it. Headless text mode adds it itself,
512
- * where a one-shot run that changed nothing is the whole complaint. */
513
- export function outstandingClause(o: RunOutstanding): string | null {
514
- const parts: string[] = [];
515
- if (o.failed.length > 0) parts.push(`${o.failed.length} failed tool call${o.failed.length === 1 ? "" : "s"} not recovered (${[...new Set(o.failed.map((f) => f.split(":")[0]))].join(", ")})`);
516
- if (o.unansweredAsk) parts.push("its question to you went unanswered");
517
- if ((o.todosOpen ?? 0) > 0) parts.push(`${o.todosOpen} of ${o.todosTotal} items still open`);
518
- if (o.verify !== undefined) parts.push(verifyClause(o.verify));
519
- return parts.length > 0 ? parts.join(" · ") : null;
520
- }
521
- /** how a surface should colour the clause: a passed check is information, everything else is a warning */
522
- export function outstandingTone(o: RunOutstanding): "info" | "warn" {
523
- const onlyPassed = o.failed.length === 0 && !o.unansweredAsk && (o.todosOpen ?? 0) === 0 && o.verify?.state === "passed";
524
- return onlyPassed ? "info" : "warn";
525
- }
526
-
527
- function appendToolResult(store: SessionStore, history: Message[], callId: string, out: { ok: boolean; output: string }): void {
528
- const rm: Message = {
529
- id: randomUUID(), role: "tool",
530
- parts: [{ kind: "tool_result", callId, ok: out.ok, output: out.output }],
531
- parentId: history.at(-1)?.id ?? null, createdAt: Date.now(),
532
- };
533
- store.append(rm); history.push(rm);
534
- }
535
-
536
- export interface TurnOutcome {
537
- parts: MessagePart[]; stopReason: StopReason; usage: TokenUsage; error?: string;
538
- /** model that actually SERVED the turn (router servedBy tag) — unset for unwrapped streams */
539
- origin?: ModelRef;
540
- }
541
-
542
- /** Drive one provider turn: yields the text and reasoning deltas as they arrive (the caller turns
543
- * them into RunEvents), returns the terminal turn as the outcome. tool_call_delta is not surfaced. */
544
- async function* collectTurn(stream: StreamFn, model: ModelRef, messages: Message[], tools?: ToolSchema[], signal?: AbortSignal, deadlineAt?: number): AsyncGenerator<Extract<StreamEvent, { type: "text_delta" | "reasoning_delta" }>, TurnOutcome> {
545
- let outcome: TurnOutcome = { parts: [], stopReason: "end_turn", usage: { input: 0, output: 0 } };
546
- for await (const ev of stream(model, messages, { tools, signal, ...(deadlineAt !== undefined ? { deadlineAt } : {}) })) {
547
- if (ev.type === "text_delta" || ev.type === "reasoning_delta") yield ev;
548
- else if (ev.type === "turn") { outcome = { parts: ev.turn.parts, stopReason: ev.turn.stopReason, usage: ev.turn.usage, error: ev.turn.error, origin: servedBy(ev.turn) }; }
549
- }
550
- return outcome;
551
- }
552
-
553
- export function partsText(parts: MessagePart[]): string {
554
- return parts.filter((p) => p.kind === "text").map((p) => (p as { text: string }).text).join("");
555
- }
556
-
557
- /** Token-bearing text of ALL parts — text, tool_call args, tool_result outputs. Context-size
558
- * accounting must see what the provider sees: a tool-heavy history counted text-only reads
559
- * near zero and never trips compaction / context-health. */
560
- export function partsTokenText(parts: MessagePart[]): string {
561
- return parts.map((p) =>
562
- p.kind === "text" ? p.text
563
- : p.kind === "tool_call" ? `${p.tool} ${JSON.stringify(p.args)}`
564
- : p.kind === "tool_result" ? p.output
565
- : "", // port #34 image parts carry no token text (image token cost is provider-specific; not estimated here)
566
- ).join("\n");
567
- }