rovecode 0.4.0-beta.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (428) hide show
  1. package/README.md +67 -69
  2. package/THIRD_PARTY_NOTICES.md +0 -44
  3. package/bin/rovecode.ts +21 -0
  4. package/package.json +16 -37
  5. package/src/account/keys.ts +97 -0
  6. package/src/account/login.ts +158 -0
  7. package/src/account/provision.ts +47 -0
  8. package/src/account/store.ts +63 -0
  9. package/src/acp/server.ts +373 -0
  10. package/src/cli/account-cmd.ts +116 -0
  11. package/src/cli/connect.ts +244 -0
  12. package/src/cli/context-cmd.ts +199 -0
  13. package/src/cli/dispatch.ts +109 -0
  14. package/src/cli/doctor.ts +324 -0
  15. package/src/cli/export.ts +278 -0
  16. package/src/cli/help.ts +240 -0
  17. package/src/cli/is-tui-invocation.ts +8 -0
  18. package/src/cli/main.ts +599 -0
  19. package/src/cli/market-cmd.ts +658 -0
  20. package/src/cli/mcp-market-cmd.ts +299 -0
  21. package/src/cli/output.ts +382 -0
  22. package/src/cli/repl.ts +172 -0
  23. package/src/cli/resume.ts +32 -0
  24. package/src/cli/run-limits.ts +78 -0
  25. package/src/cli/runtime.ts +792 -0
  26. package/src/cli/setup.ts +187 -0
  27. package/src/cli/update-cmd.ts +78 -0
  28. package/src/cli/workflow-cmd.ts +100 -0
  29. package/src/coding/checkpoints.ts +270 -0
  30. package/src/coding/diff.ts +136 -0
  31. package/src/coding/files.ts +339 -0
  32. package/src/coding/hashline.ts +319 -0
  33. package/src/coding/lsp.ts +406 -0
  34. package/src/coding/repomap-cache.ts +99 -0
  35. package/src/coding/repomap-files.ts +110 -0
  36. package/src/coding/repomap.ts +392 -0
  37. package/src/core/compaction.ts +399 -0
  38. package/src/core/config.ts +289 -0
  39. package/src/core/context-report.ts +228 -0
  40. package/src/core/context.ts +60 -0
  41. package/src/core/count-remote.ts +107 -0
  42. package/src/core/execpolicy-rules.ts +196 -0
  43. package/src/core/execpolicy.ts +385 -0
  44. package/src/core/executor.ts +397 -0
  45. package/src/core/guardrails.ts +400 -0
  46. package/src/core/hooks.ts +398 -0
  47. package/src/core/images.ts +230 -0
  48. package/src/core/intro.ts +236 -0
  49. package/src/core/loop.ts +621 -0
  50. package/src/core/modes.ts +372 -0
  51. package/src/core/orchestrator.ts +207 -0
  52. package/src/core/reflection.ts +165 -0
  53. package/src/core/sandbox-config.ts +167 -0
  54. package/src/core/session-images.ts +73 -0
  55. package/src/core/session.ts +398 -0
  56. package/src/core/settings.ts +98 -0
  57. package/src/core/stuck-detector.ts +273 -0
  58. package/src/core/tasks.ts +374 -0
  59. package/src/core/token-scale.ts +108 -0
  60. package/src/core/tool-output-budget.ts +166 -0
  61. package/src/core/tools.ts +288 -0
  62. package/src/core/types.ts +330 -0
  63. package/src/core/update-check.ts +171 -0
  64. package/src/core/update.ts +158 -0
  65. package/src/core/usage.ts +204 -0
  66. package/src/core/validate.ts +121 -0
  67. package/src/core/verify-gate.ts +159 -0
  68. package/src/core/verify.ts +237 -0
  69. package/src/core/voice.ts +158 -0
  70. package/src/core/win-job.ts +183 -0
  71. package/src/design/audit.ts +797 -0
  72. package/src/design/direction.ts +190 -0
  73. package/src/design/rules.ts +157 -0
  74. package/src/eval/bench.ts +150 -0
  75. package/src/eval/gauntlet-runner.ts +218 -0
  76. package/src/eval/gauntlet.ts +226 -0
  77. package/src/eval/grader.ts +186 -0
  78. package/src/eval/record.ts +202 -0
  79. package/src/eval/redact.ts +141 -0
  80. package/src/eval/replay.ts +147 -0
  81. package/src/eval/trajectory.ts +373 -0
  82. package/src/index.ts +17 -0
  83. package/src/market/catalogs/mcp-docs.json +111 -0
  84. package/src/market/catalogs/plugins.json +111 -0
  85. package/src/market/catalogs/skills.json +478 -0
  86. package/src/market/clone.ts +72 -0
  87. package/src/market/context-cost.ts +121 -0
  88. package/src/market/digest.ts +106 -0
  89. package/src/market/index.ts +22 -0
  90. package/src/market/install.ts +578 -0
  91. package/src/market/manifest.ts +187 -0
  92. package/src/market/prereq.ts +145 -0
  93. package/src/market/registry.ts +363 -0
  94. package/src/market/resolve.ts +111 -0
  95. package/src/market/types.ts +236 -0
  96. package/src/market/validate.ts +227 -0
  97. package/src/mcp/client.ts +431 -0
  98. package/src/mcp/config.ts +239 -0
  99. package/src/mcp/local-package.ts +211 -0
  100. package/src/mcp/market-catalog.ts +84 -0
  101. package/src/mcp/market-install.ts +289 -0
  102. package/src/mcp/market.ts +0 -0
  103. package/src/mcp/tools.ts +131 -0
  104. package/src/mcp/trust.ts +49 -0
  105. package/src/memory/blocks.ts +175 -0
  106. package/src/memory/recall.ts +355 -0
  107. package/src/memory/store.ts +105 -0
  108. package/src/memory/tools.ts +99 -0
  109. package/src/plugins/cli.ts +123 -0
  110. package/src/plugins/discover.ts +108 -0
  111. package/src/plugins/index.ts +50 -0
  112. package/src/plugins/init.ts +140 -0
  113. package/src/plugins/install.ts +184 -0
  114. package/src/plugins/load.ts +149 -0
  115. package/src/plugins/manifest.ts +106 -0
  116. package/src/plugins/state.ts +83 -0
  117. package/src/providers/auth.ts +293 -0
  118. package/src/providers/cache.ts +223 -0
  119. package/src/providers/catalog-local.ts +160 -0
  120. package/src/providers/catalog.ts +408 -0
  121. package/src/providers/middleware-context.ts +86 -0
  122. package/src/providers/middleware.ts +373 -0
  123. package/src/providers/profile-glm53.ts +111 -0
  124. package/src/providers/profile-sonnet5-persona.ts +65 -0
  125. package/src/providers/profile-sonnet5-voice.ts +23 -0
  126. package/src/providers/profiles.ts +156 -0
  127. package/src/providers/provider-config.ts +311 -0
  128. package/src/providers/registry.ts +302 -0
  129. package/src/providers/response-validation.ts +80 -0
  130. package/src/providers/retry.ts +234 -0
  131. package/src/providers/router.ts +294 -0
  132. package/src/providers/sse.ts +26 -0
  133. package/src/providers/stream-errors.ts +117 -0
  134. package/src/providers/stream.ts +569 -0
  135. package/src/providers/thinking.ts +189 -0
  136. package/src/providers/wire-messages.ts +129 -0
  137. package/src/sdk/client.ts +225 -0
  138. package/src/sdk/index.ts +3 -0
  139. package/src/server/dashboard.ts +144 -0
  140. package/src/server/http.ts +343 -0
  141. package/src/server/openapi.ts +246 -0
  142. package/src/sextant/card-hits.ts +102 -0
  143. package/src/sextant/card-keys.ts +55 -0
  144. package/src/sextant/context-source.ts +157 -0
  145. package/src/sextant/draw-agents.ts +273 -0
  146. package/src/sextant/draw-code.ts +388 -0
  147. package/src/sextant/draw-context.ts +222 -0
  148. package/src/sextant/draw-frame.ts +164 -0
  149. package/src/sextant/draw-market.ts +573 -0
  150. package/src/sextant/draw-messages.ts +386 -0
  151. package/src/sextant/draw-pet.ts +230 -0
  152. package/src/sextant/draw-plan.ts +159 -0
  153. package/src/sextant/draw-tabs.ts +85 -0
  154. package/src/sextant/draw-util.ts +65 -0
  155. package/src/sextant/engine.ts +230 -0
  156. package/src/sextant/frame-hits.ts +25 -0
  157. package/src/sextant/frame.ts +101 -0
  158. package/src/sextant/git-status.ts +197 -0
  159. package/src/sextant/grid.ts +59 -0
  160. package/src/sextant/input.ts +119 -0
  161. package/src/sextant/keys.ts +488 -0
  162. package/src/sextant/layout.ts +86 -0
  163. package/src/sextant/local-commands.ts +156 -0
  164. package/src/sextant/market-source.ts +287 -0
  165. package/src/sextant/mentions.ts +141 -0
  166. package/src/sextant/message-hits.ts +26 -0
  167. package/src/sextant/model.ts +387 -0
  168. package/src/sextant/overlays.ts +451 -0
  169. package/src/sextant/panel-hits.ts +38 -0
  170. package/src/sextant/pet.ts +399 -0
  171. package/src/sextant/screen.ts +324 -0
  172. package/src/sextant/scroll-hits.ts +66 -0
  173. package/src/sextant/scrollbar.ts +82 -0
  174. package/src/sextant/selection.ts +123 -0
  175. package/src/sextant/sextant-bridge.ts +174 -0
  176. package/src/sextant/sextant-cards.ts +142 -0
  177. package/src/sextant/sextant-diff-base.ts +63 -0
  178. package/src/sextant/sextant-files.ts +154 -0
  179. package/src/sextant/sextant-frame-loop.ts +314 -0
  180. package/src/sextant/sextant-renderer.ts +478 -0
  181. package/src/sextant/sextant-repo.ts +131 -0
  182. package/src/sextant/theme.ts +66 -0
  183. package/src/sextant/tool-rows.ts +189 -0
  184. package/src/sextant/types.ts +473 -0
  185. package/src/skills/index.ts +306 -0
  186. package/src/skills/tools.ts +69 -0
  187. package/src/skills/versioned.ts +227 -0
  188. package/src/telemetry/otel.ts +353 -0
  189. package/src/telemetry/otlp.ts +68 -0
  190. package/src/tools/ask-user.ts +156 -0
  191. package/src/tools/design.ts +151 -0
  192. package/src/tools/evalcell.ts +338 -0
  193. package/src/tools/html-text.ts +139 -0
  194. package/src/tools/provider.ts +149 -0
  195. package/src/tools/task.ts +216 -0
  196. package/src/tools/todo.ts +320 -0
  197. package/src/tools/webfetch.ts +331 -0
  198. package/src/tui/app.ts +608 -0
  199. package/src/tui/attach.ts +127 -0
  200. package/src/tui/checkpoints-cmd.ts +70 -0
  201. package/src/tui/clipboard-image.ts +81 -0
  202. package/src/tui/commands.ts +277 -0
  203. package/src/tui/cost.ts +108 -0
  204. package/src/tui/info-cmd.ts +144 -0
  205. package/src/tui/mcp-cmd.ts +128 -0
  206. package/src/tui/modes-cmd.ts +45 -0
  207. package/src/tui/overlays.ts +97 -0
  208. package/src/tui/pi-renderer.ts +424 -0
  209. package/src/tui/providers-cmd.ts +366 -0
  210. package/src/tui/renderer.ts +101 -0
  211. package/src/tui/replay-marker.ts +29 -0
  212. package/src/tui/session-cmd.ts +146 -0
  213. package/src/tui/sextant-attach.ts +68 -0
  214. package/src/tui/sextant-io.ts +184 -0
  215. package/src/tui/sextant-smoke.ts +110 -0
  216. package/src/tui/smoke.ts +72 -0
  217. package/src/tui/theme.ts +59 -0
  218. package/src/tui/todo-label.ts +7 -0
  219. package/src/workflow/engine.ts +266 -0
  220. package/tsconfig.json +30 -0
  221. package/vendor/pi-tui/LICENSE +21 -0
  222. package/vendor/pi-tui/PATCHES.md +12 -0
  223. package/vendor/pi-tui/PROVENANCE.md +12 -0
  224. package/vendor/pi-tui/README.upstream.md +854 -0
  225. package/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  226. package/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  227. package/vendor/pi-tui/src/alt-screen-search.ts +158 -0
  228. package/vendor/pi-tui/src/autocomplete.ts +827 -0
  229. package/vendor/pi-tui/src/components/alt-screen-flash.ts +52 -0
  230. package/vendor/pi-tui/src/components/box.ts +138 -0
  231. package/vendor/pi-tui/src/components/cancellable-loader.ts +41 -0
  232. package/vendor/pi-tui/src/components/editor.ts +2364 -0
  233. package/vendor/pi-tui/src/components/h-stack.ts +45 -0
  234. package/vendor/pi-tui/src/components/image.ts +128 -0
  235. package/vendor/pi-tui/src/components/input.ts +448 -0
  236. package/vendor/pi-tui/src/components/loader.ts +93 -0
  237. package/vendor/pi-tui/src/components/markdown.ts +1016 -0
  238. package/vendor/pi-tui/src/components/scroll-view.ts +217 -0
  239. package/vendor/pi-tui/src/components/select-list.ts +230 -0
  240. package/vendor/pi-tui/src/components/settings-list.ts +277 -0
  241. package/vendor/pi-tui/src/components/spacer.ts +29 -0
  242. package/vendor/pi-tui/src/components/stack.ts +155 -0
  243. package/vendor/pi-tui/src/components/text.ts +108 -0
  244. package/vendor/pi-tui/src/components/truncated-text.ts +66 -0
  245. package/vendor/pi-tui/src/components/v-stack.ts +34 -0
  246. package/vendor/pi-tui/src/editor-component.ts +75 -0
  247. package/vendor/pi-tui/src/fuzzy.ts +138 -0
  248. package/vendor/pi-tui/src/index.ts +149 -0
  249. package/vendor/pi-tui/src/keybindings.ts +321 -0
  250. package/vendor/pi-tui/src/keys.ts +1402 -0
  251. package/vendor/pi-tui/src/kill-ring.ts +47 -0
  252. package/vendor/pi-tui/src/latex.ts +1381 -0
  253. package/vendor/pi-tui/src/layout-node.ts +52 -0
  254. package/vendor/pi-tui/src/layout.ts +411 -0
  255. package/vendor/pi-tui/src/native-modifiers.ts +60 -0
  256. package/vendor/pi-tui/src/native-module-path.ts +32 -0
  257. package/vendor/pi-tui/src/stdin-buffer.ts +445 -0
  258. package/vendor/pi-tui/src/terminal-colors.ts +74 -0
  259. package/vendor/pi-tui/src/terminal-image.ts +701 -0
  260. package/vendor/pi-tui/src/terminal.ts +554 -0
  261. package/vendor/pi-tui/src/tui-alt-screen.ts +1379 -0
  262. package/vendor/pi-tui/src/tui-main-screen.ts +655 -0
  263. package/vendor/pi-tui/src/tui.ts +1264 -0
  264. package/vendor/pi-tui/src/undo-stack.ts +29 -0
  265. package/vendor/pi-tui/src/utils.ts +1327 -0
  266. package/vendor/pi-tui/src/word-navigation.ts +118 -0
  267. package/vendor/pi-tui/test/test-themes.ts +39 -0
  268. package/vendor/pi-tui/test/virtual-terminal.ts +219 -0
  269. package/CHANGELOG.md +0 -512
  270. package/bin/rovecode.js +0 -24
  271. package/dist/cli/app-dybnr56b.js +0 -2
  272. package/dist/cli/ask-user-p8hq4xgj.js +0 -2
  273. package/dist/cli/auth-login-ewpgw5sm.js +0 -2
  274. package/dist/cli/auth-m8p9grty.js +0 -2
  275. package/dist/cli/bench-xv3ypwev.js +0 -9
  276. package/dist/cli/catalog-737wb2s0.js +0 -2
  277. package/dist/cli/cli-arhg40m0.js +0 -2
  278. package/dist/cli/client-cf2pxx8q.js +0 -2
  279. package/dist/cli/commands-3p7e4xxs.js +0 -2
  280. package/dist/cli/connect-3q93d7cb.js +0 -2
  281. package/dist/cli/context-cmd-eqxmxhzq.js +0 -2
  282. package/dist/cli/context-report-hbw9zfes.js +0 -2
  283. package/dist/cli/count-remote-mby98cd0.js +0 -2
  284. package/dist/cli/design-122y0axd.js +0 -2
  285. package/dist/cli/dispatch-b4egzvvh.js +0 -2
  286. package/dist/cli/doctor-x4jkv72e.js +0 -3
  287. package/dist/cli/executor-ftvg6tsy.js +0 -2
  288. package/dist/cli/export-pdgdhkch.js +0 -2
  289. package/dist/cli/files-cez9a96p.js +0 -2
  290. package/dist/cli/gauntlet-r3xxaszc.js +0 -2
  291. package/dist/cli/gauntlet-runner-r515m7kk.js +0 -10
  292. package/dist/cli/gauntlet-wave3-bnkjk2v2.js +0 -5
  293. package/dist/cli/gauntlet-wave4-acs9s60q.js +0 -14
  294. package/dist/cli/hashline-ewg5hbe3.js +0 -2
  295. package/dist/cli/http-n0kehsk8.js +0 -5
  296. package/dist/cli/index-z5qt1s76.js +0 -2
  297. package/dist/cli/install-80mp63kx.js +0 -2
  298. package/dist/cli/loop-12twjcat.js +0 -2
  299. package/dist/cli/main-01pv9206.js +0 -4
  300. package/dist/cli/main-0jys2ccn.js +0 -3
  301. package/dist/cli/main-1ztz6fkj.js +0 -10
  302. package/dist/cli/main-23q7cmww.js +0 -9
  303. package/dist/cli/main-2rzbexn2.js +0 -3
  304. package/dist/cli/main-2wyax8k9.js +0 -9
  305. package/dist/cli/main-2yeveeve.js +0 -6
  306. package/dist/cli/main-2z3dek0b.js +0 -3
  307. package/dist/cli/main-2zgsknth.js +0 -3
  308. package/dist/cli/main-45ejth3a.js +0 -4
  309. package/dist/cli/main-45rn3trk.js +0 -22
  310. package/dist/cli/main-4p4e2w7x.js +0 -4
  311. package/dist/cli/main-4y0tnfpa.js +0 -16
  312. package/dist/cli/main-5py0rkmc.js +0 -4
  313. package/dist/cli/main-6dtqmbt6.js +0 -7
  314. package/dist/cli/main-6h9x282m.js +0 -4
  315. package/dist/cli/main-6vjeds42.js +0 -3
  316. package/dist/cli/main-78gq4bt9.js +0 -6
  317. package/dist/cli/main-7jd5vh3x.js +0 -4
  318. package/dist/cli/main-7kt6r53y.js +0 -4
  319. package/dist/cli/main-8c1tbazx.js +0 -58
  320. package/dist/cli/main-9a9rnh47.js +0 -19
  321. package/dist/cli/main-9ht36z12.js +0 -3
  322. package/dist/cli/main-a2yfvcy9.js +0 -7
  323. package/dist/cli/main-a3f51n0x.js +0 -5
  324. package/dist/cli/main-b8zq261k.js +0 -3
  325. package/dist/cli/main-bxtvnf6d.js +0 -13
  326. package/dist/cli/main-edxc3yzt.js +0 -4
  327. package/dist/cli/main-evgz4mp5.js +0 -21
  328. package/dist/cli/main-f33fc5je.js +0 -9
  329. package/dist/cli/main-fvnpq46y.js +0 -12
  330. package/dist/cli/main-gbbty4d4.js +0 -3
  331. package/dist/cli/main-gth53dnt.js +0 -25
  332. package/dist/cli/main-hqbz10aw.js +0 -9
  333. package/dist/cli/main-hrrvcfan.js +0 -38
  334. package/dist/cli/main-hzwtsb2m.js +0 -5
  335. package/dist/cli/main-j7ttv0sd.js +0 -34
  336. package/dist/cli/main-jak598k9.js +0 -5
  337. package/dist/cli/main-kba6zeyd.js +0 -6
  338. package/dist/cli/main-kwwsz6rq.js +0 -3
  339. package/dist/cli/main-m8vm17zq.js +0 -3
  340. package/dist/cli/main-mg4f96e1.js +0 -3
  341. package/dist/cli/main-mg9b20ac.js +0 -18
  342. package/dist/cli/main-mgb9ccnx.js +0 -3
  343. package/dist/cli/main-mjt2p7aj.js +0 -3
  344. package/dist/cli/main-n6qrdbmy.js +0 -3
  345. package/dist/cli/main-na7wse0x.js +0 -5
  346. package/dist/cli/main-nqveez48.js +0 -4
  347. package/dist/cli/main-ntqef02r.js +0 -10
  348. package/dist/cli/main-nvc3yjay.js +0 -136
  349. package/dist/cli/main-p0cfn6nr.js +0 -16
  350. package/dist/cli/main-qj2djy17.js +0 -19
  351. package/dist/cli/main-qsevpgsv.js +0 -3
  352. package/dist/cli/main-qvarybsp.js +0 -3
  353. package/dist/cli/main-rebtt91r.js +0 -5
  354. package/dist/cli/main-rpg7h8mb.js +0 -3
  355. package/dist/cli/main-rsy72qmw.js +0 -15
  356. package/dist/cli/main-rvetps99.js +0 -18
  357. package/dist/cli/main-s4bb0jav.js +0 -3
  358. package/dist/cli/main-s9v8k74e.js +0 -3
  359. package/dist/cli/main-tjvwmscs.js +0 -3
  360. package/dist/cli/main-tkgarpjj.js +0 -4
  361. package/dist/cli/main-v8y60bb2.js +0 -3
  362. package/dist/cli/main-vhrrq337.js +0 -3
  363. package/dist/cli/main-vp2dfb7s.js +0 -4
  364. package/dist/cli/main-vqbr22sz.js +0 -8
  365. package/dist/cli/main-vxnwe5xx.js +0 -18
  366. package/dist/cli/main-wgph00xf.js +0 -5
  367. package/dist/cli/main-wk2csfnj.js +0 -5
  368. package/dist/cli/main-wm997zjx.js +0 -3
  369. package/dist/cli/main-wpkyraxh.js +0 -3
  370. package/dist/cli/main-wqt32p5x.js +0 -4
  371. package/dist/cli/main-x9ct6y1a.js +0 -3
  372. package/dist/cli/main-xfekqh9m.js +0 -7
  373. package/dist/cli/main-xt9zc3n6.js +0 -7
  374. package/dist/cli/main-xx2z3zh5.js +0 -4
  375. package/dist/cli/main-y5c82rxr.js +0 -3
  376. package/dist/cli/main-yrjt2sqt.js +0 -14
  377. package/dist/cli/main-ys6zj3yr.js +0 -3
  378. package/dist/cli/main-ywbxshqc.js +0 -8
  379. package/dist/cli/main-z13755t8.js +0 -25
  380. package/dist/cli/main-zc7pyrbj.js +0 -4
  381. package/dist/cli/main.js +0 -279
  382. package/dist/cli/market-cmd-bm5xvn9f.js +0 -5
  383. package/dist/cli/mcp-login-bthtfpt7.js +0 -2
  384. package/dist/cli/mcp-market-cmd-mbeshfyd.js +0 -2
  385. package/dist/cli/notify-54v5z9dz.js +0 -2
  386. package/dist/cli/oauth-g5gme95c.js +0 -2
  387. package/dist/cli/output-satndjap.js +0 -16
  388. package/dist/cli/profiles-sfhpbq3m.js +0 -2
  389. package/dist/cli/provider-config-hv3xtdt4.js +0 -2
  390. package/dist/cli/provider-kwzq6g84.js +0 -2
  391. package/dist/cli/registry-fh0hdnyn.js +0 -2
  392. package/dist/cli/registry-y1y8e94r.js +0 -2
  393. package/dist/cli/repl-t4z03mqq.js +0 -11
  394. package/dist/cli/resume-fqt4chg8.js +0 -2
  395. package/dist/cli/run-flags-rysbag9t.js +0 -2
  396. package/dist/cli/runtime-j19fjbsa.js +0 -2
  397. package/dist/cli/sandbox-config-g4qxd7y5.js +0 -2
  398. package/dist/cli/server-r0b6bksk.js +0 -5
  399. package/dist/cli/session-arg-txmn5g4x.js +0 -2
  400. package/dist/cli/session-ed250d9j.js +0 -2
  401. package/dist/cli/sessions-cmd-adw7svfn.js +0 -7
  402. package/dist/cli/settings-y9rzcqx8.js +0 -2
  403. package/dist/cli/setup-jmbr11j0.js +0 -2
  404. package/dist/cli/sextant-smoke-tcth0vea.js +0 -5
  405. package/dist/cli/skills-cmd-zbdy99v6.js +0 -2
  406. package/dist/cli/smoke-1bg937kx.js +0 -8
  407. package/dist/cli/start-chat-p01cdks3.js +0 -12
  408. package/dist/cli/stream-4wmyaypz.js +0 -2
  409. package/dist/cli/task-eg4s093s.js +0 -2
  410. package/dist/cli/tasks-12v9rr9k.js +0 -2
  411. package/dist/cli/thinking-a5ngvqyh.js +0 -2
  412. package/dist/cli/todo-1wxpcecx.js +0 -2
  413. package/dist/cli/tools-2ftsya7w.js +0 -2
  414. package/dist/cli/tools-x1tj4fxm.js +0 -2
  415. package/dist/cli/trust-cmd-hccxehzb.js +0 -2
  416. package/dist/cli/update-check-ygt3vd7m.js +0 -2
  417. package/dist/cli/update-cmd-v23qhr8c.js +0 -2
  418. package/dist/cli/voice-g1gtck92.js +0 -2
  419. package/dist/cli/webfetch-0nnrjgb5.js +0 -2
  420. package/dist/cli/websearch-f0vr2p7d.js +0 -2
  421. package/dist/cli/workspace-9rq1w4ta.js +0 -2
  422. package/dist/lib/index.js +0 -62
  423. package/dist/lib/models-index.json +0 -1
  424. package/dist/lib/plugins.js +0 -6
  425. package/dist/lib/providers.js +0 -17
  426. package/dist/lib/public-api.js +0 -20
  427. package/dist/rovecode.exe +0 -4
  428. /package/{dist/cli → src/providers}/models-index.json +0 -0
