rovecode 0.3.2 → 0.4.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (491) hide show
  1. package/CHANGELOG.md +512 -0
  2. package/README.md +33 -45
  3. package/bin/rovecode.js +24 -0
  4. package/dist/cli/app-dybnr56b.js +2 -0
  5. package/dist/cli/ask-user-p8hq4xgj.js +2 -0
  6. package/dist/cli/auth-login-ewpgw5sm.js +2 -0
  7. package/dist/cli/auth-m8p9grty.js +2 -0
  8. package/dist/cli/bench-xv3ypwev.js +9 -0
  9. package/dist/cli/catalog-737wb2s0.js +2 -0
  10. package/dist/cli/cli-arhg40m0.js +2 -0
  11. package/dist/cli/client-cf2pxx8q.js +2 -0
  12. package/dist/cli/commands-3p7e4xxs.js +2 -0
  13. package/dist/cli/connect-3q93d7cb.js +2 -0
  14. package/dist/cli/context-cmd-eqxmxhzq.js +2 -0
  15. package/dist/cli/context-report-hbw9zfes.js +2 -0
  16. package/dist/cli/count-remote-mby98cd0.js +2 -0
  17. package/dist/cli/design-122y0axd.js +2 -0
  18. package/dist/cli/dispatch-b4egzvvh.js +2 -0
  19. package/dist/cli/doctor-x4jkv72e.js +3 -0
  20. package/dist/cli/executor-ftvg6tsy.js +2 -0
  21. package/dist/cli/export-pdgdhkch.js +2 -0
  22. package/dist/cli/files-cez9a96p.js +2 -0
  23. package/dist/cli/gauntlet-r3xxaszc.js +2 -0
  24. package/dist/cli/gauntlet-runner-r515m7kk.js +10 -0
  25. package/dist/cli/gauntlet-wave3-bnkjk2v2.js +5 -0
  26. package/dist/cli/gauntlet-wave4-acs9s60q.js +14 -0
  27. package/dist/cli/hashline-ewg5hbe3.js +2 -0
  28. package/dist/cli/http-n0kehsk8.js +5 -0
  29. package/dist/cli/index-z5qt1s76.js +2 -0
  30. package/dist/cli/install-80mp63kx.js +2 -0
  31. package/dist/cli/loop-12twjcat.js +2 -0
  32. package/dist/cli/main-01pv9206.js +4 -0
  33. package/dist/cli/main-0jys2ccn.js +3 -0
  34. package/dist/cli/main-1ztz6fkj.js +10 -0
  35. package/dist/cli/main-23q7cmww.js +9 -0
  36. package/dist/cli/main-2rzbexn2.js +3 -0
  37. package/dist/cli/main-2wyax8k9.js +9 -0
  38. package/dist/cli/main-2yeveeve.js +6 -0
  39. package/dist/cli/main-2z3dek0b.js +3 -0
  40. package/dist/cli/main-2zgsknth.js +3 -0
  41. package/dist/cli/main-45ejth3a.js +4 -0
  42. package/dist/cli/main-45rn3trk.js +22 -0
  43. package/dist/cli/main-4p4e2w7x.js +4 -0
  44. package/dist/cli/main-4y0tnfpa.js +16 -0
  45. package/dist/cli/main-5py0rkmc.js +4 -0
  46. package/dist/cli/main-6dtqmbt6.js +7 -0
  47. package/dist/cli/main-6h9x282m.js +4 -0
  48. package/dist/cli/main-6vjeds42.js +3 -0
  49. package/dist/cli/main-78gq4bt9.js +6 -0
  50. package/dist/cli/main-7jd5vh3x.js +4 -0
  51. package/dist/cli/main-7kt6r53y.js +4 -0
  52. package/dist/cli/main-8c1tbazx.js +58 -0
  53. package/dist/cli/main-9a9rnh47.js +19 -0
  54. package/dist/cli/main-9ht36z12.js +3 -0
  55. package/dist/cli/main-a2yfvcy9.js +7 -0
  56. package/dist/cli/main-a3f51n0x.js +5 -0
  57. package/dist/cli/main-b8zq261k.js +3 -0
  58. package/dist/cli/main-bxtvnf6d.js +13 -0
  59. package/dist/cli/main-edxc3yzt.js +4 -0
  60. package/dist/cli/main-evgz4mp5.js +21 -0
  61. package/dist/cli/main-f33fc5je.js +9 -0
  62. package/dist/cli/main-fvnpq46y.js +12 -0
  63. package/dist/cli/main-gbbty4d4.js +3 -0
  64. package/dist/cli/main-gth53dnt.js +25 -0
  65. package/dist/cli/main-hqbz10aw.js +9 -0
  66. package/dist/cli/main-hrrvcfan.js +38 -0
  67. package/dist/cli/main-hzwtsb2m.js +5 -0
  68. package/dist/cli/main-j7ttv0sd.js +34 -0
  69. package/dist/cli/main-jak598k9.js +5 -0
  70. package/dist/cli/main-kba6zeyd.js +6 -0
  71. package/dist/cli/main-kwwsz6rq.js +3 -0
  72. package/dist/cli/main-m8vm17zq.js +3 -0
  73. package/dist/cli/main-mg4f96e1.js +3 -0
  74. package/dist/cli/main-mg9b20ac.js +18 -0
  75. package/dist/cli/main-mgb9ccnx.js +3 -0
  76. package/dist/cli/main-mjt2p7aj.js +3 -0
  77. package/dist/cli/main-n6qrdbmy.js +3 -0
  78. package/dist/cli/main-na7wse0x.js +5 -0
  79. package/dist/cli/main-nqveez48.js +4 -0
  80. package/dist/cli/main-ntqef02r.js +10 -0
  81. package/dist/cli/main-nvc3yjay.js +136 -0
  82. package/dist/cli/main-p0cfn6nr.js +16 -0
  83. package/dist/cli/main-qj2djy17.js +19 -0
  84. package/dist/cli/main-qsevpgsv.js +3 -0
  85. package/dist/cli/main-qvarybsp.js +3 -0
  86. package/dist/cli/main-rebtt91r.js +5 -0
  87. package/dist/cli/main-rpg7h8mb.js +3 -0
  88. package/dist/cli/main-rsy72qmw.js +15 -0
  89. package/dist/cli/main-rvetps99.js +18 -0
  90. package/dist/cli/main-s4bb0jav.js +3 -0
  91. package/dist/cli/main-s9v8k74e.js +3 -0
  92. package/dist/cli/main-tjvwmscs.js +3 -0
  93. package/dist/cli/main-tkgarpjj.js +4 -0
  94. package/dist/cli/main-v8y60bb2.js +3 -0
  95. package/dist/cli/main-vhrrq337.js +3 -0
  96. package/dist/cli/main-vp2dfb7s.js +4 -0
  97. package/dist/cli/main-vqbr22sz.js +8 -0
  98. package/dist/cli/main-vxnwe5xx.js +18 -0
  99. package/dist/cli/main-wgph00xf.js +5 -0
  100. package/dist/cli/main-wk2csfnj.js +5 -0
  101. package/dist/cli/main-wm997zjx.js +3 -0
  102. package/dist/cli/main-wpkyraxh.js +3 -0
  103. package/dist/cli/main-wqt32p5x.js +4 -0
  104. package/dist/cli/main-x9ct6y1a.js +3 -0
  105. package/dist/cli/main-xfekqh9m.js +7 -0
  106. package/dist/cli/main-xt9zc3n6.js +7 -0
  107. package/dist/cli/main-xx2z3zh5.js +4 -0
  108. package/dist/cli/main-y5c82rxr.js +3 -0
  109. package/dist/cli/main-yrjt2sqt.js +14 -0
  110. package/dist/cli/main-ys6zj3yr.js +3 -0
  111. package/dist/cli/main-ywbxshqc.js +8 -0
  112. package/dist/cli/main-z13755t8.js +25 -0
  113. package/dist/cli/main-zc7pyrbj.js +4 -0
  114. package/dist/cli/main.js +279 -0
  115. package/dist/cli/market-cmd-bm5xvn9f.js +5 -0
  116. package/dist/cli/mcp-login-bthtfpt7.js +2 -0
  117. package/dist/cli/mcp-market-cmd-mbeshfyd.js +2 -0
  118. package/dist/cli/notify-54v5z9dz.js +2 -0
  119. package/dist/cli/oauth-g5gme95c.js +2 -0
  120. package/dist/cli/output-satndjap.js +16 -0
  121. package/dist/cli/profiles-sfhpbq3m.js +2 -0
  122. package/dist/cli/provider-config-hv3xtdt4.js +2 -0
  123. package/dist/cli/provider-kwzq6g84.js +2 -0
  124. package/dist/cli/registry-fh0hdnyn.js +2 -0
  125. package/dist/cli/registry-y1y8e94r.js +2 -0
  126. package/dist/cli/repl-t4z03mqq.js +11 -0
  127. package/dist/cli/resume-fqt4chg8.js +2 -0
  128. package/dist/cli/run-flags-rysbag9t.js +2 -0
  129. package/dist/cli/runtime-j19fjbsa.js +2 -0
  130. package/dist/cli/sandbox-config-g4qxd7y5.js +2 -0
  131. package/dist/cli/server-r0b6bksk.js +5 -0
  132. package/dist/cli/session-arg-txmn5g4x.js +2 -0
  133. package/dist/cli/session-ed250d9j.js +2 -0
  134. package/dist/cli/sessions-cmd-adw7svfn.js +7 -0
  135. package/dist/cli/settings-y9rzcqx8.js +2 -0
  136. package/dist/cli/setup-jmbr11j0.js +2 -0
  137. package/dist/cli/sextant-smoke-tcth0vea.js +5 -0
  138. package/dist/cli/skills-cmd-zbdy99v6.js +2 -0
  139. package/dist/cli/smoke-1bg937kx.js +8 -0
  140. package/dist/cli/start-chat-p01cdks3.js +12 -0
  141. package/dist/cli/stream-4wmyaypz.js +2 -0
  142. package/dist/cli/task-eg4s093s.js +2 -0
  143. package/dist/cli/tasks-12v9rr9k.js +2 -0
  144. package/dist/cli/thinking-a5ngvqyh.js +2 -0
  145. package/dist/cli/todo-1wxpcecx.js +2 -0
  146. package/dist/cli/tools-2ftsya7w.js +2 -0
  147. package/dist/cli/tools-x1tj4fxm.js +2 -0
  148. package/dist/cli/trust-cmd-hccxehzb.js +2 -0
  149. package/dist/cli/update-check-ygt3vd7m.js +2 -0
  150. package/dist/cli/update-cmd-v23qhr8c.js +2 -0
  151. package/dist/cli/voice-g1gtck92.js +2 -0
  152. package/dist/cli/webfetch-0nnrjgb5.js +2 -0
  153. package/dist/cli/websearch-f0vr2p7d.js +2 -0
  154. package/dist/cli/workspace-9rq1w4ta.js +2 -0
  155. package/dist/lib/index.js +62 -0
  156. package/dist/lib/models-index.json +1 -0
  157. package/dist/lib/plugins.js +6 -0
  158. package/dist/lib/providers.js +17 -0
  159. package/dist/lib/public-api.js +20 -0
  160. package/dist/rovecode.exe +4 -0
  161. package/package.json +38 -11
  162. package/bin/rovecode.ts +0 -21
  163. package/src/acp/server.ts +0 -374
  164. package/src/cli/auth-login.ts +0 -122
  165. package/src/cli/connect.ts +0 -244
  166. package/src/cli/context-cmd.ts +0 -199
  167. package/src/cli/dispatch.ts +0 -82
  168. package/src/cli/doctor.ts +0 -362
  169. package/src/cli/export.ts +0 -276
  170. package/src/cli/help.ts +0 -293
  171. package/src/cli/is-tui-invocation.ts +0 -8
  172. package/src/cli/main.ts +0 -583
  173. package/src/cli/market-cmd.ts +0 -658
  174. package/src/cli/mcp-login.ts +0 -141
  175. package/src/cli/mcp-market-cmd.ts +0 -302
  176. package/src/cli/output.ts +0 -382
  177. package/src/cli/repl.ts +0 -250
  178. package/src/cli/repomap-root.ts +0 -14
  179. package/src/cli/resume.ts +0 -57
  180. package/src/cli/run-flags.ts +0 -43
  181. package/src/cli/run-limits.ts +0 -78
  182. package/src/cli/runtime.ts +0 -931
  183. package/src/cli/session-arg.ts +0 -30
  184. package/src/cli/sessions-cmd.ts +0 -145
  185. package/src/cli/setup.ts +0 -153
  186. package/src/cli/skills-cmd.ts +0 -194
  187. package/src/cli/start-chat.ts +0 -65
  188. package/src/cli/trust-cmd.ts +0 -52
  189. package/src/coding/bash.ts +0 -148
  190. package/src/coding/checkpoints.ts +0 -327
  191. package/src/coding/diff.ts +0 -138
  192. package/src/coding/files.ts +0 -341
  193. package/src/coding/hashline.ts +0 -274
  194. package/src/coding/lsp-gate.ts +0 -254
  195. package/src/coding/lsp-servers.ts +0 -147
  196. package/src/coding/lsp.ts +0 -283
  197. package/src/coding/repomap-cache.ts +0 -99
  198. package/src/coding/repomap-files.ts +0 -192
  199. package/src/coding/repomap.ts +0 -481
  200. package/src/core/agents.ts +0 -255
  201. package/src/core/compaction.ts +0 -259
  202. package/src/core/config.ts +0 -289
  203. package/src/core/context-report.ts +0 -228
  204. package/src/core/context.ts +0 -60
  205. package/src/core/count-remote.ts +0 -107
  206. package/src/core/execpolicy-rules.ts +0 -196
  207. package/src/core/execpolicy.ts +0 -385
  208. package/src/core/executor.ts +0 -454
  209. package/src/core/guardrails.ts +0 -400
  210. package/src/core/hooks.ts +0 -411
  211. package/src/core/images.ts +0 -230
  212. package/src/core/intro.ts +0 -266
  213. package/src/core/loop.ts +0 -567
  214. package/src/core/modes.ts +0 -372
  215. package/src/core/orchestrator.ts +0 -245
  216. package/src/core/proc-group.ts +0 -48
  217. package/src/core/project-trust.ts +0 -98
  218. package/src/core/reflection.ts +0 -165
  219. package/src/core/sandbox-config.ts +0 -186
  220. package/src/core/session-id.ts +0 -24
  221. package/src/core/session-images.ts +0 -73
  222. package/src/core/session-ops.ts +0 -183
  223. package/src/core/session-text.ts +0 -29
  224. package/src/core/session.ts +0 -469
  225. package/src/core/settings.ts +0 -170
  226. package/src/core/tasks.ts +0 -646
  227. package/src/core/token-scale.ts +0 -108
  228. package/src/core/tools.ts +0 -309
  229. package/src/core/trust.ts +0 -104
  230. package/src/core/types.ts +0 -330
  231. package/src/core/update-check.ts +0 -171
  232. package/src/core/usage.ts +0 -204
  233. package/src/core/validate.ts +0 -121
  234. package/src/core/verify-gate.ts +0 -159
  235. package/src/core/verify.ts +0 -236
  236. package/src/core/voice.ts +0 -158
  237. package/src/core/win-job.ts +0 -183
  238. package/src/core/workspace.ts +0 -184
  239. package/src/design/audit.ts +0 -797
  240. package/src/design/direction.ts +0 -190
  241. package/src/design/rules.ts +0 -157
  242. package/src/eval/bench.ts +0 -150
  243. package/src/eval/gauntlet-runner.ts +0 -215
  244. package/src/eval/gauntlet-support.ts +0 -84
  245. package/src/eval/gauntlet-wave3.ts +0 -269
  246. package/src/eval/gauntlet-wave4.ts +0 -217
  247. package/src/eval/gauntlet.ts +0 -253
  248. package/src/index.ts +0 -17
  249. package/src/lanes/agy.ts +0 -95
  250. package/src/lanes/approval.ts +0 -24
  251. package/src/lanes/claude.ts +0 -129
  252. package/src/lanes/codex.ts +0 -127
  253. package/src/lanes/events.ts +0 -130
  254. package/src/lanes/job.ts +0 -142
  255. package/src/lanes/opencode.ts +0 -122
  256. package/src/lanes/process.ts +0 -184
  257. package/src/lanes/progress.ts +0 -183
  258. package/src/lanes/registry.ts +0 -178
  259. package/src/lanes/runner.ts +0 -124
  260. package/src/lanes/types.ts +0 -112
  261. package/src/market/catalogs/mcp-docs.json +0 -111
  262. package/src/market/catalogs/plugins.json +0 -111
  263. package/src/market/catalogs/skills.json +0 -478
  264. package/src/market/clone.ts +0 -72
  265. package/src/market/context-cost.ts +0 -121
  266. package/src/market/digest.ts +0 -106
  267. package/src/market/index.ts +0 -22
  268. package/src/market/install.ts +0 -578
  269. package/src/market/manifest.ts +0 -187
  270. package/src/market/prereq.ts +0 -145
  271. package/src/market/registry.ts +0 -363
  272. package/src/market/resolve.ts +0 -111
  273. package/src/market/types.ts +0 -236
  274. package/src/market/validate.ts +0 -227
  275. package/src/mcp/client.ts +0 -449
  276. package/src/mcp/config.ts +0 -252
  277. package/src/mcp/local-package.ts +0 -211
  278. package/src/mcp/market-catalog.ts +0 -84
  279. package/src/mcp/market-install.ts +0 -289
  280. package/src/mcp/market.ts +0 -362
  281. package/src/mcp/oauth.ts +0 -251
  282. package/src/mcp/prompts-resources.ts +0 -249
  283. package/src/mcp/shared.ts +0 -149
  284. package/src/mcp/status.ts +0 -67
  285. package/src/mcp/tools.ts +0 -275
  286. package/src/mcp/transport.ts +0 -122
  287. package/src/mcp/trust.ts +0 -25
  288. package/src/memory/blocks.ts +0 -278
  289. package/src/memory/recall.ts +0 -355
  290. package/src/memory/scope.ts +0 -182
  291. package/src/memory/store.ts +0 -105
  292. package/src/memory/tools.ts +0 -99
  293. package/src/plugins/cli.ts +0 -119
  294. package/src/plugins/discover.ts +0 -108
  295. package/src/plugins/index.ts +0 -50
  296. package/src/plugins/install.ts +0 -184
  297. package/src/plugins/load.ts +0 -124
  298. package/src/plugins/manifest.ts +0 -92
  299. package/src/plugins/state.ts +0 -83
  300. package/src/providers/auth.ts +0 -408
  301. package/src/providers/cache.ts +0 -223
  302. package/src/providers/catalog-local.ts +0 -160
  303. package/src/providers/catalog.ts +0 -421
  304. package/src/providers/middleware-context.ts +0 -86
  305. package/src/providers/middleware.ts +0 -373
  306. package/src/providers/model-list.ts +0 -23
  307. package/src/providers/oauth/common.ts +0 -105
  308. package/src/providers/oauth/device-code.ts +0 -107
  309. package/src/providers/oauth/github-copilot.ts +0 -146
  310. package/src/providers/oauth/loopback.ts +0 -158
  311. package/src/providers/oauth/openai.ts +0 -163
  312. package/src/providers/oauth/openrouter.ts +0 -89
  313. package/src/providers/oauth/pkce.ts +0 -45
  314. package/src/providers/oauth/registry.ts +0 -39
  315. package/src/providers/oauth/seam.ts +0 -89
  316. package/src/providers/profile-glm53.ts +0 -111
  317. package/src/providers/profile-sonnet5-persona.ts +0 -65
  318. package/src/providers/profile-sonnet5-voice.ts +0 -23
  319. package/src/providers/profiles.ts +0 -156
  320. package/src/providers/provider-config.ts +0 -311
  321. package/src/providers/registry.ts +0 -333
  322. package/src/providers/responses.ts +0 -209
  323. package/src/providers/retry.ts +0 -234
  324. package/src/providers/router.ts +0 -294
  325. package/src/providers/sse.ts +0 -26
  326. package/src/providers/stream-errors.ts +0 -117
  327. package/src/providers/stream.ts +0 -566
  328. package/src/providers/thinking.ts +0 -189
  329. package/src/providers/wire-messages.ts +0 -129
  330. package/src/providers/wire-responses.ts +0 -79
  331. package/src/providers/wire-select.ts +0 -53
  332. package/src/server/http.ts +0 -291
  333. package/src/server/openapi.ts +0 -246
  334. package/src/sextant/card-hits.ts +0 -102
  335. package/src/sextant/card-keys.ts +0 -55
  336. package/src/sextant/context-source.ts +0 -157
  337. package/src/sextant/crew-cards.ts +0 -350
  338. package/src/sextant/draw-agents.ts +0 -273
  339. package/src/sextant/draw-code.ts +0 -388
  340. package/src/sextant/draw-context.ts +0 -222
  341. package/src/sextant/draw-frame.ts +0 -164
  342. package/src/sextant/draw-market.ts +0 -573
  343. package/src/sextant/draw-messages.ts +0 -386
  344. package/src/sextant/draw-pet.ts +0 -230
  345. package/src/sextant/draw-plan.ts +0 -187
  346. package/src/sextant/draw-tabs.ts +0 -85
  347. package/src/sextant/draw-util.ts +0 -65
  348. package/src/sextant/draw-wizard.ts +0 -378
  349. package/src/sextant/engine.ts +0 -230
  350. package/src/sextant/frame-hits.ts +0 -25
  351. package/src/sextant/frame.ts +0 -101
  352. package/src/sextant/git-status.ts +0 -197
  353. package/src/sextant/grid.ts +0 -59
  354. package/src/sextant/input.ts +0 -119
  355. package/src/sextant/keys.ts +0 -521
  356. package/src/sextant/layout.ts +0 -86
  357. package/src/sextant/local-commands.ts +0 -169
  358. package/src/sextant/market-source.ts +0 -287
  359. package/src/sextant/mentions.ts +0 -200
  360. package/src/sextant/message-hits.ts +0 -26
  361. package/src/sextant/model.ts +0 -387
  362. package/src/sextant/overlays.ts +0 -456
  363. package/src/sextant/panel-hits.ts +0 -38
  364. package/src/sextant/pet.ts +0 -399
  365. package/src/sextant/screen.ts +0 -324
  366. package/src/sextant/scroll-hits.ts +0 -66
  367. package/src/sextant/scrollbar.ts +0 -82
  368. package/src/sextant/sextant-bridge.ts +0 -174
  369. package/src/sextant/sextant-cards.ts +0 -142
  370. package/src/sextant/sextant-diff-base.ts +0 -63
  371. package/src/sextant/sextant-files.ts +0 -154
  372. package/src/sextant/sextant-frame-loop.ts +0 -335
  373. package/src/sextant/sextant-renderer.ts +0 -574
  374. package/src/sextant/sextant-repo.ts +0 -140
  375. package/src/sextant/theme.ts +0 -66
  376. package/src/sextant/tool-rows.ts +0 -189
  377. package/src/sextant/types.ts +0 -493
  378. package/src/skills/index.ts +0 -387
  379. package/src/skills/pack.ts +0 -220
  380. package/src/skills/spec.ts +0 -162
  381. package/src/skills/tools.ts +0 -69
  382. package/src/skills/versioned.ts +0 -227
  383. package/src/telemetry/otel-export.ts +0 -122
  384. package/src/telemetry/otel-lanes.ts +0 -89
  385. package/src/telemetry/otel-logs.ts +0 -131
  386. package/src/telemetry/otel-metrics.ts +0 -136
  387. package/src/telemetry/otel.ts +0 -397
  388. package/src/telemetry/otlp.ts +0 -76
  389. package/src/tools/ask-user.ts +0 -156
  390. package/src/tools/bash-bg.ts +0 -94
  391. package/src/tools/bash-jobs.ts +0 -237
  392. package/src/tools/design.ts +0 -151
  393. package/src/tools/evalcell.ts +0 -338
  394. package/src/tools/html-text.ts +0 -139
  395. package/src/tools/provider.ts +0 -149
  396. package/src/tools/task.ts +0 -250
  397. package/src/tools/todo.ts +0 -320
  398. package/src/tools/webfetch.ts +0 -332
  399. package/src/tools/websearch.ts +0 -359
  400. package/src/tui/agents-cmd.ts +0 -41
  401. package/src/tui/app.ts +0 -749
  402. package/src/tui/attach.ts +0 -127
  403. package/src/tui/boot-notes.ts +0 -41
  404. package/src/tui/builtin-prompts.ts +0 -59
  405. package/src/tui/checkpoints-cmd.ts +0 -70
  406. package/src/tui/clipboard-image.ts +0 -81
  407. package/src/tui/clipboard.ts +0 -78
  408. package/src/tui/commands.ts +0 -283
  409. package/src/tui/config-view.ts +0 -53
  410. package/src/tui/context-cmds.ts +0 -282
  411. package/src/tui/cost.ts +0 -108
  412. package/src/tui/crash-guard.ts +0 -173
  413. package/src/tui/focus-terminal.ts +0 -34
  414. package/src/tui/git-cmds.ts +0 -273
  415. package/src/tui/git-plain.ts +0 -58
  416. package/src/tui/info-cmd.ts +0 -150
  417. package/src/tui/input-plain.ts +0 -76
  418. package/src/tui/mcp-cmd.ts +0 -128
  419. package/src/tui/memory-note.ts +0 -77
  420. package/src/tui/modes-cmd.ts +0 -45
  421. package/src/tui/notify-seq.ts +0 -100
  422. package/src/tui/notify.ts +0 -318
  423. package/src/tui/overlays.ts +0 -97
  424. package/src/tui/pi-renderer.ts +0 -428
  425. package/src/tui/providers-cmd.ts +0 -377
  426. package/src/tui/reasoning-view.ts +0 -56
  427. package/src/tui/renderer.ts +0 -128
  428. package/src/tui/replay-marker.ts +0 -29
  429. package/src/tui/session-cmd.ts +0 -148
  430. package/src/tui/session-manage.ts +0 -95
  431. package/src/tui/sextant-attach.ts +0 -102
  432. package/src/tui/sextant-io.ts +0 -202
  433. package/src/tui/sextant-smoke.ts +0 -110
  434. package/src/tui/shell-cmd.ts +0 -158
  435. package/src/tui/smoke.ts +0 -72
  436. package/src/tui/staged-terminal.ts +0 -50
  437. package/src/tui/startup.ts +0 -12
  438. package/src/tui/theme.ts +0 -59
  439. package/src/tui/todo-label.ts +0 -7
  440. package/src/tui/trust-card.ts +0 -107
  441. package/src/tui/tui-commands.ts +0 -87
  442. package/tsconfig.json +0 -30
  443. package/vendor/pi-tui/LICENSE +0 -21
  444. package/vendor/pi-tui/PATCHES.md +0 -12
  445. package/vendor/pi-tui/PROVENANCE.md +0 -12
  446. package/vendor/pi-tui/README.upstream.md +0 -854
  447. package/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  448. package/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  449. package/vendor/pi-tui/src/alt-screen-search.ts +0 -158
  450. package/vendor/pi-tui/src/autocomplete.ts +0 -827
  451. package/vendor/pi-tui/src/components/alt-screen-flash.ts +0 -52
  452. package/vendor/pi-tui/src/components/box.ts +0 -138
  453. package/vendor/pi-tui/src/components/cancellable-loader.ts +0 -41
  454. package/vendor/pi-tui/src/components/editor.ts +0 -2364
  455. package/vendor/pi-tui/src/components/h-stack.ts +0 -45
  456. package/vendor/pi-tui/src/components/image.ts +0 -128
  457. package/vendor/pi-tui/src/components/input.ts +0 -448
  458. package/vendor/pi-tui/src/components/loader.ts +0 -93
  459. package/vendor/pi-tui/src/components/markdown.ts +0 -1016
  460. package/vendor/pi-tui/src/components/scroll-view.ts +0 -217
  461. package/vendor/pi-tui/src/components/select-list.ts +0 -230
  462. package/vendor/pi-tui/src/components/settings-list.ts +0 -277
  463. package/vendor/pi-tui/src/components/spacer.ts +0 -29
  464. package/vendor/pi-tui/src/components/stack.ts +0 -155
  465. package/vendor/pi-tui/src/components/text.ts +0 -108
  466. package/vendor/pi-tui/src/components/truncated-text.ts +0 -66
  467. package/vendor/pi-tui/src/components/v-stack.ts +0 -34
  468. package/vendor/pi-tui/src/editor-component.ts +0 -75
  469. package/vendor/pi-tui/src/fuzzy.ts +0 -138
  470. package/vendor/pi-tui/src/index.ts +0 -149
  471. package/vendor/pi-tui/src/keybindings.ts +0 -321
  472. package/vendor/pi-tui/src/keys.ts +0 -1402
  473. package/vendor/pi-tui/src/kill-ring.ts +0 -47
  474. package/vendor/pi-tui/src/latex.ts +0 -1381
  475. package/vendor/pi-tui/src/layout-node.ts +0 -52
  476. package/vendor/pi-tui/src/layout.ts +0 -411
  477. package/vendor/pi-tui/src/native-modifiers.ts +0 -60
  478. package/vendor/pi-tui/src/native-module-path.ts +0 -32
  479. package/vendor/pi-tui/src/stdin-buffer.ts +0 -445
  480. package/vendor/pi-tui/src/terminal-colors.ts +0 -74
  481. package/vendor/pi-tui/src/terminal-image.ts +0 -701
  482. package/vendor/pi-tui/src/terminal.ts +0 -554
  483. package/vendor/pi-tui/src/tui-alt-screen.ts +0 -1379
  484. package/vendor/pi-tui/src/tui-main-screen.ts +0 -655
  485. package/vendor/pi-tui/src/tui.ts +0 -1264
  486. package/vendor/pi-tui/src/undo-stack.ts +0 -29
  487. package/vendor/pi-tui/src/utils.ts +0 -1327
  488. package/vendor/pi-tui/src/word-navigation.ts +0 -118
  489. package/vendor/pi-tui/test/test-themes.ts +0 -39
  490. package/vendor/pi-tui/test/virtual-terminal.ts +0 -219
  491. /package/{src/providers → dist/cli}/models-index.json +0 -0
