rovecode 0.4.0-beta.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -72
- package/THIRD_PARTY_NOTICES.md +0 -44
- package/bin/rovecode.ts +21 -0
- package/package.json +16 -38
- package/src/account/keys.ts +97 -0
- package/src/account/login.ts +158 -0
- package/src/account/provision.ts +47 -0
- package/src/account/store.ts +63 -0
- package/src/acp/server.ts +373 -0
- package/src/cli/account-cmd.ts +116 -0
- package/src/cli/connect.ts +244 -0
- package/src/cli/context-cmd.ts +199 -0
- package/src/cli/dispatch.ts +109 -0
- package/src/cli/doctor.ts +324 -0
- package/src/cli/export.ts +278 -0
- package/src/cli/help.ts +240 -0
- package/src/cli/is-tui-invocation.ts +8 -0
- package/src/cli/main.ts +599 -0
- package/src/cli/market-cmd.ts +658 -0
- package/src/cli/mcp-market-cmd.ts +299 -0
- package/src/cli/output.ts +382 -0
- package/src/cli/repl.ts +172 -0
- package/src/cli/resume.ts +32 -0
- package/src/cli/run-limits.ts +78 -0
- package/src/cli/runtime.ts +792 -0
- package/src/cli/setup.ts +187 -0
- package/src/cli/update-cmd.ts +78 -0
- package/src/cli/workflow-cmd.ts +100 -0
- package/src/coding/checkpoints.ts +270 -0
- package/src/coding/diff.ts +136 -0
- package/src/coding/files.ts +339 -0
- package/src/coding/hashline.ts +319 -0
- package/src/coding/lsp.ts +406 -0
- package/src/coding/repomap-cache.ts +99 -0
- package/src/coding/repomap-files.ts +110 -0
- package/src/coding/repomap.ts +392 -0
- package/src/core/compaction.ts +399 -0
- package/src/core/config.ts +289 -0
- package/src/core/context-report.ts +228 -0
- package/src/core/context.ts +60 -0
- package/src/core/count-remote.ts +107 -0
- package/src/core/execpolicy-rules.ts +196 -0
- package/src/core/execpolicy.ts +385 -0
- package/src/core/executor.ts +397 -0
- package/src/core/guardrails.ts +400 -0
- package/src/core/hooks.ts +398 -0
- package/src/core/images.ts +230 -0
- package/src/core/intro.ts +236 -0
- package/src/core/loop.ts +621 -0
- package/src/core/modes.ts +372 -0
- package/src/core/orchestrator.ts +207 -0
- package/src/core/reflection.ts +165 -0
- package/src/core/sandbox-config.ts +167 -0
- package/src/core/session-images.ts +73 -0
- package/src/core/session.ts +398 -0
- package/src/core/settings.ts +98 -0
- package/src/core/stuck-detector.ts +273 -0
- package/src/core/tasks.ts +374 -0
- package/src/core/token-scale.ts +108 -0
- package/src/core/tool-output-budget.ts +166 -0
- package/src/core/tools.ts +288 -0
- package/src/core/types.ts +330 -0
- package/src/core/update-check.ts +171 -0
- package/src/core/update.ts +158 -0
- package/src/core/usage.ts +204 -0
- package/src/core/validate.ts +121 -0
- package/src/core/verify-gate.ts +159 -0
- package/src/core/verify.ts +237 -0
- package/src/core/voice.ts +158 -0
- package/src/core/win-job.ts +183 -0
- package/src/design/audit.ts +797 -0
- package/src/design/direction.ts +190 -0
- package/src/design/rules.ts +157 -0
- package/src/eval/bench.ts +150 -0
- package/src/eval/gauntlet-runner.ts +218 -0
- package/src/eval/gauntlet.ts +226 -0
- package/src/eval/grader.ts +186 -0
- package/src/eval/record.ts +202 -0
- package/src/eval/redact.ts +141 -0
- package/src/eval/replay.ts +147 -0
- package/src/eval/trajectory.ts +373 -0
- package/src/index.ts +17 -0
- package/src/market/catalogs/mcp-docs.json +111 -0
- package/src/market/catalogs/plugins.json +111 -0
- package/src/market/catalogs/skills.json +478 -0
- package/src/market/clone.ts +72 -0
- package/src/market/context-cost.ts +121 -0
- package/src/market/digest.ts +106 -0
- package/src/market/index.ts +22 -0
- package/src/market/install.ts +578 -0
- package/src/market/manifest.ts +187 -0
- package/src/market/prereq.ts +145 -0
- package/src/market/registry.ts +363 -0
- package/src/market/resolve.ts +111 -0
- package/src/market/types.ts +236 -0
- package/src/market/validate.ts +227 -0
- package/src/mcp/client.ts +431 -0
- package/src/mcp/config.ts +239 -0
- package/src/mcp/local-package.ts +211 -0
- package/src/mcp/market-catalog.ts +84 -0
- package/src/mcp/market-install.ts +289 -0
- package/src/mcp/market.ts +0 -0
- package/src/mcp/tools.ts +131 -0
- package/src/mcp/trust.ts +49 -0
- package/src/memory/blocks.ts +175 -0
- package/src/memory/recall.ts +355 -0
- package/src/memory/store.ts +105 -0
- package/src/memory/tools.ts +99 -0
- package/src/plugins/cli.ts +123 -0
- package/src/plugins/discover.ts +108 -0
- package/src/plugins/index.ts +50 -0
- package/src/plugins/init.ts +140 -0
- package/src/plugins/install.ts +184 -0
- package/src/plugins/load.ts +149 -0
- package/src/plugins/manifest.ts +106 -0
- package/src/plugins/state.ts +83 -0
- package/src/providers/auth.ts +293 -0
- package/src/providers/cache.ts +223 -0
- package/src/providers/catalog-local.ts +160 -0
- package/src/providers/catalog.ts +408 -0
- package/src/providers/middleware-context.ts +86 -0
- package/src/providers/middleware.ts +373 -0
- package/src/providers/profile-glm53.ts +111 -0
- package/src/providers/profile-sonnet5-persona.ts +65 -0
- package/src/providers/profile-sonnet5-voice.ts +23 -0
- package/src/providers/profiles.ts +156 -0
- package/src/providers/provider-config.ts +311 -0
- package/src/providers/registry.ts +302 -0
- package/src/providers/response-validation.ts +80 -0
- package/src/providers/retry.ts +234 -0
- package/src/providers/router.ts +294 -0
- package/src/providers/sse.ts +26 -0
- package/src/providers/stream-errors.ts +117 -0
- package/src/providers/stream.ts +569 -0
- package/src/providers/thinking.ts +189 -0
- package/src/providers/wire-messages.ts +129 -0
- package/src/sdk/client.ts +225 -0
- package/src/sdk/index.ts +3 -0
- package/src/server/dashboard.ts +144 -0
- package/src/server/http.ts +343 -0
- package/src/server/openapi.ts +246 -0
- package/src/sextant/card-hits.ts +102 -0
- package/src/sextant/card-keys.ts +55 -0
- package/src/sextant/context-source.ts +157 -0
- package/src/sextant/draw-agents.ts +273 -0
- package/src/sextant/draw-code.ts +388 -0
- package/src/sextant/draw-context.ts +222 -0
- package/src/sextant/draw-frame.ts +164 -0
- package/src/sextant/draw-market.ts +573 -0
- package/src/sextant/draw-messages.ts +386 -0
- package/src/sextant/draw-pet.ts +230 -0
- package/src/sextant/draw-plan.ts +159 -0
- package/src/sextant/draw-tabs.ts +85 -0
- package/src/sextant/draw-util.ts +65 -0
- package/src/sextant/engine.ts +230 -0
- package/src/sextant/frame-hits.ts +25 -0
- package/src/sextant/frame.ts +101 -0
- package/src/sextant/git-status.ts +197 -0
- package/src/sextant/grid.ts +59 -0
- package/src/sextant/input.ts +119 -0
- package/src/sextant/keys.ts +488 -0
- package/src/sextant/layout.ts +86 -0
- package/src/sextant/local-commands.ts +156 -0
- package/src/sextant/market-source.ts +287 -0
- package/src/sextant/mentions.ts +141 -0
- package/src/sextant/message-hits.ts +26 -0
- package/src/sextant/model.ts +387 -0
- package/src/sextant/overlays.ts +451 -0
- package/src/sextant/panel-hits.ts +38 -0
- package/src/sextant/pet.ts +399 -0
- package/src/sextant/screen.ts +324 -0
- package/src/sextant/scroll-hits.ts +66 -0
- package/src/sextant/scrollbar.ts +82 -0
- package/src/sextant/selection.ts +123 -0
- package/src/sextant/sextant-bridge.ts +174 -0
- package/src/sextant/sextant-cards.ts +142 -0
- package/src/sextant/sextant-diff-base.ts +63 -0
- package/src/sextant/sextant-files.ts +154 -0
- package/src/sextant/sextant-frame-loop.ts +314 -0
- package/src/sextant/sextant-renderer.ts +478 -0
- package/src/sextant/sextant-repo.ts +131 -0
- package/src/sextant/theme.ts +66 -0
- package/src/sextant/tool-rows.ts +189 -0
- package/src/sextant/types.ts +473 -0
- package/src/skills/index.ts +306 -0
- package/src/skills/tools.ts +69 -0
- package/src/skills/versioned.ts +227 -0
- package/src/telemetry/otel.ts +353 -0
- package/src/telemetry/otlp.ts +68 -0
- package/src/tools/ask-user.ts +156 -0
- package/src/tools/design.ts +151 -0
- package/src/tools/evalcell.ts +338 -0
- package/src/tools/html-text.ts +139 -0
- package/src/tools/provider.ts +149 -0
- package/src/tools/task.ts +216 -0
- package/src/tools/todo.ts +320 -0
- package/src/tools/webfetch.ts +331 -0
- package/src/tui/app.ts +608 -0
- package/src/tui/attach.ts +127 -0
- package/src/tui/checkpoints-cmd.ts +70 -0
- package/src/tui/clipboard-image.ts +81 -0
- package/src/tui/commands.ts +277 -0
- package/src/tui/cost.ts +108 -0
- package/src/tui/info-cmd.ts +144 -0
- package/src/tui/mcp-cmd.ts +128 -0
- package/src/tui/modes-cmd.ts +45 -0
- package/src/tui/overlays.ts +97 -0
- package/src/tui/pi-renderer.ts +424 -0
- package/src/tui/providers-cmd.ts +366 -0
- package/src/tui/renderer.ts +101 -0
- package/src/tui/replay-marker.ts +29 -0
- package/src/tui/session-cmd.ts +146 -0
- package/src/tui/sextant-attach.ts +68 -0
- package/src/tui/sextant-io.ts +184 -0
- package/src/tui/sextant-smoke.ts +110 -0
- package/src/tui/smoke.ts +72 -0
- package/src/tui/theme.ts +59 -0
- package/src/tui/todo-label.ts +7 -0
- package/src/workflow/engine.ts +266 -0
- package/tsconfig.json +30 -0
- package/vendor/pi-tui/LICENSE +21 -0
- package/vendor/pi-tui/PATCHES.md +12 -0
- package/vendor/pi-tui/PROVENANCE.md +12 -0
- package/vendor/pi-tui/README.upstream.md +854 -0
- package/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
- package/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
- package/vendor/pi-tui/src/alt-screen-search.ts +158 -0
- package/vendor/pi-tui/src/autocomplete.ts +827 -0
- package/vendor/pi-tui/src/components/alt-screen-flash.ts +52 -0
- package/vendor/pi-tui/src/components/box.ts +138 -0
- package/vendor/pi-tui/src/components/cancellable-loader.ts +41 -0
- package/vendor/pi-tui/src/components/editor.ts +2364 -0
- package/vendor/pi-tui/src/components/h-stack.ts +45 -0
- package/vendor/pi-tui/src/components/image.ts +128 -0
- package/vendor/pi-tui/src/components/input.ts +448 -0
- package/vendor/pi-tui/src/components/loader.ts +93 -0
- package/vendor/pi-tui/src/components/markdown.ts +1016 -0
- package/vendor/pi-tui/src/components/scroll-view.ts +217 -0
- package/vendor/pi-tui/src/components/select-list.ts +230 -0
- package/vendor/pi-tui/src/components/settings-list.ts +277 -0
- package/vendor/pi-tui/src/components/spacer.ts +29 -0
- package/vendor/pi-tui/src/components/stack.ts +155 -0
- package/vendor/pi-tui/src/components/text.ts +108 -0
- package/vendor/pi-tui/src/components/truncated-text.ts +66 -0
- package/vendor/pi-tui/src/components/v-stack.ts +34 -0
- package/vendor/pi-tui/src/editor-component.ts +75 -0
- package/vendor/pi-tui/src/fuzzy.ts +138 -0
- package/vendor/pi-tui/src/index.ts +149 -0
- package/vendor/pi-tui/src/keybindings.ts +321 -0
- package/vendor/pi-tui/src/keys.ts +1402 -0
- package/vendor/pi-tui/src/kill-ring.ts +47 -0
- package/vendor/pi-tui/src/latex.ts +1381 -0
- package/vendor/pi-tui/src/layout-node.ts +52 -0
- package/vendor/pi-tui/src/layout.ts +411 -0
- package/vendor/pi-tui/src/native-modifiers.ts +60 -0
- package/vendor/pi-tui/src/native-module-path.ts +32 -0
- package/vendor/pi-tui/src/stdin-buffer.ts +445 -0
- package/vendor/pi-tui/src/terminal-colors.ts +74 -0
- package/vendor/pi-tui/src/terminal-image.ts +701 -0
- package/vendor/pi-tui/src/terminal.ts +554 -0
- package/vendor/pi-tui/src/tui-alt-screen.ts +1379 -0
- package/vendor/pi-tui/src/tui-main-screen.ts +655 -0
- package/vendor/pi-tui/src/tui.ts +1264 -0
- package/vendor/pi-tui/src/undo-stack.ts +29 -0
- package/vendor/pi-tui/src/utils.ts +1327 -0
- package/vendor/pi-tui/src/word-navigation.ts +118 -0
- package/vendor/pi-tui/test/test-themes.ts +39 -0
- package/vendor/pi-tui/test/virtual-terminal.ts +219 -0
- package/CHANGELOG.md +0 -527
- package/bin/rovecode.js +0 -24
- package/dist/cli/app-j6gn14w3.js +0 -2
- package/dist/cli/ask-user-cwstt8fz.js +0 -2
- package/dist/cli/auth-login-9bbp9915.js +0 -2
- package/dist/cli/auth-m8p9grty.js +0 -2
- package/dist/cli/bench-16zqdms5.js +0 -9
- package/dist/cli/catalog-1xchffa4.js +0 -2
- package/dist/cli/cli-1n1zb64f.js +0 -2
- package/dist/cli/client-2t9gjkck.js +0 -2
- package/dist/cli/commands-exafvm2b.js +0 -2
- package/dist/cli/connect-6zde0kn3.js +0 -2
- package/dist/cli/context-cmd-5t43wgqt.js +0 -2
- package/dist/cli/context-report-kt01pw8y.js +0 -2
- package/dist/cli/count-remote-ap7x3vh6.js +0 -2
- package/dist/cli/design-ne5zszyh.js +0 -2
- package/dist/cli/dispatch-2r5myxye.js +0 -2
- package/dist/cli/doctor-ws4fh4tn.js +0 -3
- package/dist/cli/executor-bdrjn634.js +0 -2
- package/dist/cli/export-1mxb9g5p.js +0 -2
- package/dist/cli/files-g104xghh.js +0 -2
- package/dist/cli/gauntlet-07xrjpj7.js +0 -2
- package/dist/cli/gauntlet-runner-xvy64436.js +0 -10
- package/dist/cli/gauntlet-wave3-jm91yt5w.js +0 -5
- package/dist/cli/gauntlet-wave4-r13py7p1.js +0 -14
- package/dist/cli/hashline-znvrat11.js +0 -2
- package/dist/cli/http-xafw6fsh.js +0 -143
- package/dist/cli/index-1sgjm25y.js +0 -2
- package/dist/cli/init-g2m0tn4m.js +0 -51
- package/dist/cli/install-avaqjjqq.js +0 -2
- package/dist/cli/loop-mmpfft01.js +0 -2
- package/dist/cli/main-0904f6ps.js +0 -5
- package/dist/cli/main-0ab9fc26.js +0 -9
- package/dist/cli/main-0jys2ccn.js +0 -3
- package/dist/cli/main-0mtcdbs7.js +0 -3
- package/dist/cli/main-0z1w2zsg.js +0 -3
- package/dist/cli/main-1dchs7xv.js +0 -18
- package/dist/cli/main-1ereejm1.js +0 -3
- package/dist/cli/main-1k1kw6b5.js +0 -3
- package/dist/cli/main-27y4sm2k.js +0 -38
- package/dist/cli/main-2wwjex5j.js +0 -58
- package/dist/cli/main-2yeveeve.js +0 -6
- package/dist/cli/main-2yfck9b5.js +0 -3
- package/dist/cli/main-2zmzgkwh.js +0 -3
- package/dist/cli/main-351pz3z7.js +0 -7
- package/dist/cli/main-3gjqfh7a.js +0 -6
- package/dist/cli/main-3nf3kgve.js +0 -3
- package/dist/cli/main-3pjrb2hd.js +0 -3
- package/dist/cli/main-3rxcvgna.js +0 -19
- package/dist/cli/main-4b3jgy66.js +0 -19
- package/dist/cli/main-4wndhjdc.js +0 -7
- package/dist/cli/main-4xcmvxnk.js +0 -3
- package/dist/cli/main-5tbz0wbz.js +0 -4
- package/dist/cli/main-5ywnwthm.js +0 -3
- package/dist/cli/main-6b62vkz0.js +0 -14
- package/dist/cli/main-6dnk69vp.js +0 -3
- package/dist/cli/main-6genrmhs.js +0 -136
- package/dist/cli/main-73g7eff4.js +0 -15
- package/dist/cli/main-7c5thhjd.js +0 -5
- package/dist/cli/main-7rn6bqje.js +0 -3
- package/dist/cli/main-80haw7qk.js +0 -4
- package/dist/cli/main-875s60s2.js +0 -4
- package/dist/cli/main-8kjxbpw4.js +0 -8
- package/dist/cli/main-90ds1z4e.js +0 -10
- package/dist/cli/main-9etavkew.js +0 -3
- package/dist/cli/main-a9njrkk1.js +0 -3
- package/dist/cli/main-aecrjq2d.js +0 -12
- package/dist/cli/main-ck9asesq.js +0 -9
- package/dist/cli/main-cta9racd.js +0 -4
- package/dist/cli/main-ddv7j2ag.js +0 -3
- package/dist/cli/main-dfreez27.js +0 -10
- package/dist/cli/main-f7rw7des.js +0 -3
- package/dist/cli/main-ggcn7rd7.js +0 -5
- package/dist/cli/main-gzkmycnv.js +0 -3
- package/dist/cli/main-hq51jg8v.js +0 -18
- package/dist/cli/main-jft389w9.js +0 -8
- package/dist/cli/main-k1eqkg83.js +0 -3
- package/dist/cli/main-k2y8a2aw.js +0 -9
- package/dist/cli/main-kcpbykxz.js +0 -4
- package/dist/cli/main-kd488vje.js +0 -22
- package/dist/cli/main-kh32yvgk.js +0 -5
- package/dist/cli/main-kqxnqjnv.js +0 -25
- package/dist/cli/main-kyn0xnsg.js +0 -3
- package/dist/cli/main-m1kk6fp5.js +0 -21
- package/dist/cli/main-mv40pcr2.js +0 -4
- package/dist/cli/main-n0t3973w.js +0 -3
- package/dist/cli/main-nqveez48.js +0 -4
- package/dist/cli/main-pknhvrmj.js +0 -3
- package/dist/cli/main-pn1w7a7j.js +0 -3
- package/dist/cli/main-prxxs70n.js +0 -4
- package/dist/cli/main-q3vsesf9.js +0 -3
- package/dist/cli/main-qsevpgsv.js +0 -3
- package/dist/cli/main-rdgdw24b.js +0 -25
- package/dist/cli/main-rfth4tbm.js +0 -16
- package/dist/cli/main-rg0wn0xf.js +0 -5
- package/dist/cli/main-sdmxhtv8.js +0 -4
- package/dist/cli/main-skbp13js.js +0 -18
- package/dist/cli/main-t4xnd213.js +0 -7
- package/dist/cli/main-vqak588n.js +0 -4
- package/dist/cli/main-w2n1303f.js +0 -9
- package/dist/cli/main-wbrdspr2.js +0 -5
- package/dist/cli/main-wsrg79c1.js +0 -7
- package/dist/cli/main-x4r0fne4.js +0 -5
- package/dist/cli/main-xea2f3tn.js +0 -6
- package/dist/cli/main-xg704a3c.js +0 -3
- package/dist/cli/main-xvnrabfp.js +0 -16
- package/dist/cli/main-xy53xf0r.js +0 -4
- package/dist/cli/main-y1fqy60y.js +0 -3
- package/dist/cli/main-yn8cd281.js +0 -34
- package/dist/cli/main-yr0ksc0h.js +0 -4
- package/dist/cli/main-z2ex2vyf.js +0 -4
- package/dist/cli/main-z3aayzvq.js +0 -3
- package/dist/cli/main-zaqh35jg.js +0 -3
- package/dist/cli/main-zc2e8e46.js +0 -4
- package/dist/cli/main-zzrfw6cf.js +0 -13
- package/dist/cli/main.js +0 -280
- package/dist/cli/market-cmd-e14kmx9n.js +0 -5
- package/dist/cli/mcp-login-wq7ktdek.js +0 -2
- package/dist/cli/mcp-market-cmd-9mg3jecy.js +0 -2
- package/dist/cli/notify-b7qc0cjb.js +0 -2
- package/dist/cli/oauth-z8whcgfx.js +0 -2
- package/dist/cli/output-b3ewj3ps.js +0 -16
- package/dist/cli/profiles-6mr5he5e.js +0 -2
- package/dist/cli/provider-config-g7j42q8x.js +0 -2
- package/dist/cli/provider-jr1y8vvm.js +0 -2
- package/dist/cli/registry-s8yk86g0.js +0 -2
- package/dist/cli/registry-t6p8d4mn.js +0 -2
- package/dist/cli/repl-bajwe1mh.js +0 -11
- package/dist/cli/resume-rwn9nz7y.js +0 -2
- package/dist/cli/run-flags-nah7ndpt.js +0 -2
- package/dist/cli/runtime-n7gafzhb.js +0 -2
- package/dist/cli/sandbox-config-emdy18x4.js +0 -2
- package/dist/cli/server-b0nvs2bn.js +0 -5
- package/dist/cli/session-arg-y75wd4kj.js +0 -2
- package/dist/cli/session-j62evmjq.js +0 -2
- package/dist/cli/sessions-cmd-tsnwz0ns.js +0 -7
- package/dist/cli/settings-df10wfez.js +0 -2
- package/dist/cli/setup-jzvv72fg.js +0 -2
- package/dist/cli/sextant-smoke-37m81ke6.js +0 -5
- package/dist/cli/skills-cmd-gjxnxnhx.js +0 -2
- package/dist/cli/smoke-p7748apt.js +0 -8
- package/dist/cli/start-chat-s4st3mm0.js +0 -12
- package/dist/cli/stream-gmeyewds.js +0 -2
- package/dist/cli/task-gh0kkp3n.js +0 -2
- package/dist/cli/tasks-z1kfpe8e.js +0 -2
- package/dist/cli/thinking-0eqkrz6t.js +0 -2
- package/dist/cli/todo-5brcrt9m.js +0 -2
- package/dist/cli/tools-7pzm0vj9.js +0 -2
- package/dist/cli/tools-s635p6s8.js +0 -2
- package/dist/cli/trust-cmd-cjav8zgm.js +0 -2
- package/dist/cli/update-check-pt31bm2f.js +0 -2
- package/dist/cli/update-cmd-tk131s9t.js +0 -2
- package/dist/cli/voice-56nabd8d.js +0 -2
- package/dist/cli/webfetch-xd8q596m.js +0 -2
- package/dist/cli/websearch-5hkf98k1.js +0 -2
- package/dist/cli/workflow-cmd-cy3cvzjp.js +0 -4
- package/dist/cli/workspace-q10g5z3e.js +0 -2
- package/dist/lib/index.js +0 -62
- package/dist/lib/models-index.json +0 -1
- package/dist/lib/plugins.js +0 -55
- package/dist/lib/providers.js +0 -17
- package/dist/lib/public-api.js +0 -20
- package/dist/lib/sdk.js +0 -360
- /package/{dist/cli → src/providers}/models-index.json +0 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/** Reflection loop for failed edits (port #28) — a BUILT-IN hook set on the hooks-v2 seam
|
|
2
|
+
* (core/hooks.ts HookSet: pre_run / post_tool / post_run), attached by cli/runtime.ts as
|
|
3
|
+
* "reflection" at createRuntime; core/loop.ts is untouched.
|
|
4
|
+
*
|
|
5
|
+
* Mechanism: when a MUTATING tool (hashline edit / write) FAILS — or succeeds but carries the LSP
|
|
6
|
+
* gate's diagnostics note (coding/lsp.ts formatGateNote) — post_tool pushes ONE nudge into the
|
|
7
|
+
* runtime's SteeringQueue. The loop drains steering at the next turn start as a user message
|
|
8
|
+
* (core/loop.ts "steering drain point"), so the model's next request carries the failed tool_result
|
|
9
|
+
* AND a user-visible `reflection: …` message: re-read, fix, retry, or say why it cannot be fixed.
|
|
10
|
+
* That IS aider's reflected_message — the edit-failure / lint / test text becomes the next user
|
|
11
|
+
* message and the model gets a bounded number of tries.
|
|
12
|
+
*
|
|
13
|
+
* Bounds (pure: no I/O, no clock, never touches the tool output — hashline's text is already the
|
|
14
|
+
* actionable one, describeEditFailure):
|
|
15
|
+
* - per-run cap: DEFAULT_REFLECTION_MAX (2) nudges per runId, ROVECODE_REFLECTION_MAX overrides (aider
|
|
16
|
+
* max_reflections = 3, base_coder.py:101); reset on pre_run (aider init_before_message resets
|
|
17
|
+
* reflected_message / num_reflections per user message, base_coder.py:864-871)
|
|
18
|
+
* - identical consecutive failures never re-nudge — repetition is the loop guard's territory
|
|
19
|
+
* (core/guardrails.ts keeps firing: post_tool runs AFTER the guard's result pass, and a stubbed
|
|
20
|
+
* call never reaches post_tool at all); the guard's own `[loop-guard]` warn suffix is stripped
|
|
21
|
+
* before comparing, so a warned repeat still counts as identical
|
|
22
|
+
* - never for an aborted result (ABORTED_TOOL_RESULT), never for non-mutating tools, never for a
|
|
23
|
+
* clean success (which also ends the "consecutive" streak — the next identical failure is new)
|
|
24
|
+
* - a nudge the loop never drained (budget / abort right after the failing call) is swept out of
|
|
25
|
+
* the queue at the run boundary (post_run, and pre_run of the next run) so it cannot open the
|
|
26
|
+
* next run's first turn; other steering messages (port #26 task notes) are preserved
|
|
27
|
+
* - ROVECODE_REFLECTION=0 → the set is not registered (cli/runtime.ts door)
|
|
28
|
+
* - ownership (fix-wave 4, #26 MED-A): ONE set serves ONE queue, so it acts only on runs it OWNS
|
|
29
|
+
* (opts.owns; cli/runtime.ts binds it to `ctx.sessionId === the ACTIVE session store's id`, which
|
|
30
|
+
* TUI session switches keep current via setSessionStore). Background-task children (port #26) run
|
|
31
|
+
* under the runtime's hooks since 0cf2992 with their OWN store ids: before this, a child's failed
|
|
32
|
+
* edit nudged the PARENT's next turn about an edit it never made, and a child's run boundary
|
|
33
|
+
* swept the parent's own pending nudge. Trade-off: children get NO reflection (a child's queue is
|
|
34
|
+
* unreachable from a hook — HookCtx carries no queue handle on purpose, hooks.ts header); their
|
|
35
|
+
* loop guard still bounds identical repeats and their failure text reaches the parent through the
|
|
36
|
+
* task note / `task_status result`.
|
|
37
|
+
*
|
|
38
|
+
* Source (pattern only, no code copied; Apache-2.0, credited in THIRD_PARTY_NOTICES.md): aider
|
|
39
|
+
* @ 5dc9490 aider/coders/base_coder.py — run_one :924-944 (`while message: self.reflected_message
|
|
40
|
+
* = None; … if not self.reflected_message: break; if self.num_reflections >= self.max_reflections:
|
|
41
|
+
* tool_warning("Only N reflections allowed, stopping."); return; self.num_reflections += 1;
|
|
42
|
+
* message = self.reflected_message`), apply_updates :2296-2328 (an edit-format / apply error →
|
|
43
|
+
* `self.reflected_message = str(err)`), lint / test errors as the reflected message :1596-1623;
|
|
44
|
+
* editblock_coder.py:84-124 the actionable failed-block report the message carries.
|
|
45
|
+
* Deviations: the nudge rides the steering queue (the only way a hook can add a user message), the
|
|
46
|
+
* cap is 2 with the loop guard as the second bound, identical repeats are deduped, not re-sent. */
|
|
47
|
+
|
|
48
|
+
import type { HookCtx, HookSet } from "./hooks.ts";
|
|
49
|
+
import type { SteeringQueue } from "./loop.ts";
|
|
50
|
+
import { ABORTED_TOOL_RESULT } from "./tools.ts";
|
|
51
|
+
|
|
52
|
+
export const DEFAULT_REFLECTION_MAX = 2;
|
|
53
|
+
/** chars of the tool's error text carried in a failure nudge */
|
|
54
|
+
export const REFLECTION_ERROR_CHARS = 300;
|
|
55
|
+
/** chars of the diagnostics list carried in a diagnostics nudge */
|
|
56
|
+
export const REFLECTION_DIAG_CHARS = 400;
|
|
57
|
+
export const REFLECTION_PREFIX = "reflection: ";
|
|
58
|
+
/** the mutating tools watched by default — hashline's edit + write (kind "write") */
|
|
59
|
+
export const DEFAULT_REFLECTION_TOOLS: readonly string[] = ["edit", "write"];
|
|
60
|
+
const MAX_DIAG_LINES = 5;
|
|
61
|
+
const MAX_TRACKED_RUNS = 8;
|
|
62
|
+
const REMEDY = "Re-read the file, fix the anchors/content, and retry; if it cannot be fixed, say why and stop.";
|
|
63
|
+
/** the loop guard's warn suffix — core/tools.ts dispatch appends `\n\n[loop-guard] …` to the output */
|
|
64
|
+
const GUARD_NOTE = /\n\n\[loop-guard\] [\s\S]*$/;
|
|
65
|
+
/** coding/lsp.ts formatGateNote: `\n\nlsp-gate (<server>): N error(s) in <path> — fix before proceeding:\n<lines>` */
|
|
66
|
+
const LSP_NOTE = /\n\nlsp-gate \([^)\n]*\): \d+ error\(s\) in [^\n]* — fix before proceeding:\n([\s\S]*)$/;
|
|
67
|
+
|
|
68
|
+
export interface ReflectionOptions {
|
|
69
|
+
/** the runtime's ONE steering queue (cli/runtime.ts rt.steering) — the nudge lands on the next turn */
|
|
70
|
+
steering: Pick<SteeringQueue, "push" | "drainAll">;
|
|
71
|
+
/** nudges per run; default reflectionMax() (ROVECODE_REFLECTION_MAX → 2) */
|
|
72
|
+
max?: number;
|
|
73
|
+
/** tool names treated as mutating; default DEFAULT_REFLECTION_TOOLS */
|
|
74
|
+
tools?: Iterable<string>;
|
|
75
|
+
/** observer for every nudge pushed (tests, surfaces) */
|
|
76
|
+
onNudge?: (text: string) => void;
|
|
77
|
+
/** which runs this set serves — false → pre_run / post_run / post_tool return at once (no nudge, no
|
|
78
|
+
* sweep, no per-run state). Default: every run. cli/runtime.ts: the active session store's runs. */
|
|
79
|
+
owns?: (ctx: HookCtx) => boolean;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** ROVECODE_REFLECTION=0 disables the built-in set; anything else (incl. unset) enables it. */
|
|
83
|
+
export function reflectionEnabled(env: Record<string, string | undefined> = process.env): boolean {
|
|
84
|
+
return env["ROVECODE_REFLECTION"] !== "0";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** ROVECODE_REFLECTION_MAX: a non-negative integer (0 = attached but silent); blank/invalid → default. */
|
|
88
|
+
export function reflectionMax(env: Record<string, string | undefined> = process.env): number {
|
|
89
|
+
const raw = (env["ROVECODE_REFLECTION_MAX"] ?? "").trim();
|
|
90
|
+
if (raw === "") return DEFAULT_REFLECTION_MAX;
|
|
91
|
+
const v = Number(raw);
|
|
92
|
+
return Number.isFinite(v) && v >= 0 ? Math.floor(v) : DEFAULT_REFLECTION_MAX;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface RunState { nudges: number; lastKey: string | null }
|
|
96
|
+
|
|
97
|
+
export function createReflectionHooks(opts: ReflectionOptions): HookSet {
|
|
98
|
+
const max = opts.max ?? reflectionMax();
|
|
99
|
+
const owns = opts.owns ?? ((): boolean => true);
|
|
100
|
+
const watched = new Set(opts.tools ?? DEFAULT_REFLECTION_TOOLS);
|
|
101
|
+
const runs = new Map<string, RunState>(); // runId → state; bounded (bare dispatch has no runId → "")
|
|
102
|
+
const pending = new Set<string>(); // nudges pushed this run that the loop may not have drained yet
|
|
103
|
+
const keyOf = (ctx: HookCtx): string => ctx.runId ?? "";
|
|
104
|
+
const fresh = (): RunState => ({ nudges: 0, lastKey: null });
|
|
105
|
+
const stateFor = (ctx: HookCtx): RunState => {
|
|
106
|
+
const k = keyOf(ctx);
|
|
107
|
+
let s = runs.get(k);
|
|
108
|
+
if (s === undefined) {
|
|
109
|
+
s = fresh();
|
|
110
|
+
runs.set(k, s);
|
|
111
|
+
if (runs.size > MAX_TRACKED_RUNS) runs.delete(runs.keys().next().value!); // oldest first
|
|
112
|
+
}
|
|
113
|
+
return s;
|
|
114
|
+
};
|
|
115
|
+
/** run boundary: drop OUR undrained nudges, keep everything else in queue order */
|
|
116
|
+
const sweep = (): void => {
|
|
117
|
+
if (pending.size === 0) return;
|
|
118
|
+
for (const s of opts.steering.drainAll()) if (!pending.has(s)) opts.steering.push(s);
|
|
119
|
+
pending.clear();
|
|
120
|
+
};
|
|
121
|
+
return {
|
|
122
|
+
pre_run(ctx) { if (!owns(ctx)) return; sweep(); runs.set(keyOf(ctx), fresh()); },
|
|
123
|
+
post_run(ctx) { if (!owns(ctx)) return; sweep(); runs.delete(keyOf(ctx)); },
|
|
124
|
+
post_tool(ctx, call, result) {
|
|
125
|
+
if (!owns(ctx) || !watched.has(call.tool)) return;
|
|
126
|
+
const st = stateFor(ctx);
|
|
127
|
+
const core = result.output.replace(GUARD_NOTE, "");
|
|
128
|
+
const diag = result.ok ? diagnosticsOf(core) : null;
|
|
129
|
+
if (result.ok && diag === null) { st.lastKey = null; return; } // clean success: the streak ends
|
|
130
|
+
if (!result.ok && isAborted(core)) return;
|
|
131
|
+
const key = `${call.tool}\n${diag ?? core}`;
|
|
132
|
+
if (st.lastKey === key) return; // identical consecutive failure: the loop guard's job
|
|
133
|
+
st.lastKey = key;
|
|
134
|
+
if (st.nudges >= max) return;
|
|
135
|
+
st.nudges += 1;
|
|
136
|
+
const text = diag !== null ? diagnosticsNudge(call.tool, diag) : failureNudge(call.tool, core);
|
|
137
|
+
opts.steering.push(text);
|
|
138
|
+
pending.add(text);
|
|
139
|
+
opts.onNudge?.(text);
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** The bounded diagnostics list inside an LSP-gated tool output, or null when the gate note is
|
|
145
|
+
* absent (clean output, or a gate that saw warnings only — formatGateNote emits nothing then). */
|
|
146
|
+
export function diagnosticsOf(output: string): string | null {
|
|
147
|
+
const m = LSP_NOTE.exec(output);
|
|
148
|
+
if (m === null) return null;
|
|
149
|
+
const lines = m[1]!.split("\n").filter((l) => l.trim().length > 0);
|
|
150
|
+
const shown = lines.slice(0, MAX_DIAG_LINES).join("; ") + (lines.length > MAX_DIAG_LINES ? ` … and ${lines.length - MAX_DIAG_LINES} more` : "");
|
|
151
|
+
return clip(shown, REFLECTION_DIAG_CHARS);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function isAborted(output: string): boolean { return output.startsWith(ABORTED_TOOL_RESULT); }
|
|
155
|
+
|
|
156
|
+
function failureNudge(tool: string, error: string): string {
|
|
157
|
+
const excerpt = clip(error.trim(), REFLECTION_ERROR_CHARS).replace(/[.\s]+$/, "");
|
|
158
|
+
return `${REFLECTION_PREFIX}the ${tool} call failed — ${excerpt}. ${REMEDY}`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function diagnosticsNudge(tool: string, diag: string): string {
|
|
162
|
+
return `${REFLECTION_PREFIX}the ${tool} introduced diagnostics — ${diag.replace(/[.\s]+$/, "")}. Fix them or explain.`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function clip(s: string, max: number): string { return s.length <= max ? s : s.slice(0, Math.max(0, max - 1)) + "…"; }
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/** Sandbox rung configuration (PORT #27): makes the port-#10 executor ladder
|
|
2
|
+
* SELECTABLE in production — closes the #10 G7 residual ("no production
|
|
3
|
+
* selector; getExecutor() lazily returns direct").
|
|
4
|
+
*
|
|
5
|
+
* Pattern source: openai/codex, Apache-2.0 (snapshot research/source_snapshots/
|
|
6
|
+
* openai-codex @ 379d50be), ported at pattern level, no code copied:
|
|
7
|
+
* - the tier is a CONFIG CHOICE layered as file < override: config.toml
|
|
8
|
+
* `sandbox_mode = "…"` (app-server/tests/common/config.rs:140) with the
|
|
9
|
+
* `--sandbox` flag mapped onto the same SandboxMode enum
|
|
10
|
+
* (utils/cli/src/shared_options.rs:40-41, utils/cli/src/sandbox_mode_cli_arg.rs:14-25).
|
|
11
|
+
* Here: `.rovecode/sandbox.json` < `ROVECODE_SANDBOX` env (rovecode has no per-run CLI
|
|
12
|
+
* flag for this; env is the override layer, as for every other ROVECODE_* knob);
|
|
13
|
+
* - an explicitly requested but unprovidable tier is a HARD ERROR
|
|
14
|
+
* (sandboxing/src/manager.rs:203-222) — the executor seam already refuses
|
|
15
|
+
* to fall DOWN the ladder (RungUnavailableError); this module turns that
|
|
16
|
+
* refusal into a ONE-LINE startup error every entrypoint can print.
|
|
17
|
+
*
|
|
18
|
+
* Deliberate deviation from the modes.json idiom (src/core/modes.ts
|
|
19
|
+
* loadModesConfig "config can never crash startup"): a malformed sandbox file
|
|
20
|
+
* or an unknown rung is an ERROR, not a default. Degrading a misspelt "wsl"
|
|
21
|
+
* to `direct` would be exactly the silent fallback down the ladder that #10
|
|
22
|
+
* forbids — the user asked for isolation and would silently not get it.
|
|
23
|
+
*
|
|
24
|
+
* Rungs are `direct` | `wsl` | `docker` only. bubblewrap/seatbelt are not
|
|
25
|
+
* rungs on this harness (Windows-first; codex's bwrap tier has no analogue).
|
|
26
|
+
*
|
|
27
|
+
* Scope: consumed by createRuntime (src/cli/runtime.ts) — the ONE runtime
|
|
28
|
+
* construction behind tui/run/repl/acp/serve. The gauntlet runner
|
|
29
|
+
* (src/eval/gauntlet-runner.ts) registers tools directly and never builds a
|
|
30
|
+
* runtime, so `rovecode gauntlet` stays on the lazy `direct` seam regardless of
|
|
31
|
+
* the cwd's sandbox config (by design: the eval suite measures the loop, not
|
|
32
|
+
* the machine's WSL/Docker state). The executor seam is process-wide (#10):
|
|
33
|
+
* in a multi-session process (serve/acp) the most recently booted session's
|
|
34
|
+
* rung is the one every bash call uses. */
|
|
35
|
+
|
|
36
|
+
import { readFileSync } from "node:fs";
|
|
37
|
+
import { join } from "node:path";
|
|
38
|
+
import { DEFAULT_DOCKER_IMAGE, RUNGS, RungUnavailableError, type Rung } from "./executor.ts";
|
|
39
|
+
|
|
40
|
+
export type SandboxSource = "default" | "file" | "env";
|
|
41
|
+
|
|
42
|
+
export interface SandboxConfig {
|
|
43
|
+
rung: Rung;
|
|
44
|
+
/** docker rung image (must contain bash); resolved to DEFAULT_DOCKER_IMAGE for
|
|
45
|
+
* the docker rung when nothing configures it; a file-provided image is kept
|
|
46
|
+
* verbatim for other rungs (informational) */
|
|
47
|
+
dockerImage?: string;
|
|
48
|
+
/** where `rung` came from — env beats file beats the direct default */
|
|
49
|
+
source: SandboxSource;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const SANDBOX_FILE = ".rovecode/sandbox.json";
|
|
53
|
+
export const SANDBOX_ENV = "ROVECODE_SANDBOX";
|
|
54
|
+
export const SANDBOX_IMAGE_ENV = "ROVECODE_SANDBOX_IMAGE";
|
|
55
|
+
|
|
56
|
+
const HINT = `fix ${SANDBOX_FILE} or ${SANDBOX_ENV} (default: direct)`;
|
|
57
|
+
|
|
58
|
+
/** Collapse to ONE line: startup errors are printed verbatim by entrypoints. */
|
|
59
|
+
function oneLine(s: string): string {
|
|
60
|
+
return s.replace(/\s*[\r\n]+\s*/g, " ").trim();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The ONE startup-error class for sandbox selection: bad config (unknown rung,
|
|
64
|
+
* malformed file, bad image) AND a configured rung the machine cannot provide.
|
|
65
|
+
* `.message` is always a single actionable line — never a stack. */
|
|
66
|
+
export class SandboxConfigError extends Error {
|
|
67
|
+
constructor(message: string, options?: { cause?: unknown }) {
|
|
68
|
+
super(oneLine(message), options);
|
|
69
|
+
this.name = "SandboxConfigError";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function isRung(v: string): v is Rung {
|
|
74
|
+
return (RUNGS as readonly string[]).includes(v);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Rung names are trimmed + lower-cased ("WSL" is not a typo worth an error). */
|
|
78
|
+
function parseRung(raw: unknown, where: string): Rung {
|
|
79
|
+
const v = typeof raw === "string" ? raw.trim().toLowerCase() : "";
|
|
80
|
+
if (isRung(v)) return v;
|
|
81
|
+
const shown = typeof raw === "string" ? raw : JSON.stringify(raw);
|
|
82
|
+
return fail(`sandbox rung ${JSON.stringify(String(shown).slice(0, 60))} from ${where} is not a rung — use one of ${RUNGS.join(", ")} (bubblewrap is not a rung on this harness); ${HINT}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function parseImage(raw: unknown, where: string): string {
|
|
86
|
+
if (typeof raw === "string" && raw.trim() !== "") return raw.trim();
|
|
87
|
+
return fail(`sandbox dockerImage from ${where} must be a non-empty string (e.g. "${DEFAULT_DOCKER_IMAGE}"); ${HINT}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function fail(message: string): never {
|
|
91
|
+
throw new SandboxConfigError(message);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Raw file fields; values are validated only where SELECTED (an env override
|
|
95
|
+
* makes the file's rung irrelevant), but a file that cannot be parsed at all
|
|
96
|
+
* is always an error — intent is unreadable, and "fix or delete" is one step. */
|
|
97
|
+
interface FileFields { rung?: unknown; dockerImage?: unknown; path: string }
|
|
98
|
+
|
|
99
|
+
function readSandboxFile(cwd: string): FileFields | null {
|
|
100
|
+
const path = join(cwd, ".rovecode", "sandbox.json");
|
|
101
|
+
let text: string;
|
|
102
|
+
try {
|
|
103
|
+
text = readFileSync(path, "utf8");
|
|
104
|
+
} catch (e) {
|
|
105
|
+
if ((e as NodeJS.ErrnoException).code === "ENOENT") return null; // no file = default rung
|
|
106
|
+
return fail(`sandbox config ${path} cannot be read (${e instanceof Error ? e.message : String(e)}) — ${HINT}`);
|
|
107
|
+
}
|
|
108
|
+
let raw: unknown;
|
|
109
|
+
try {
|
|
110
|
+
raw = JSON.parse(text);
|
|
111
|
+
} catch (e) {
|
|
112
|
+
return fail(`sandbox config ${path} is not valid JSON (${e instanceof Error ? e.message : String(e)}) — fix or delete it; ${HINT}`);
|
|
113
|
+
}
|
|
114
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
115
|
+
return fail(`sandbox config ${path} must be a JSON object like {"rung":"wsl"} or {"rung":"docker","dockerImage":"${DEFAULT_DOCKER_IMAGE}"}; ${HINT}`);
|
|
116
|
+
}
|
|
117
|
+
const o = raw as Record<string, unknown>;
|
|
118
|
+
return { rung: o.rung, dockerImage: o.dockerImage, path };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Resolve the sandbox rung for `cwd`: `ROVECODE_SANDBOX` (rung name) beats
|
|
122
|
+
* `<cwd>/.rovecode/sandbox.json` `{ rung, dockerImage }` beats the `direct`
|
|
123
|
+
* default; `ROVECODE_SANDBOX_IMAGE` beats the file's dockerImage. Throws
|
|
124
|
+
* SandboxConfigError (one line) on an unknown rung, a malformed file, or a
|
|
125
|
+
* bad dockerImage — never degrades silently. */
|
|
126
|
+
export function loadSandboxConfig(cwd: string, env: Record<string, string | undefined> = process.env): SandboxConfig {
|
|
127
|
+
const file = readSandboxFile(cwd);
|
|
128
|
+
const envRung = env[SANDBOX_ENV]?.trim() ?? "";
|
|
129
|
+
const envImage = env[SANDBOX_IMAGE_ENV]?.trim() ?? "";
|
|
130
|
+
let rung: Rung = "direct";
|
|
131
|
+
let source: SandboxSource = "default";
|
|
132
|
+
if (envRung !== "") {
|
|
133
|
+
rung = parseRung(envRung, SANDBOX_ENV);
|
|
134
|
+
source = "env";
|
|
135
|
+
} else if (file && file.rung !== undefined) {
|
|
136
|
+
rung = parseRung(file.rung, file.path);
|
|
137
|
+
source = "file";
|
|
138
|
+
}
|
|
139
|
+
let dockerImage: string | undefined;
|
|
140
|
+
if (envImage !== "") dockerImage = envImage;
|
|
141
|
+
else if (file && file.dockerImage !== undefined) dockerImage = parseImage(file.dockerImage, file.path);
|
|
142
|
+
else if (rung === "docker") dockerImage = DEFAULT_DOCKER_IMAGE;
|
|
143
|
+
return dockerImage !== undefined ? { rung, dockerImage, source } : { rung, source };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Wrap a failed configureExecutor(cfg.rung) into the startup error class:
|
|
147
|
+
* the probe detail (why the wrapper could not run `bash -c true`) plus the
|
|
148
|
+
* fix, on one line. The original error rides along as `cause`. */
|
|
149
|
+
export function unavailableRungError(cfg: SandboxConfig, cause: unknown): SandboxConfigError {
|
|
150
|
+
const detail = cause instanceof RungUnavailableError ? cause.detail
|
|
151
|
+
: cause instanceof Error ? cause.message : String(cause);
|
|
152
|
+
return new SandboxConfigError(
|
|
153
|
+
`sandbox rung "${cfg.rung}" (from ${originOf(cfg)}) unavailable: ${detail} — ${HINT}`,
|
|
154
|
+
{ cause },
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function originOf(cfg: SandboxConfig): string {
|
|
159
|
+
return cfg.source === "env" ? SANDBOX_ENV : cfg.source === "file" ? SANDBOX_FILE : "default";
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** One-line human form for /status: `direct (default)`, `wsl (.rovecode/sandbox.json)`,
|
|
163
|
+
* `docker debian:stable-slim (ROVECODE_SANDBOX)`. */
|
|
164
|
+
export function describeSandbox(cfg: SandboxConfig): string {
|
|
165
|
+
const image = cfg.rung === "docker" ? ` ${cfg.dockerImage ?? DEFAULT_DOCKER_IMAGE}` : "";
|
|
166
|
+
return `${cfg.rung}${image} (${originOf(cfg)})`;
|
|
167
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** Port #34 image sidecars for the session store (session.ts): the persisted form ↔ the in-memory
|
|
2
|
+
* form of a message's image parts.
|
|
3
|
+
* Sidecars live at `<session>/attachments/<sha256>.<ext>`, content-addressed (the same image
|
|
4
|
+
* attached twice is one file). The store references them from entries.jsonl by ONE canonical
|
|
5
|
+
* session-relative form, `attachments/<file>` (forward slash, one segment, not `.`/`..`), and
|
|
6
|
+
* resolves that form to an absolute path in memory only, so core/images.ts imageData() can read
|
|
7
|
+
* it without knowing the session.
|
|
8
|
+
* Path confinement: every other persisted path is foreign and never resolves to a readable file —
|
|
9
|
+
* a non-canonical relative path stays relative (F2: `../outside.png` used to hydrate to
|
|
10
|
+
* <sessions>/outside.png) and an absolute path is DROPPED (F3: the store never writes one, so a
|
|
11
|
+
* persisted absolute path is a tampered or foreign line that would otherwise ship arbitrary
|
|
12
|
+
* on-disk bytes to the provider as an image block). imageData()/imageByteSize() decline both, so
|
|
13
|
+
* the part lowers to a "file unavailable" placeholder. Consequently the only file a hydrated part
|
|
14
|
+
* can name is `<session>/attachments/<one segment>`, whatever entries.jsonl says.
|
|
15
|
+
* `rovecode export --json` copies entries.jsonl ALONE — the attachments directory travels with the
|
|
16
|
+
* session directory, not with the export (the JSONL stays a small, verbatim-copyable record). */
|
|
17
|
+
|
|
18
|
+
import { createHash } from "node:crypto";
|
|
19
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
20
|
+
import { isAbsolute, join } from "node:path";
|
|
21
|
+
import { imageExt } from "./images.ts";
|
|
22
|
+
import type { ImagePart, MessagePart } from "./types.ts";
|
|
23
|
+
|
|
24
|
+
export const ATTACHMENTS_DIR = "attachments";
|
|
25
|
+
|
|
26
|
+
/** F2 (port #34 hardening): the only persisted sidecar form is `attachments/<file>` — one segment,
|
|
27
|
+
* no separators, not `.`/`..`. Anything else is undefined here and left unresolved (hydrateImageParts). */
|
|
28
|
+
export function sidecarFile(persisted: string): string | undefined {
|
|
29
|
+
const prefix = `${ATTACHMENTS_DIR}/`;
|
|
30
|
+
if (!persisted.startsWith(prefix)) return undefined;
|
|
31
|
+
const file = persisted.slice(prefix.length);
|
|
32
|
+
return file !== "" && file !== "." && file !== ".." && !/[\\/]/.test(file) ? file : undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The on-disk form: every inline image (`bytes`) becomes a sidecar file under `dir` and the part
|
|
36
|
+
* keeps the session-relative `path` instead — the hash chain covers the path, the content-addressed
|
|
37
|
+
* filename covers the bytes. A sidecar that cannot be written (read-only dir, disk full) keeps its
|
|
38
|
+
* bytes inline, so nothing is ever dropped. undefined when there is nothing to do. */
|
|
39
|
+
export function sidecarImageParts(dir: string, parts: readonly MessagePart[]): MessagePart[] | undefined {
|
|
40
|
+
if (!parts.some((p) => p.kind === "image" && p.bytes !== undefined)) return undefined;
|
|
41
|
+
return parts.map((p) => {
|
|
42
|
+
if (p.kind !== "image" || p.bytes === undefined) return p;
|
|
43
|
+
const buf = Buffer.from(p.bytes, "base64");
|
|
44
|
+
const file = `${createHash("sha256").update(buf).digest("hex")}.${imageExt(p.mime)}`;
|
|
45
|
+
try {
|
|
46
|
+
mkdirSync(join(dir, ATTACHMENTS_DIR), { recursive: true });
|
|
47
|
+
const abs = join(dir, ATTACHMENTS_DIR, file);
|
|
48
|
+
if (!existsSync(abs)) writeFileSync(abs, buf);
|
|
49
|
+
} catch {
|
|
50
|
+
return p; // inline fallback
|
|
51
|
+
}
|
|
52
|
+
const out: ImagePart = { kind: "image", mime: p.mime, path: `${ATTACHMENTS_DIR}/${file}` };
|
|
53
|
+
if (p.width !== undefined) out.width = p.width;
|
|
54
|
+
if (p.height !== undefined) out.height = p.height;
|
|
55
|
+
if (p.name !== undefined) out.name = p.name;
|
|
56
|
+
return out;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The in-memory form: the canonical `attachments/<file>` path → absolute under `dir`; a persisted
|
|
61
|
+
* ABSOLUTE path is dropped (F3, header) so the part is unreadable rather than a read of whatever
|
|
62
|
+
* file the line names; any other relative path stays as it is (F2). undefined when there is
|
|
63
|
+
* nothing to do. */
|
|
64
|
+
export function hydrateImageParts(dir: string, parts: readonly MessagePart[]): MessagePart[] | undefined {
|
|
65
|
+
const touched = (p: MessagePart): boolean => p.kind === "image" && p.path !== undefined && (isAbsolute(p.path) || sidecarFile(p.path) !== undefined);
|
|
66
|
+
if (!parts.some(touched)) return undefined;
|
|
67
|
+
return parts.map((p) => {
|
|
68
|
+
if (p.kind !== "image" || p.path === undefined) return p;
|
|
69
|
+
if (isAbsolute(p.path)) { const rest = { ...p }; delete rest.path; return rest; }
|
|
70
|
+
const file = sidecarFile(p.path);
|
|
71
|
+
return file === undefined ? p : { ...p, path: join(dir, ATTACHMENTS_DIR, file) };
|
|
72
|
+
});
|
|
73
|
+
}
|