@@ -0,0 +1,399 @@
1
+ /** Compaction strategy seam + adaptive trigger (port #25; ADR-007 history compaction).
2
+ *
3
+ * Strategies — oh-my-pi compaction/compaction.ts:167-231 @ 65f79e7 (MIT) keeps a strategy enum
4
+ * on the settings object and decides provider-native vs local per model; same split here:
5
+ * head-summarize the pre-#25 path, byte-for-byte: summarize the head with the weak model,
6
+ * keep the tail under half budget (aider history.py:41 via context.ts).
7
+ * keep-window deterministic, NO model call: keep the last N user turns + everything
8
+ * since the last user message, under a token cap; tool_call/tool_result
9
+ * pairs leave or stay TOGETHER (senpi deterministic-fallback.ts:28-33
10
+ * @ a0f26a6 rejects an "atomic-tool-chain-cut"; opencode message-v2.ts:
11
+ * 349-360 — every tool_use needs its tool_result on the wire). When the
12
+ * current turn alone overflows, the user's request survives and the cut
13
+ * lands inside its tool traffic (opencode compaction.ts:340-356 replays
14
+ * the last user message after an overflow compaction).
15
+ * provider-native delegate to a server-side compaction capability when the active provider
16
+ * advertises one (oh-my-pi :222-231 shouldUseProviderNativeCompaction;
17
+ * :1264-1281 local summary when remote is off/unsupported). HONESTY: none
18
+ * of rovecode's adapters advertise it today — the seam (LoopDeps.compactNative)
19
+ * and a test double ship; absent or declining → head-summarize fallback.
20
+ *
21
+ * Trigger (senpi a0f26a6): "speculative" when the ESTIMATE crosses budget × threshold, before
22
+ * the provider call (compaction-settings.ts:22-24 speculativeFraction; agent-session.ts:5874
23
+ * "Threshold: compact, NO auto-retry"). "emergency" when the provider REJECTS the request as
24
+ * a context overflow (agent-session.ts:5873 "Overflow: LLM returned context overflow error …
25
+ * compact, auto-retry"; :6061-6080 exactly ONE compact-and-retry; opencode processor.ts:
26
+ * 607-617 ContextOverflowError → needsCompaction → prompt.ts:1320-1327 create({overflow}) →
27
+ * the loop restarts the turn). Emergency plans against the OBSERVED size, not the configured
28
+ * window — the estimator just proved optimistic.
29
+ *
30
+ * Pure over Messages. The loop owns token accounting (ctx.tokenText = partsTokenText, so the
31
+ * numbers match its trigger) and persistence (SessionStore.appendEvent). No loop import here:
32
+ * loop.ts imports this module. Without a summarizer, speculative compaction stays off exactly
33
+ * as before #25; an emergency still shrinks via the deterministic window (senpi deterministic-
34
+ * fallback.ts:111 createRequiredCompactionFallback — a required compaction never wedges). */
35
+
36
+ import { randomUUID } from "node:crypto";
37
+ import type { Message, ModelRef, RunConfig } from "./types.ts";
38
+ import { estimateTokens, planCompaction as planHeadTail } from "./context.ts";
39
+ import { safeHead } from "./tool-output-budget.ts";
40
+
41
+ export type CompactionStrategy = "head-summarize" | "keep-window" | "provider-native";
42
+ export type CompactionTrigger = "speculative" | "emergency";
43
+
44
+ export const COMPACTION_STRATEGIES: readonly CompactionStrategy[] = ["head-summarize", "keep-window", "provider-native"];
45
+ export const DEFAULT_COMPACTION_STRATEGY: CompactionStrategy = "head-summarize";
46
+ /** keep-window: user turns retained BEFORE the current one when RunConfig.compactionKeepTurns is unset */
47
+ export const DEFAULT_KEEP_TURNS = 2;
48
+
49
+ /** `ROVECODE_COMPACTION=<strategy>`, case-insensitive — unset/blank → undefined (the caller applies the
50
+ * default). An unknown name is ALSO undefined (→ default) plus ONE stderr note per process and value:
51
+ * createRuntime has no warnings channel, and buildCfg re-parses the env before every run. */
52
+ const unknownStrategyNoted = new Set<string>();
53
+ export function parseCompactionStrategy(raw: string | undefined): CompactionStrategy | undefined {
54
+ const v = (raw ?? "").trim().toLowerCase();
55
+ if (v === "") return undefined;
56
+ if ((COMPACTION_STRATEGIES as readonly string[]).includes(v)) return v as CompactionStrategy;
57
+ if (!unknownStrategyNoted.has(v)) {
58
+ unknownStrategyNoted.add(v);
59
+ console.error(`rovecode: unknown ROVECODE_COMPACTION "${(raw ?? "").trim()}" — using ${DEFAULT_COMPACTION_STRATEGY} (known: ${COMPACTION_STRATEGIES.join(", ")})`);
60
+ }
61
+ return undefined;
62
+ }
63
+
64
+ /** Server-side compaction capability. Resolve to the replacement history, or null to decline
65
+ * (unsupported model, endpoint error) — the seam then falls back locally. Contract: the
66
+ * returned history's LAST message must be one the store already holds (the loop parents the
67
+ * next message on it). */
68
+ export type NativeCompactor = (
69
+ history: Message[],
70
+ opts: { model?: ModelRef; budgetTokens: number; trigger: CompactionTrigger; signal?: AbortSignal },
71
+ ) => Promise<Message[] | null>;
72
+
73
+ export interface CompactionCtx {
74
+ trigger: CompactionTrigger;
75
+ /** token-bearing text of ONE message, all parts (loop.ts partsTokenText) — tokens = estimateTokens(tokenText) */
76
+ tokenText: (m: Message) => string;
77
+ summarize?: (texts: string[]) => Promise<string>;
78
+ native?: NativeCompactor;
79
+ model?: ModelRef;
80
+ signal?: AbortSignal;
81
+ }
82
+
83
+ export interface CompactionPlan {
84
+ strategy: CompactionStrategy;
85
+ /** the configured strategy when it could not run and `strategy` is its fallback */
86
+ fallbackFrom?: CompactionStrategy;
87
+ keep: Message[];
88
+ drop: Message[];
89
+ /** head-summarize: a summarizer call is required before the history can be rebuilt */
90
+ summaryNeeded: boolean;
91
+ tokensBefore: number;
92
+ /** planning budget in force: the window (speculative) or half the observed size (emergency) */
93
+ budgetTokens: number;
94
+ }
95
+
96
+ export interface CompactionOutcome { history: Message[]; strategy: CompactionStrategy; fallbackFrom?: CompactionStrategy }
97
+
98
+ // ---------- trigger ----------
99
+
100
+ /** Adaptive trigger. Strictly GREATER than budget × threshold: AT the threshold nothing fires
101
+ * (pinned). A provider overflow rejection is an emergency whatever the estimate says. */
102
+ export function compactionTrigger(histTokens: number, cfg: RunConfig, overflow: boolean): CompactionTrigger | null {
103
+ if (overflow) return "emergency";
104
+ return histTokens > cfg.contextBudgetTokens * cfg.compactionThreshold ? "speculative" : null;
105
+ }
106
+
107
+ /** Provider phrasings of "the prompt does not fit" — opencode-2026 llm/provider-error.ts:4-38
108
+ * @ ebece6e (MIT), minus the throttling exclusions; plus HTTP 413 (provider/error.ts:175).
109
+ * rovecode error text is "HTTP <status>: <body>" (stream-errors.ts httpErrorTurn; router.ts
110
+ * classifyStreamError reads the same prefix) or a bare transport message. 429/5xx are the
111
+ * retry classes (port #23) and never an overflow, whatever their body says. */
112
+ const OVERFLOW_PATTERNS = [
113
+ /prompt is too long/i, /request_too_large/i, /input is too long for requested model/i,
114
+ /exceeds the context window/i, /maximum context length/i, /context[_ ]length[_ ]exceeded/i,
115
+ /input token count.*exceeds the maximum/i, /tokens in request more than max tokens allowed/i,
116
+ /maximum prompt length is \d+/i, /reduce the length of the messages/i, /request entity too large/i,
117
+ /model_context_window_exceeded/i, /too many tokens/i, /token limit exceeded/i,
118
+ /exceeds the available context size/i, /greater than the context length/i,
119
+ ];
120
+ const OVERFLOW_EXCLUSIONS = [/rate limit/i, /too many requests/i, /throttling/i, /service unavailable/i];
121
+
122
+ export function isContextOverflow(error: string | undefined): boolean {
123
+ if (!error || OVERFLOW_EXCLUSIONS.some((p) => p.test(error))) return false;
124
+ // the status may sit behind the router's exhausted-chain rewrite ("model chain 'x' exhausted
125
+ // (N candidates failed); last: HTTP 500: …", router.ts) — a 5xx body that mentions tokens is
126
+ // still a 5xx, never an overflow
127
+ const status = /(?:^|last: )HTTP (\d{3})\b/.exec(error)?.[1];
128
+ if (status === "413") return true;
129
+ if (status === "429" || status?.startsWith("5")) return false;
130
+ return OVERFLOW_PATTERNS.some((p) => p.test(error));
131
+ }
132
+
133
+ // ---------- planning ----------
134
+
135
+ /** Speculative plans against the window. Emergency halves the smaller of window and OBSERVED
136
+ * history, so the kept tail lands ≤ a quarter of what just overflowed (senpi shrinks the
137
+ * summarization input geometrically per overflow retry — overflow-retry-bound.test.ts:126-142;
138
+ * we get ONE retry, so one aggressive step). */
139
+ export function planningBudget(cfg: RunConfig, observedTokens: number, trigger: CompactionTrigger): number {
140
+ if (trigger === "speculative") return cfg.contextBudgetTokens;
141
+ return Math.max(1, Math.floor(Math.min(cfg.contextBudgetTokens, observedTokens) / 2));
142
+ }
143
+
144
+ function resolveStrategy(requested: CompactionStrategy, ctx: CompactionCtx): CompactionStrategy | null {
145
+ if (requested === "keep-window") return "keep-window";
146
+ if (requested === "provider-native" && ctx.native) return "provider-native";
147
+ if (ctx.summarize) return "head-summarize";
148
+ // no summarizer: speculative stays off (pre-#25 gate); an emergency must still shrink
149
+ return ctx.trigger === "emergency" ? "keep-window" : null;
150
+ }
151
+
152
+ export function planCompaction(history: Message[], cfg: RunConfig, ctx: CompactionCtx): CompactionPlan | null {
153
+ const requested = cfg.compactionStrategy ?? DEFAULT_COMPACTION_STRATEGY;
154
+ const strategy = resolveStrategy(requested, ctx);
155
+ if (!strategy) return null;
156
+ const tokens = (m: Message) => estimateTokens(ctx.tokenText(m));
157
+ const tokensBefore = history.reduce((n, m) => n + tokens(m), 0);
158
+ const budgetTokens = planningBudget(cfg, tokensBefore, ctx.trigger);
159
+ const base = { strategy, tokensBefore, budgetTokens, ...(strategy !== requested ? { fallbackFrom: requested } : {}) };
160
+ if (strategy === "provider-native") return { ...base, keep: history, drop: [], summaryNeeded: false };
161
+ if (strategy === "keep-window") {
162
+ const turns = ctx.trigger === "emergency" ? 0 : (cfg.compactionKeepTurns ?? DEFAULT_KEEP_TURNS);
163
+ return { ...base, ...keepWindow(history, tokens, budgetTokens, turns), summaryNeeded: false };
164
+ }
165
+ // head-summarize: the aider head/tail plan over {id,tokens,text} projections, mapped back to
166
+ // the real messages (loop.test.ts "rebuilds history from real messages, not projections")
167
+ const plan = planHeadTail(history.map((m) => ({ id: m.id, tokens: tokens(m), text: ctx.tokenText(m) })), budgetTokens);
168
+ const keepIds = new Set(plan.keep.map((k) => k.id));
169
+ // never an empty tail: the summary message is working-history only (never persisted), so the next
170
+ // assistant must parent on a REAL message or the store reloads with an orphan chain. When the last
171
+ // message alone exceeds half the budget the aider plan keeps nothing — keep it anyway, and when it
172
+ // is a tool result keep the assistant that issued the call too (over cap accepted over an orphan,
173
+ // exactly keepWindow's alignCut rule; the plan's ordinary cut stays prose-oriented, as before)
174
+ if (keepIds.size === 0 && history.length > 0) {
175
+ let i = history.length - 1;
176
+ while (i > 0 && history[i]!.role === "tool") i--;
177
+ for (const m of history.slice(i)) keepIds.add(m.id);
178
+ }
179
+ return { ...base, keep: history.filter((m) => keepIds.has(m.id)), drop: history.filter((m) => !keepIds.has(m.id)), summaryNeeded: true };
180
+ }
181
+
182
+ /** Deterministic window. Turn-level cuts land on user messages; when the current turn alone
183
+ * exceeds the cap (half the planning budget), the user's request is kept and the cut moves
184
+ * through its tool traffic one assistant+results group at a time (alignCut). */
185
+ export function keepWindow(history: Message[], tokens: (m: Message) => number, budgetTokens: number, keepTurns: number): { keep: Message[]; drop: Message[] } {
186
+ const cap = Math.floor(budgetTokens / 2);
187
+ const sum = (ms: Message[]) => ms.reduce((n, m) => n + tokens(m), 0);
188
+ const userIdx = history.flatMap((m, i) => (m.role === "user" ? [i] : []));
189
+ let ui = Math.max(0, userIdx.length - 1 - Math.max(0, keepTurns));
190
+ let start = userIdx.length > 0 ? userIdx[ui]! : 0;
191
+ while (sum(history.slice(start)) > cap && ui < userIdx.length - 1) start = userIdx[++ui]!;
192
+ const lastUser = userIdx.at(-1);
193
+ if (lastUser !== undefined && start === lastUser && sum(history.slice(start)) > cap) {
194
+ // the current turn alone overflows: keep the request, cut inside the traffic after it
195
+ let inner = lastUser + 1;
196
+ while (inner < history.length && tokens(history[lastUser]!) + sum(history.slice(inner)) > cap) {
197
+ const next = alignCut(history, inner + 1);
198
+ if (next <= inner || next >= history.length) break;
199
+ inner = next;
200
+ }
201
+ return { keep: [history[lastUser]!, ...history.slice(inner)], drop: [...history.slice(0, lastUser), ...history.slice(lastUser + 1, inner)] };
202
+ }
203
+ if (userIdx.length === 0) {
204
+ // no user turn at all (foreign history): plain message-level cut, pairs still intact
205
+ while (sum(history.slice(start)) > cap) {
206
+ const next = alignCut(history, start + 1);
207
+ if (next <= start || next >= history.length) break;
208
+ start = next;
209
+ }
210
+ }
211
+ return { keep: history.slice(start), drop: history.slice(0, start) };
212
+ }
213
+
214
+ /** A cut on a tool message would orphan results whose call sits in the dropped head: advance
215
+ * past the result run so call + results leave together. Returns history.length when only
216
+ * results remain — the caller stops there and keeps the calling assistant with them. */
217
+ function alignCut(history: Message[], i: number): number {
218
+ let j = i;
219
+ while (j < history.length && history[j]!.role === "tool") j++;
220
+ return j;
221
+ }
222
+
223
+ // ---------- prune (P0-2, view-only) ----------
224
+ //
225
+ // OpenCode's prune (opencode dev session/compaction.ts:271-294, erişim 2026-09-14): walk the
226
+ // history backwards, protect the last user turn plus PRUNE_PROTECT tokens of tool traffic, and
227
+ // erase older tool outputs IN PLACE; below PRUNE_MINIMUM of gain do nothing. Claude Code's
228
+ // auto-compact phase 1 is the same idea ("clears older tool outputs first"). The difference
229
+ // here is WHERE it runs: rovecode never rewrites the session record — the store keeps every
230
+ // byte (replay/export/trace stay truthful) and prune transforms only the VIEW sent to the
231
+ // provider, recomputed per turn from the full history. Because parts are stubbed in place —
232
+ // never removed — every tool_call keeps its tool_result on the wire (the invariant
233
+ // message-v2.ts:349-360 protects) by construction, and turn/branch bookkeeping is untouched.
234
+
235
+ export interface PruneConfig {
236
+ /** tokens of tool traffic protected from the cut, walking back from the last user turn
237
+ * (OpenCode PRUNE_PROTECT = 40_000) */
238
+ protectTokens: number;
239
+ /** prune only when at least this many tokens are freed (OpenCode PRUNE_MINIMUM = 20_000) —
240
+ * below it the marker costs more than the win */
241
+ minGainTokens: number;
242
+ /** tool names whose results are never pruned — OpenCode protects ["skill"]; here the active
243
+ * skill body (skill_view) is what the model is following this minute */
244
+ protectedTools: readonly string[];
245
+ /** chars of the original output kept at the head of the stub, so the model can tell what it
246
+ * was without re-running the tool (the rest is named, not shown) */
247
+ headChars: number;
248
+ }
249
+
250
+ export const DEFAULT_PRUNE_CONFIG: PruneConfig = {
251
+ protectTokens: 40_000,
252
+ minGainTokens: 20_000,
253
+ protectedTools: ["skill_view"],
254
+ headChars: 160,
255
+ };
256
+
257
+ /** stub marker — the idempotence check keys on it, so a pruned view is never re-pruned */
258
+ export const PRUNE_STUB_MARK = "[pruned from context:";
259
+
260
+ export interface PruneOutcome {
261
+ /** new message objects where stubs landed; untouched messages keep identity (===) */
262
+ view: Message[];
263
+ pruned: number;
264
+ tokensBefore: number;
265
+ tokensAfter: number;
266
+ tokensFreed: number;
267
+ }
268
+
269
+ /** Stub text: a small head of the original (what the output WAS), then the numbers and the
270
+ * remedy. The session transcript keeps the full output — the stub says so. */
271
+ function pruneStub(output: string, headChars: number): string {
272
+ const head = headChars > 0 ? safeHead(output, headChars) : "";
273
+ return `${head}\n${PRUNE_STUB_MARK} ${output.length} chars (~${estimateTokens(output)} tokens) removed — the full output is in the session transcript; re-read the file or re-run the tool if you need it]`;
274
+ }
275
+
276
+ /** Pure over the history; never mutates `history`. Returns null when the gain is below the
277
+ * threshold or there is nothing to do — the caller then sends the history unchanged and
278
+ * yields no event. */
279
+ export function pruneToolOutputs(
280
+ history: readonly Message[],
281
+ cfg: PruneConfig,
282
+ tokenText: (m: Message) => string,
283
+ ): PruneOutcome | null {
284
+ // callId → tool name (results carry no tool name; the call does)
285
+ const toolOf = new Map<string, string>();
286
+ for (const m of history) {
287
+ if (m.role !== "assistant") continue;
288
+ for (const p of m.parts) if (p.kind === "tool_call") toolOf.set(p.id, p.tool);
289
+ }
290
+ // the active turn: from the last user message on, everything is untouchable
291
+ let lastUser = -1;
292
+ for (let i = 0; i < history.length; i++) if (history[i]!.role === "user") lastUser = i;
293
+ const start = (lastUser === -1 ? history.length : lastUser) - 1;
294
+ // walk back: protect protectTokens of tool traffic; everything older is eligible
295
+ let acc = 0;
296
+ const eligible = new Set<number>();
297
+ for (let i = start; i >= 0; i--) {
298
+ const m = history[i]!;
299
+ if (m.role !== "tool") continue;
300
+ let mt = 0;
301
+ for (const p of m.parts) if (p.kind === "tool_result") mt += estimateTokens(p.output);
302
+ if (acc + mt <= cfg.protectTokens) acc += mt;
303
+ else eligible.add(i);
304
+ }
305
+ if (eligible.size === 0) return null;
306
+ const stubbable = (callId: string, ok: boolean, output: string): boolean =>
307
+ ok && // errors stay verbatim — they are the lesson
308
+ !output.includes(PRUNE_STUB_MARK) && // idempotence: never re-stub a stub
309
+ !cfg.protectedTools.includes(toolOf.get(callId) ?? "") &&
310
+ output.length > 256; // a stub must pay for its own marker
311
+ let freed = 0;
312
+ let pruned = 0;
313
+ const view = history.map((m, i) => {
314
+ if (!eligible.has(i)) return m;
315
+ let changed = false;
316
+ const parts = m.parts.map((p) => {
317
+ if (p.kind !== "tool_result" || !stubbable(p.callId, p.ok, p.output)) return p;
318
+ const stub = pruneStub(p.output, cfg.headChars);
319
+ if (stub.length >= p.output.length) return p; // paranoia: never grow
320
+ freed += estimateTokens(p.output) - estimateTokens(stub);
321
+ pruned++;
322
+ changed = true;
323
+ return { ...p, output: stub };
324
+ });
325
+ return changed ? { ...m, parts } : m;
326
+ });
327
+ if (pruned === 0 || freed < cfg.minGainTokens) return null;
328
+ const measure = (ms: readonly Message[]) => ms.reduce((n, m) => n + estimateTokens(tokenText(m)), 0);
329
+ const tokensBefore = measure(history);
330
+ const tokensAfter = measure(view);
331
+ return { view: view as Message[], pruned, tokensBefore, tokensAfter, tokensFreed: tokensBefore - tokensAfter };
332
+ }
333
+
334
+ // ---------- thrash guard (P0-4) ----------
335
+ //
336
+ // Claude Code stops auto-compacting after a few attempts when the window refills immediately
337
+ // ("auto-compact thrashing" — code.claude.com/docs/en/troubleshooting.md). The same protection
338
+ // here, as a pure pace ledger the loop carries per run: a compaction within `rapidWindow` turns
339
+ // of the previous one earns one strike, one that fails to shrink the wire by 5% earns another —
340
+ // three strikes and the run stops compacting. (A first-ever compaction can LEGITIMATELY not
341
+ // shrink — a one-token head summarizes into a ten-token summary; that alone must never trip the
342
+ // guard, which is why a strike pair, not the bare fact, is the signal.) An emergency arriving
343
+ // during cooldown ends the run in error instead of re-driving a request whose compaction just
344
+ // failed — a loop that will not stop is worse than one that stops early (core/loop.ts).
345
+
346
+ export interface CompactionPace { lastTurn: number; strikes: number }
347
+ export const initialCompactionPace: CompactionPace = { lastTurn: -1_000_000, strikes: 0 };
348
+ export const DEFAULT_RAPID_WINDOW_TURNS = 2;
349
+ export const COOLDOWN_STRIKES = 3;
350
+
351
+ /** Strike ledger: +1 when rapid (≤ rapidWindow turns since the last compaction), +1 when the
352
+ * wire did not shrink by ≥5%. A spaced, effective compaction clears the ledger. */
353
+ export function updateCompactionPace(pace: CompactionPace, turn: number, shrankWell: boolean, rapidWindow: number): CompactionPace {
354
+ const strikes = (turn - pace.lastTurn <= rapidWindow ? 1 : 0) + (shrankWell ? 0 : 1);
355
+ return { lastTurn: turn, strikes: strikes === 0 ? 0 : pace.strikes + strikes };
356
+ }
357
+
358
+ export function compactionCooldown(pace: CompactionPace, rapidWindow: number): boolean {
359
+ return rapidWindow > 0 && pace.strikes >= COOLDOWN_STRIKES;
360
+ }
361
+
362
+ // ---------- apply ----------
363
+
364
+ /** Rebuild the working history per plan. null = nothing to compact (keep-window found nothing
365
+ * droppable; a native decline with no local fallback) — the caller yields no event then. */
366
+ export async function applyCompaction(history: Message[], plan: CompactionPlan, cfg: RunConfig, ctx: CompactionCtx): Promise<CompactionOutcome | null> {
367
+ const tag = plan.fallbackFrom ? { fallbackFrom: plan.fallbackFrom } : {};
368
+ if (plan.strategy === "provider-native") {
369
+ let out: Message[] | null = null;
370
+ if (ctx.native) {
371
+ try { out = await ctx.native(history, { model: ctx.model, budgetTokens: plan.budgetTokens, trigger: ctx.trigger, signal: ctx.signal }); } catch { out = null; }
372
+ }
373
+ if (out) return { history: out, strategy: "provider-native", ...tag };
374
+ // declined at apply time: re-plan locally (oh-my-pi :1264 keeps a portable local summary
375
+ // rather than strand the history); the local plan is never provider-native — bounded
376
+ const local = planCompaction(history, { ...cfg, compactionStrategy: DEFAULT_COMPACTION_STRATEGY }, { ...ctx, native: undefined });
377
+ if (!local) return null;
378
+ const res = await applyCompaction(history, local, cfg, { ...ctx, native: undefined });
379
+ return res ? { ...res, fallbackFrom: "provider-native" } : null;
380
+ }
381
+ if (plan.strategy === "keep-window") {
382
+ if (plan.drop.length === 0) return null;
383
+ const dropped = plan.drop.reduce((n, m) => n + estimateTokens(ctx.tokenText(m)), 0);
384
+ const marker: Message = {
385
+ id: randomUUID(), role: "system",
386
+ parts: [{ kind: "text", text: `[context compacted (keep-window): ${plan.drop.length} earlier messages, ~${dropped} tokens, removed from the working context]` }],
387
+ parentId: plan.keep[0]?.id ?? null, createdAt: Date.now(),
388
+ };
389
+ return { history: [marker, ...plan.keep], strategy: "keep-window", ...tag };
390
+ }
391
+ if (!ctx.summarize) return null;
392
+ const summary = await ctx.summarize(plan.drop.map((m) => ctx.tokenText(m)));
393
+ const compactMsg: Message = {
394
+ id: randomUUID(), role: "system",
395
+ parts: [{ kind: "text", text: `Summary of earlier conversation:\n${summary}` }],
396
+ parentId: history[0]?.id ?? null, createdAt: Date.now(),
397
+ };
398
+ return { history: [compactMsg, ...plan.keep], strategy: "head-summarize", ...tag };
399
+ }