package/src/core/tasks.ts DELETED
@@ -1,646 +0,0 @@
1
- /** Background subagents as jobs (port #26): a bounded FIFO job manager over the ONE
2
- * agent loop. Every task runs through orchestrator.ts runChild (ADR-009), which runs
3
- * agentLoop (ADR-003) — this file only schedules, collects results and notifies. It is
4
- * NOT a second loop generation (research/anti_patterns.md:13; ADR-013 rejects lanes
5
- * that are not child sessions/jobs).
6
- *
7
- * Port #47: an external agentic-CLI lane (claude / codex / opencode / agy) is a JOB HERE TOO, not a
8
- * second engine — TaskInfo.kind "external", run by lanes/job.ts in its own worktree, gated by
9
- * ROVECODE_LANES_ALLOW, settling through the same finish() as every child. The reason it is worth
10
- * saying twice: a nested coding CLI already ran fine through the bash tool, but it ran INVISIBLY —
11
- * the approval card could not name it, the directory it worked in, or the account it billed. Made a
12
- * job, it inherits all three from machinery that already exists.
13
- *
14
- * BATCHES: bindRun() is called exactly once per run by every surface, so "started under the same
15
- * run" is a grouping already present in the data — TaskInfo.batch is stamped there and a crew of
16
- * agents settles into ONE note instead of N (noteFor → batchNote). Nothing here guesses at a group:
17
- * a time bucket or a shared label prefix would put two unrelated runs a second apart in one card.
18
- * The summary exists for the part of a finished crew nobody can reconstruct — two agents that wrote
19
- * the SAME file, named with the path, from the patches that were merged back — and it leads with a
20
- * failure rather than footnoting one under a count of successes.
21
- *
22
- * Pattern source: opencode packages/core/src/background-job.ts (MIT, snapshot
23
- * research/source_snapshots/opencode-2026 @ ebece6e) — a process-local, deliberately
24
- * non-durable job registry (:113-119); Status running|completed|error|cancelled (:7);
25
- * Info {id,type,title,status,started_at,completed_at,output,error} (:9-19); start forks
26
- * the run and settle() derives the terminal status from the exit (:126-171, :202-254);
27
- * wait with an optional timeout returns the snapshot on timeout (:292-301); cancel marks
28
- * the job and closes its scope (:337-358). Its task tool (packages/opencode/src/tool/
29
- * task.ts) injects a synthetic message into the PARENT session when the job settles
30
- * (:227-265 inject/notify) and tells the model not to poll (:31-35).
31
- * Departures: opencode starts every job at once (no bound); here a FIFO queue bounds
32
- * concurrency (default 3, env ROVECODE_TASKS_MAX) and a RUNNING task that waits on a QUEUED
33
- * one lends it its slot so nested pools cannot deadlock. Their notification is a new
34
- * prompt on the parent session; ours is a push into the parent's SteeringQueue, which
35
- * the loop drains before its next model call (loop.ts:136) — the loop stays untouched. */
36
-
37
- import { SteeringQueue } from "./loop.ts";
38
- import { DEFAULT_MAX_DEPTH, preflightSpawn, runChild, type ChildRunnerDeps } from "./orchestrator.ts";
39
- import type { SpawnRequest, SpawnResult, TokenUsage } from "./types.ts";
40
- import { isAdapterId, lanePermissions, laneRefusal, runLaneJob, type AdapterId, type LaneJobDeps, type LaneJobResult } from "../lanes/job.ts";
41
- // ONE patch parser and ONE progress shape for the whole codebase: the lane runner already folds
42
- // counters and reads file lists out of a diff, and a second reading of the same patch here is a
43
- // second thing to keep true.
44
- import { filesFromPatch, type LaneProgress } from "../lanes/progress.ts";
45
- // the card's own sentence, so what a lane is ANNOUNCED with cannot drift from the flags it got
46
- import { laneStartNote } from "../lanes/registry.ts";
47
-
48
- export type TaskId = string;
49
- export type TaskStatus = "queued" | "running" | "done" | "failed" | "cancelled";
50
-
51
- export interface TaskInfo {
52
- id: TaskId;
53
- label: string;
54
- agent: string;
55
- /** bounded preview of the goal (≤200 chars) */
56
- goal: string;
57
- isolated: boolean;
58
- /** the child's depth (parent depth + 1; root-started tasks run at 1) */
59
- depth: number;
60
- status: TaskStatus;
61
- createdAt: number;
62
- startedAt?: number;
63
- finishedAt?: number;
64
- /** the child's final text (done) — bounded by runChild (≤4000 chars) */
65
- summary?: string;
66
- /** failure reason (failed) or "cancelled" */
67
- error?: string;
68
- usage?: TokenUsage;
69
- /** isolated children: line count of the patch merged back into the parent tree */
70
- patchLines?: number;
71
- /** #47: "external" when this task is an agentic-CLI lane rather than one of our own agents */
72
- kind?: "agent" | "external";
73
- /** #47: the adapter id of an external lane (claude / codex / opencode / agy) */
74
- lane?: AdapterId;
75
- /** #47: what the lane was permitted to do, in the same words the approval card used */
76
- permissions?: string;
77
- /** #47: the lane CLI's own exit code. Three states, deliberately: a number is what the CLI returned,
78
- * `null` is a lane that ended without one (killed, or the process never reported), and absent means
79
- * this task is not a lane at all. Collapsing null into absent would make "we could not read it" look
80
- * identical to "there was nothing to read". */
81
- laneExit?: number | null;
82
- /** #47: the model the lane actually ran with, when the CLI reported one */
83
- laneModel?: string;
84
- /** #47: the lane CLI's own session id, for anyone who wants to resume it in that CLI */
85
- laneSession?: string;
86
- /** the run this task was started under, stamped from bindRun(): everything started under one
87
- * run is ONE batch. The run is the only grouping already in the data — a time bucket or a
88
- * shared label prefix would guess, and two unrelated runs a second apart would read as one
89
- * piece of work. Absent for a task started before any run was bound. */
90
- batch?: string;
91
- /** that run's own label (one line, ≤60 chars — bounded HERE, not by whatever draws it) */
92
- batchLabel?: string;
93
- /** what the task has DONE, folded by the runner (lanes/progress.ts): tool calls, confirmed writes,
94
- * usage. ABSENT until a runner reports something — an unreported lane must not read as `0 calls`,
95
- * which is why this is never defaulted to an empty tally here (its own `usage` keeps the same rule
96
- * one level down). Set live while running, replaced at finish by the measured list. */
97
- progress?: LaneProgress;
98
- /** repo-relative paths this task changed in the PARENT tree, from its merged patch — the diff is
99
- * the tree, so this is measured rather than claimed. Set only when the patch was merged back: a
100
- * failed or cancelled child's patch is returned and never applied (orchestrator.ts:185), so it
101
- * changed nothing here and cannot collide with anything — and neither did one whose `git apply` was
102
- * REJECTED, which is a different thing from failing and is why this is not `patchLines !== undefined`.
103
- * Capped like progress.filesWritten. */
104
- files?: readonly string[];
105
- }
106
-
107
- export interface StartOptions {
108
- label?: string;
109
- /** depth of the loop starting this task (root = 0); the child runs at depth + 1,
110
- * mirroring the loop's own ctx.spawn contract (loop.ts:267) */
111
- parentDepth?: number;
112
- /** where the completion note lands; default = the queue attached to the manager */
113
- notify?: SteeringQueue;
114
- /** id of the RUNNING task that starts this one (nested) — enables slot lending */
115
- caller?: TaskId;
116
- /** the run that OWNS this task: its abort cancels the task (nested: the parent task's
117
- * own signal, so cancellation cascades). Default = the signal bound via bindRun(). */
118
- owner?: AbortSignal;
119
- /** the WORKING TREE of the loop starting this task (ChildContext.dir → tools/task.ts parentDir).
120
- * An external lane builds its worktree from here and merges its patch back here, so a lane
121
- * started by an ISOLATED child must be given the child's own tree: with the top-level root it
122
- * would diff and patch the user's live repo, and the child's own patch would not show that it
123
- * had. Unset = deps.rootDir, which is the truth for a root start and what every existing caller
124
- * relies on. */
125
- parentDir?: string;
126
- /** the tool names of the registry the STARTING tool serves (nested `task` from a restricted agent) → the child's
127
- * ChildContext.parentTools, so a definition's allow-list is transitive (core/agents.ts restrictTools clamps to it) */
128
- parentTools?: ReadonlySet<string>;
129
- }
130
-
131
- /** `childPolicy` (fix-wave MED-2): how the child's rules derive from the starting run's —
132
- * "gated" when that config carries a prompt rule (children turn prompt→deny, orchestrator
133
- * deriveChildRules: read-only unless allow rules cover an action), "open" otherwise (yolo).
134
- * The `task` tool's start output says so, for the approver and the model. */
135
- export type StartResult = { ok: true; id: TaskId; childPolicy: "gated" | "open" } | { ok: false; reason: string };
136
-
137
- export interface WaitOptions {
138
- /** max wait in ms; undefined = until settled (hold an abort signal); 0 = snapshot now */
139
- timeoutMs?: number;
140
- signal?: AbortSignal;
141
- /** the running task doing the waiting — its slot is lent to a queued waited-on task */
142
- caller?: TaskId;
143
- }
144
-
145
- export interface TaskManagerOptions {
146
- /** child runner deps, resolved at EACH start so defs/config follow the parent's
147
- * latest run; null = no provider configured (start refuses) */
148
- deps: () => ChildRunnerDeps | null;
149
- /** concurrent children bound; default tasksMaxFromEnv() */
150
- maxConcurrent?: number;
151
- maxDepth?: number;
152
- /** injectable child runner (tests); default orchestrator runChild */
153
- run?: typeof runChild;
154
- /** #47: what an external lane needs to run — env for the knobs, spawn seam for tests; {} = defaults */
155
- lanes?: LaneJobDeps;
156
- }
157
-
158
- export const DEFAULT_TASKS_MAX = 3;
159
-
160
- /** ROVECODE_TASKS_MAX: positive integer, else the default. */
161
- export function tasksMaxFromEnv(env: Record<string, string | undefined> = process.env): number {
162
- const n = Number(env["ROVECODE_TASKS_MAX"] ?? "");
163
- return Number.isInteger(n) && n >= 1 ? n : DEFAULT_TASKS_MAX;
164
- }
165
-
166
- interface TaskRecord {
167
- info: TaskInfo;
168
- req: SpawnRequest;
169
- deps: ChildRunnerDeps;
170
- ac: AbortController;
171
- notify?: SteeringQueue;
172
- done: Promise<void>;
173
- resolveDone: () => void;
174
- /** true once the child run has RETURNED (status alone is not enough: cancel() flips
175
- * status to "cancelled" while the aborted run is still winding down) */
176
- settled: boolean;
177
- /** true once this task's outcome has gone out in a note — its own, or a batch summary that
178
- * covered it. Keeps a second summary for the same batch (a run that starts more tasks after
179
- * the first group settled) from repeating agents already reported. */
180
- reported: boolean;
181
- /** detach the owner-abort listener (settle) */
182
- unbind?: () => void;
183
- /** #47: set when this record is an external lane rather than a child agent */
184
- lane?: AdapterId;
185
- /** the starting loop's working tree (StartOptions.parentDir); a lane runs and merges back HERE */
186
- parentDir?: string;
187
- }
188
-
189
- const TERMINAL: ReadonlySet<TaskStatus> = new Set(["done", "failed", "cancelled"]);
190
- export const isTerminal = (s: TaskStatus): boolean => TERMINAL.has(s);
191
-
192
- function brief(text: string | undefined, max = 200): string {
193
- const one = (text ?? "").split("\n").find((l) => l.trim() !== "")?.trim() ?? "";
194
- return one.length > max ? one.slice(0, max - 1) + "…" : one || "(no output)";
195
- }
196
-
197
- /** The steer the parent sees on its next turn (opencode task.ts:241-249 renders a
198
- * <task state> block; ours is one line the model can act on). */
199
- export function taskNote(t: TaskInfo): string {
200
- const head = `task ${t.id} (${t.label})`;
201
- if (t.status === "done") return `${head} finished: ${brief(t.summary)} — call task_status result ${t.id} for details`;
202
- if (t.status === "failed") return `${head} failed: ${brief(t.error)} — call task_status result ${t.id} for details`;
203
- return `${head} cancelled`;
204
- }
205
-
206
- /** One line per task for /tasks and the tool's list action; newest last, ≤50 rows. */
207
- export function formatTaskList(tasks: TaskInfo[], now = Date.now()): string {
208
- if (tasks.length === 0) return "(no background tasks)";
209
- const rows = tasks.slice(-50).map((t) => {
210
- const end = t.finishedAt ?? now;
211
- const age = t.startedAt !== undefined ? ` ${Math.max(0, Math.round((end - t.startedAt) / 1000))}s` : "";
212
- const tail = t.status === "failed" ? ` — ${brief(t.error, 80)}` : t.status === "done" ? ` — ${brief(t.summary, 80)}` : "";
213
- return `${t.id.padEnd(4)} ${t.status.padEnd(9)}${age.padEnd(6)} ${t.label}${tail}`;
214
- });
215
- return (tasks.length > 50 ? `(showing 50 of ${tasks.length})\n` : "") + rows.join("\n");
216
- }
217
-
218
- /** bindRun's label bound: one line, clipped here so no surface has to. */
219
- export const BATCH_LABEL_MAX = 60;
220
-
221
- function oneLine(s: string | undefined, max: number): string {
222
- const one = (s ?? "").replace(/\s+/g, " ").trim();
223
- return one.length > max ? one.slice(0, max - 1) + "…" : one;
224
- }
225
-
226
- /** Two tasks of one batch that changed the SAME file in the parent tree. */
227
- export interface BatchCollision {
228
- path: string;
229
- tasks: TaskId[];
230
- }
231
-
232
- export interface BatchSummary {
233
- batch: string;
234
- label?: string;
235
- /** the batch's tasks, in start order */
236
- tasks: TaskInfo[];
237
- done: number;
238
- failed: number;
239
- cancelled: number;
240
- /** every file more than one task wrote, path first — the one thing nobody can work out
241
- * from a note that just counts finished agents */
242
- collisions: BatchCollision[];
243
- }
244
-
245
- const andList = (xs: string[]): string =>
246
- xs.length <= 1 ? (xs[0] ?? "") : `${xs.slice(0, -1).join(", ")} and ${xs[xs.length - 1]}`;
247
-
248
- /** `tasks` are the ones this summary REPORTS; `peers` is the whole batch it belongs to, which is
249
- * wider when part of the batch has already been reported (a run that started one agent, waited, then
250
- * started two more). Collisions are looked for across the whole batch and kept when they involve
251
- * someone in this group — an overlap with an agent announced ten minutes ago is still an overlap,
252
- * and reporting only within the group is how it would go unsaid. */
253
- export function summariseBatch(tasks: TaskInfo[], peers: TaskInfo[] = tasks): BatchSummary {
254
- const mine = new Set(tasks.map((t) => t.id));
255
- const writers = new Map<string, TaskId[]>();
256
- for (const t of peers) {
257
- for (const f of t.files ?? []) writers.set(f, [...(writers.get(f) ?? []), t.id]);
258
- }
259
- const collisions = [...writers]
260
- .filter(([, ids]) => ids.length > 1 && ids.some((id) => mine.has(id)))
261
- .map(([path, ids]) => ({ path, tasks: ids }))
262
- .sort((a, b) => a.path.localeCompare(b.path));
263
- const count = (s: TaskStatus): number => tasks.reduce((n, t) => n + (t.status === s ? 1 : 0), 0);
264
- const label = tasks.find((t) => t.batchLabel !== undefined)?.batchLabel;
265
- return {
266
- batch: tasks[0]?.batch ?? "",
267
- ...(label !== undefined ? { label } : {}),
268
- tasks, done: count("done"), failed: count("failed"), cancelled: count("cancelled"),
269
- collisions,
270
- };
271
- }
272
-
273
- /** ONE steer for a whole batch, in place of one note per agent. Two rules it exists to keep:
274
- * a batch with a failure says so in its FIRST words (a failed agent is not a footnote on a
275
- * success), and a file two agents both wrote is named, with its path, above the per-agent
276
- * rows — it is the only part of this a person cannot reconstruct from the diff themselves. */
277
- export function batchNote(s: BatchSummary): string {
278
- const parts: string[] = [];
279
- if (s.failed > 0) parts.push(`${s.failed} FAILED`);
280
- if (s.cancelled > 0) parts.push(`${s.cancelled} cancelled`);
281
- if (s.done > 0) parts.push(`${s.done} done`);
282
- const n = s.tasks.length;
283
- const head = `batch ${s.batch}${s.label !== undefined ? ` (${s.label})` : ""}: ${n} agent${n === 1 ? "" : "s"} — ${parts.join(", ")}`;
284
- const lines = [head];
285
- for (const c of s.collisions) lines.push(`COLLISION: ${c.path} — ${andList(c.tasks)} both changed this file`);
286
- for (const t of s.tasks) {
287
- const secs = t.startedAt !== undefined && t.finishedAt !== undefined
288
- ? ` ${Math.max(0, Math.round((t.finishedAt - t.startedAt) / 1000))}s` : "";
289
- const state = t.status === "failed" ? "FAILED" : t.status;
290
- const files = t.files !== undefined && t.files.length > 0 ? ` [${t.files.length} file${t.files.length === 1 ? "" : "s"}]` : "";
291
- const what = t.status === "done" ? brief(t.summary, 80) : t.status === "failed" ? brief(t.error, 80) : "";
292
- lines.push(` ${t.id} ${t.agent}${secs} ${state}${files}${what !== "" ? ` — ${what}` : ""}`);
293
- }
294
- lines.push(`call task_status result <id> for any of them`);
295
- return lines.join("\n");
296
- }
297
-
298
- export class TaskManager {
299
- readonly maxConcurrent: number;
300
- readonly maxDepth: number;
301
- private readonly tasks = new Map<TaskId, TaskRecord>();
302
- private readonly queue: TaskId[] = [];
303
- private readonly listeners = new Set<(t: TaskInfo) => void>();
304
- private readonly run: typeof runChild;
305
- private sink: SteeringQueue | null = null;
306
- private runSignal: AbortSignal | null = null;
307
- private batch: { id: string; label?: string } | null = null;
308
- private running = 0;
309
- private seq = 0;
310
- private runSeq = 0;
311
-
312
- constructor(private readonly opts: TaskManagerOptions) {
313
- this.maxConcurrent = Math.max(1, Math.floor(opts.maxConcurrent ?? tasksMaxFromEnv()));
314
- // fix-wave L4: runChild re-preflights against the orchestrator's DEFAULT_MAX_DEPTH, so a
315
- // LARGER manager cap would launch a child only to fail it there — clamp, and every depth
316
- // refusal is start() data. Smaller caps are honored as given.
317
- this.maxDepth = Math.min(opts.maxDepth ?? DEFAULT_MAX_DEPTH, DEFAULT_MAX_DEPTH);
318
- this.run = opts.run ?? runChild;
319
- }
320
-
321
- /** #47: the lane deps, defaulted — a manager built without them still refuses every lane, because
322
- * the allow-list is read from the environment and an unset list allows nothing. */
323
- private get lanes(): LaneJobDeps { return this.opts.lanes ?? {}; }
324
-
325
- /** Default notification target: the SAME SteeringQueue the surface hands to agentLoop. */
326
- attach(steering: SteeringQueue): void { this.sink = steering; }
327
-
328
- /** The surface's per-run controller signal (the one Esc / DELETE / session-cancel
329
- * aborts): root tasks started from now on are owned by that run and cancelled when it
330
- * aborts. NOT ToolContext.signal — the loop's own controller aborts on EVERY settle,
331
- * a normal run end included (loop.ts:91), which would kill background work the moment
332
- * the parent finishes its turn. Call before each agentLoop; tasks from earlier runs
333
- * keep their own owner.
334
- *
335
- * It is also the BATCH seam: every task started under this run carries the same
336
- * TaskInfo.batch, because "started under the same run" is the only grouping the data
337
- * already has (all four surfaces call this exactly once per run). `batch.id` defaults to
338
- * a counter, so a caller that passes nothing still gets one distinct batch per run rather
339
- * than every run sharing an empty group; the label is one-lined and clipped HERE. */
340
- bindRun(signal: AbortSignal, batch?: { id?: string; label?: string }): void {
341
- this.runSignal = signal;
342
- const label = oneLine(batch?.label, BATCH_LABEL_MAX);
343
- this.batch = { id: batch?.id?.trim() || `r${++this.runSeq}`, ...(label !== "" ? { label } : {}) };
344
- }
345
-
346
- /** Enqueue a child run. Refusals are data, never throws: unknown agent, no provider,
347
- * an already-aborted owner, and the orchestrator's own preflight (depth cap, spawn
348
- * policy "none"). */
349
- start(req: SpawnRequest, opts: StartOptions = {}): StartResult {
350
- const deps = this.opts.deps();
351
- if (!deps) return { ok: false, reason: "no provider configured" };
352
- // #47: the four adapter ids are RESERVED — `task start claude` is an external lane, never an agent
353
- // definition that happens to share the name. The allow-list gate refuses here, before any record
354
- // exists, so a refused lane leaves nothing behind to explain: no task row, no worktree, no process.
355
- const lane = isAdapterId(req.agent) ? req.agent : null;
356
- const refused = lane ? laneRefusal(lane, this.lanes.env) : null;
357
- if (refused) return { ok: false, reason: refused };
358
- const def = lane ? { name: lane, systemPrompt: "", tools: [] } : deps.defs.get(req.agent);
359
- if (!def) return { ok: false, reason: `unknown agent '${req.agent}'` };
360
- const owner = opts.owner ?? this.runSignal ?? undefined;
361
- if (owner?.aborted) return { ok: false, reason: "parent run aborted" };
362
- const depth = (opts.parentDepth ?? 0) + 1;
363
- const gate = preflightSpawn(def, { depth, maxDepth: this.maxDepth, parentSessionId: opts.caller ?? "" });
364
- if (!gate.ok) return { ok: false, reason: gate.reason ?? "spawn refused" };
365
- const childPolicy = deps.baseConfig.permissionRules.some((r) => r.effect === "prompt") ? "gated" : "open";
366
- const id: TaskId = `t${++this.seq}`;
367
- let resolveDone: () => void = () => {};
368
- const done = new Promise<void>((r) => { resolveDone = r; });
369
- const goal = req.goal.replace(/\s+/g, " ").trim();
370
- // a NESTED task belongs to its CALLER's batch, not to whatever run is bound now: a long task
371
- // that spawns a child after the next run has been bound would otherwise land in a batch it has
372
- // nothing to do with. A caller without a batch falls back to the current run.
373
- const inherited = opts.caller !== undefined ? this.tasks.get(opts.caller)?.info : undefined;
374
- const batch = inherited?.batch !== undefined
375
- ? { id: inherited.batch, ...(inherited.batchLabel !== undefined ? { label: inherited.batchLabel } : {}) }
376
- : this.batch;
377
- const rec: TaskRecord = {
378
- info: {
379
- id, label: (opts.label ?? "").trim() || (goal.length > 40 ? goal.slice(0, 39) + "…" : goal || "(no goal)"),
380
- agent: req.agent, goal: goal.length > 200 ? goal.slice(0, 199) + "…" : goal,
381
- // a lane is ALWAYS isolated: it works in its own worktree and its diff comes back as a patch
382
- isolated: lane !== null || req.isolated === true, depth, status: "queued", createdAt: Date.now(),
383
- ...(lane ? { kind: "external" as const, permissions: lanePermissions(lane, this.lanes.env) } : {}),
384
- ...(batch ? { batch: batch.id, ...(batch.label !== undefined ? { batchLabel: batch.label } : {}) } : {}),
385
- },
386
- req: opts.parentTools ? { ...req, parentTools: opts.parentTools } : req,
387
- deps, ...(lane ? { lane } : {}), ...(opts.parentDir !== undefined ? { parentDir: opts.parentDir } : {}),
388
- ac: new AbortController(), notify: opts.notify, done, resolveDone,
389
- settled: false, reported: false,
390
- };
391
- if (owner) {
392
- // a parent-run abort reaches its children: cancel() aborts the child's own controller
393
- const onAbort = (): void => { this.cancel(id); };
394
- owner.addEventListener("abort", onAbort, { once: true });
395
- rec.unbind = () => owner.removeEventListener("abort", onAbort);
396
- }
397
- this.tasks.set(id, rec);
398
- this.queue.push(id);
399
- this.emit(rec);
400
- this.pump();
401
- return { ok: true, id, childPolicy };
402
- }
403
-
404
- status(id: TaskId): TaskInfo | undefined {
405
- const t = this.tasks.get(id);
406
- return t ? { ...t.info } : undefined;
407
- }
408
-
409
- /** All tasks, oldest first. */
410
- list(): TaskInfo[] { return [...this.tasks.values()].map((t) => ({ ...t.info })); }
411
-
412
- counts(): Record<TaskStatus, number> {
413
- const c: Record<TaskStatus, number> = { queued: 0, running: 0, done: 0, failed: 0, cancelled: 0 };
414
- for (const t of this.tasks.values()) c[t.info.status]++;
415
- return c;
416
- }
417
-
418
- /** Wait for a task to settle (bounded/abortable), then return its snapshot — on a
419
- * timeout or abort the snapshot still reports the live status. A cancelled task is
420
- * "settled" only once its aborted run has returned. Unknown id → undefined. */
421
- async result(id: TaskId, opts: WaitOptions = {}): Promise<TaskInfo | undefined> {
422
- const t = this.tasks.get(id);
423
- if (!t) return undefined;
424
- if (t.settled) return { ...t.info };
425
- // slot lending: the waiter holds a slot but generates no load while it waits — under
426
- // a full pool a parent waiting on its queued child would otherwise sit until the
427
- // deadline (nested-pool deadlock). Promote the waited-on task now; `running` may
428
- // exceed the bound by exactly the number of blocked waiters.
429
- if (t.info.status === "queued" && opts.caller !== undefined && this.tasks.get(opts.caller)?.info.status === "running") {
430
- this.dequeue(id);
431
- this.launch(t);
432
- }
433
- await waitFor(t.done, opts.timeoutMs, opts.signal);
434
- return { ...t.info };
435
- }
436
-
437
- /** Cancel: a queued task never runs; a running one has its child run aborted (the
438
- * signal threads runChild → agentLoop → fetch/tools) and settles when it returns.
439
- * Returns the snapshot (status already "cancelled"); unknown id → undefined. */
440
- cancel(id: TaskId): TaskInfo | undefined {
441
- const t = this.tasks.get(id);
442
- if (!t) return undefined;
443
- if (isTerminal(t.info.status)) return { ...t.info };
444
- if (t.info.status === "queued") {
445
- this.dequeue(id);
446
- t.info.status = "cancelled"; t.info.error = "cancelled";
447
- this.settle(t);
448
- } else {
449
- t.info.status = "cancelled"; t.info.error = "cancelled";
450
- t.ac.abort(); // finish() → settle() runs (one terminal emission) when runChild returns
451
- }
452
- return { ...t.info };
453
- }
454
-
455
- /** Cancel every non-terminal task (surface shutdown); returns how many were live. */
456
- cancelAll(): number {
457
- let n = 0;
458
- for (const t of this.tasks.values()) {
459
- if (!isTerminal(t.info.status)) { this.cancel(t.info.id); n++; }
460
- }
461
- return n;
462
- }
463
-
464
- /** Resolve when nothing is queued or running (or the deadline passes → false). */
465
- async drain(timeoutMs: number): Promise<boolean> {
466
- const deadline = Date.now() + timeoutMs;
467
- while (this.running > 0 || this.queue.length > 0) {
468
- if (Date.now() >= deadline) return false;
469
- await new Promise<void>((r) => setTimeout(r, 10));
470
- }
471
- return true;
472
- }
473
-
474
- /** Status-transition listener (TUI live notes, tests). Returns the unsubscribe. */
475
- subscribe(fn: (t: TaskInfo) => void): () => void {
476
- this.listeners.add(fn);
477
- return () => { this.listeners.delete(fn); };
478
- }
479
-
480
- private dequeue(id: TaskId): void {
481
- const i = this.queue.indexOf(id);
482
- if (i >= 0) this.queue.splice(i, 1);
483
- }
484
-
485
- private pump(): void {
486
- while (this.running < this.maxConcurrent && this.queue.length > 0) {
487
- const t = this.tasks.get(this.queue.shift()!);
488
- if (t && t.info.status === "queued") this.launch(t);
489
- }
490
- }
491
-
492
- private launch(t: TaskRecord): void {
493
- t.info.status = "running"; t.info.startedAt = Date.now();
494
- this.running++;
495
- this.emit(t);
496
- // An external CLI is about to work in a real tree. With the lane allow-list on by default there is
497
- // no approval card left to say so, and under permission=auto there never was one — so the START is
498
- // stated here, at every permission level, and here is also the only place that knows the lane is
499
- // RUNNING rather than queued behind the concurrency bound. Once per task: launch() runs once per
500
- // record. The id is on it because four lanes at a time make an unattributed sentence useless.
501
- if (t.lane) (t.notify ?? this.sink)?.push(`task ${t.info.id} (${t.info.label}): ${laneStartNote(t.lane, this.lanes.env)}`);
502
- // the child's registry learns its task id (ChildContext) so a nested `task_status result`
503
- // can identify its caller for slot lending
504
- const deps: ChildRunnerDeps = {
505
- ...t.deps,
506
- registryFactory: (def, cwd, child) => t.deps.registryFactory(def, cwd, child ? { ...child, taskId: t.info.id } : undefined),
507
- };
508
- // #47: an external lane runs its CLI in its own worktree (lanes/job.ts) — same slot, same finish().
509
- // It is cut from, and merged back into, the tree of the loop that STARTED it: a lane started by an
510
- // isolated child belongs to that child's worktree, not to the user's live repo.
511
- const run: Promise<SpawnResult | LaneJobResult> = t.lane
512
- ? runLaneJob(this.lanes, t.lane, t.req.goal, t.parentDir ?? t.deps.rootDir, t.ac.signal, (preview, counters) => this.progress(t, preview, counters))
513
- : this.run(deps, t.req, t.info.depth, t.ac.signal);
514
- void run
515
- .then((r) => this.finish(t, r), (e: unknown) => this.finish(t, undefined, e))
516
- .finally(() => { this.running--; this.pump(); });
517
- }
518
-
519
- /** #47: a running lane's log tail is its live summary preview — finish() replaces it with the result.
520
- * A lane can run for minutes with nothing to show otherwise, and a job that shows nothing is one a
521
- * person has no way to tell from a job that hung.
522
- * The runner's counters ride the same call and are OPTIONAL: a lane that has reported nothing
523
- * countable leaves `progress` undefined instead of showing a zeroed tally, because "we have read
524
- * nothing yet" and "it has done nothing" are different facts about a running job. */
525
- private progress(t: TaskRecord, preview: string, counters?: LaneProgress): void {
526
- if (t.info.status !== "running") return;
527
- t.info.summary = preview;
528
- if (counters) {
529
- t.info.progress = counters;
530
- if (counters.usage) t.info.usage = counters.usage;
531
- }
532
- this.emit(t);
533
- }
534
-
535
- private finish(t: TaskRecord, r: SpawnResult | LaneJobResult | undefined, err?: unknown): void {
536
- const info = t.info;
537
- // #47: a lane's own facts, recorded whatever the outcome — a cancelled lane that already reported
538
- // its exit code and session id still has both, and they are how anyone resumes it in its own CLI
539
- if (r && "permissions" in r) {
540
- if (r.sessionId) info.laneSession = r.sessionId;
541
- info.laneExit = r.exitCode;
542
- if (r.model) info.laneModel = r.model;
543
- // the runner's final tally replaces the live one (its file list is measured from the diff);
544
- // a refusal carries none, and then the live one — or nothing at all — is what we know
545
- if (r.progress) info.progress = r.progress;
546
- }
547
- if (info.status === "cancelled") {
548
- if (r) info.usage = r.usage; // the aborted run's own summary ("run aborted") is not a result
549
- } else if (r === undefined) {
550
- info.status = "failed";
551
- info.error = `child runner threw: ${err instanceof Error ? err.message : String(err)}`;
552
- } else if (!r.ok) {
553
- info.status = "failed"; info.error = r.summary; info.usage = r.usage;
554
- } else {
555
- info.status = "done"; info.summary = r.summary; info.usage = r.usage;
556
- if (r.patch !== undefined) {
557
- info.patchLines = r.patch.trim() === "" ? 0 : r.patch.split("\n").length;
558
- // only a merged-back patch changed the parent tree, and only such a patch can collide with
559
- // another task's — a failed or cancelled child's is returned but never applied
560
- // (orchestrator.ts:185). A lane's runner has already measured the same list from the same
561
- // diff; take it rather than reading the patch twice.
562
- // …and only when it ACTUALLY LANDED. A rejected `git apply` still leaves ok:true (job.ts:127,
563
- // orchestrator.ts:192 both only append a marker to the summary), and the patch most likely to be
564
- // rejected is the SECOND one to touch a file — precisely the case the collision line is about.
565
- // Claiming those paths would turn the one thing this summary exists to say into the one thing it
566
- // gets wrong: the task that LOST the race named as a writer of the file it never wrote.
567
- //
568
- // Both paths carry the fact as a BOOLEAN, so neither is read out of prose: a lane reports it as
569
- // progress.applied, a child agent as SpawnResult.applied (set in orchestrator.ts beside the
570
- // applyPatch call that knows the answer). This used to sniff the marker out of `r.summary`, which
571
- // over-claimed in one direction nobody expected — the marker is appended and the summary is THEN
572
- // sliced to 4000 chars, so a child verbose enough to describe its own diff lost the marker and
573
- // read as applied. Absent means there was no merge-back to attempt at all (no isolation, no
574
- // patch), and that keeps its old meaning: nothing to apply, so nothing failed.
575
- const landed = info.progress?.applied ?? r.applied ?? true;
576
- const files = info.progress?.filesWritten ?? filesFromPatch(r.patch);
577
- if (landed && files.length > 0) info.files = files;
578
- }
579
- }
580
- this.settle(t);
581
- }
582
-
583
- /** Terminal tail shared by finish() and queued-cancel: stamp, notify the parent
584
- * (steer — queued BEFORE waiters wake, so a `result` waiter's next turn sees it),
585
- * release waiters, tell listeners. */
586
- private settle(t: TaskRecord): void {
587
- t.settled = true;
588
- t.unbind?.();
589
- t.info.finishedAt = Date.now();
590
- const note = this.noteFor(t);
591
- if (note !== null) (t.notify ?? this.sink)?.push(note);
592
- t.resolveDone();
593
- this.emit(t);
594
- }
595
-
596
- /** What this settle says, and to whom. A task outside any batch, or the only one of its batch,
597
- * pushes its own note exactly as before. When a batch has several tasks the note is held until
598
- * the LAST of them settles and then ONE summary goes out for all of them — three agents finishing
599
- * used to mean three notes and no statement about whether they had written over each other.
600
- *
601
- * Grouping is by batch AND by notification target: a nested task inherits its caller's batch but
602
- * steers the caller's own queue (orchestrator ChildContext), so summarising across queues would
603
- * put a child's outcome in a queue that never asked for it and take it away from the one that did. */
604
- private noteFor(t: TaskRecord): string | null {
605
- const batch = t.info.batch;
606
- if (batch === undefined) { t.reported = true; return taskNote(t.info); }
607
- const q = t.notify ?? this.sink;
608
- const peers = [...this.tasks.values()].filter((o) => o.info.batch === batch && (o.notify ?? this.sink) === q);
609
- if (peers.some((o) => !o.settled)) return null; // the batch is still running; t waits for the summary
610
- const group = peers.filter((o) => !o.reported);
611
- for (const o of group) o.reported = true;
612
- const s = summariseBatch(group.map((o) => ({ ...o.info })), peers.map((o) => ({ ...o.info })));
613
- // one task and nothing to say about it that its own note does not: the old wording, unchanged.
614
- // A collision is something new to say, so it earns the summary even for a single agent —
615
- // otherwise a run that started its agents one at a time would never hear about an overlap.
616
- if (s.tasks.length <= 1 && s.collisions.length === 0) return taskNote(t.info);
617
- return batchNote(s);
618
- }
619
-
620
- private emit(t: TaskRecord): void {
621
- for (const fn of this.listeners) {
622
- try { fn({ ...t.info }); } catch { /* listeners never break the manager */ }
623
- }
624
- }
625
- }
626
-
627
- /** Resolve when `p` settles, the deadline passes, or `signal` aborts — whichever is
628
- * first; the timer and listener are always released (a pending timer would hold the
629
- * process; a parked promise with no timer hangs the Bun runner). */
630
- function waitFor(p: Promise<void>, timeoutMs: number | undefined, signal: AbortSignal | undefined): Promise<void> {
631
- return new Promise<void>((resolve) => {
632
- let timer: ReturnType<typeof setTimeout> | undefined;
633
- let settled = false;
634
- const finish = (): void => {
635
- if (settled) return;
636
- settled = true;
637
- if (timer !== undefined) clearTimeout(timer);
638
- signal?.removeEventListener("abort", finish);
639
- resolve();
640
- };
641
- if (signal?.aborted) { finish(); return; }
642
- if (timeoutMs !== undefined) timer = setTimeout(finish, Math.max(0, timeoutMs));
643
- signal?.addEventListener("abort", finish, { once: true });
644
- void p.then(finish, finish);
645
- });
646
- }