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
package/src/tui/app.ts
ADDED
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
/** TUI chat app (port #1): wires the ONE agentLoop (ADR-003) into a Renderer.
|
|
2
|
+
* All vendor contact lives behind Renderer (renderer.ts) — swap-friendly. Slash handlers live
|
|
3
|
+
* beside it (ADR-002 cap): info-cmd.ts (/help /status /cost /skills /memory /export /todos
|
|
4
|
+
* /tasks), session-cmd.ts (/new /rewind /sessions /resume), checkpoints-cmd.ts, modes-cmd.ts. */
|
|
5
|
+
|
|
6
|
+
import { agentLoop, outstandingClause, outstandingTone } from "../core/loop.ts";
|
|
7
|
+
import { resetTurnFailureCount } from "../memory/tools.ts";
|
|
8
|
+
import { createRuntime } from "../cli/runtime.ts";
|
|
9
|
+
import { SandboxConfigError } from "../core/sandbox-config.ts";
|
|
10
|
+
import type { SpawnRunner } from "../core/executor.ts";
|
|
11
|
+
import { SessionStore, listSessions } from "../core/session.ts";
|
|
12
|
+
import { BlockStore } from "../memory/blocks.ts";
|
|
13
|
+
import { ModelCatalog } from "../providers/catalog.ts";
|
|
14
|
+
import { ModeManager, loadModesConfig, modeFromEntries, type AgentMode } from "../core/modes.ts";
|
|
15
|
+
import { isTerminal, taskNote } from "../core/tasks.ts";
|
|
16
|
+
import { togglePlanAct, applyModeToRun, flushModeSwitch } from "./modes-cmd.ts";
|
|
17
|
+
import { cmdCheckpoints, cmdRestore, type CheckpointCmdCtx } from "./checkpoints-cmd.ts";
|
|
18
|
+
import type { SessionCmdCtx } from "./session-cmd.ts";
|
|
19
|
+
import type { InfoCmdCtx } from "./info-cmd.ts";
|
|
20
|
+
import { todoLabel } from "./todo-label.ts";
|
|
21
|
+
import { cmdAttach, cmdPasteImage, carryOverAttachments, queuedAttachNote, userTurnLine, ATTACH_COMMAND, PASTE_COMMAND, type AttachCtx } from "./attach.ts";
|
|
22
|
+
import { cmdConnect, cmdModel as cmdModelSwitch, cmdModels, cmdProvider, cmdSetup, listModelIds, watchProviders, CONNECT_COMMAND, MODEL_COMMAND, PROVIDER_COMMANDS, SETUP_COMMAND, type ProviderCmdCtx } from "./providers-cmd.ts";
|
|
23
|
+
import { cmdMcp, MCP_COMMAND } from "./mcp-cmd.ts";
|
|
24
|
+
import { summarizePlugins } from "../plugins/index.ts";
|
|
25
|
+
import { acceptEditsNote, effortNote, modeSwitchNote, noModelHint, resumedLine, welcomeCard } from "../core/voice.ts";
|
|
26
|
+
import { checkForUpdate, updateLine } from "../core/update-check.ts";
|
|
27
|
+
import pkg from "../../package.json";
|
|
28
|
+
import { compactionNote } from "./replay-marker.ts";
|
|
29
|
+
import { previewDiff } from "../coding/diff.ts";
|
|
30
|
+
import { discoverCommands, commandsForPalette, dispatchCustomCommand, type CustomCommandCtx } from "./commands.ts";
|
|
31
|
+
import type { Renderer, AssistantView, SlashCommand, StatusInfo } from "./renderer.ts";
|
|
32
|
+
// pi-renderer.ts (and the vendored pi-tui under it) costs ~27 MB resident; a sextant session never
|
|
33
|
+
// constructs it, so it is required where it is constructed, not imported here (tests: pi-renderer-lazy)
|
|
34
|
+
type PiRendererMod = typeof import("./pi-renderer.ts");
|
|
35
|
+
import { buildSextantAttach, SEXTANT_LOCAL_NAMES } from "./sextant-attach.ts";
|
|
36
|
+
import type { ModelRef, PermissionLevel, RunEvent, StreamFn } from "../core/types.ts";
|
|
37
|
+
import { thinkingLine } from "../providers/thinking.ts";
|
|
38
|
+
import { anthropicShapeFor } from "../providers/stream.ts";
|
|
39
|
+
import { parseEffort, THINKING_EFFORTS } from "../core/types.ts";
|
|
40
|
+
import { resolvePermission, saveSetting } from "../core/settings.ts";
|
|
41
|
+
import type { ThinkingEffort } from "../core/types.ts";
|
|
42
|
+
import { join } from "node:path";
|
|
43
|
+
|
|
44
|
+
export { buildCostNote } from "./cost.ts"; // moved for the ADR-002 cap; re-exported for tests
|
|
45
|
+
|
|
46
|
+
// lazy loaders — info-cmd and session-cmd are deferred until the first slash command
|
|
47
|
+
type InfoCmdMod = typeof import("./info-cmd.ts");
|
|
48
|
+
let _infoCmdMod: InfoCmdMod | null = null;
|
|
49
|
+
function lazyInfoCmd(): InfoCmdMod {
|
|
50
|
+
if (_infoCmdMod === null) {
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
52
|
+
_infoCmdMod = require("./info-cmd.ts") as InfoCmdMod;
|
|
53
|
+
}
|
|
54
|
+
return _infoCmdMod;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
type SessionCmdMod = typeof import("./session-cmd.ts");
|
|
58
|
+
let _sessionCmdMod: SessionCmdMod | null = null;
|
|
59
|
+
function lazySessionCmd(): SessionCmdMod {
|
|
60
|
+
if (_sessionCmdMod === null) {
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
62
|
+
_sessionCmdMod = require("./session-cmd.ts") as SessionCmdMod;
|
|
63
|
+
}
|
|
64
|
+
return _sessionCmdMod;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** resolveBootSession inlined from session-cmd.ts so the module loads lazily.
|
|
68
|
+
* `rovecode --resume <id>` boot resolution: exact/new ids pass, unique prefix resolves,
|
|
69
|
+
* ambiguous prefix starts fresh and warns. */
|
|
70
|
+
function resolveBootSession(sessionsDir: string, id: string | undefined): { id: string | undefined; warn?: string } {
|
|
71
|
+
if (id === undefined) return { id };
|
|
72
|
+
const known = listSessions(sessionsDir);
|
|
73
|
+
if (known.some((s) => s.id === id)) return { id };
|
|
74
|
+
const pre = known.filter((s) => s.id.startsWith(id));
|
|
75
|
+
if (pre.length === 1) return { id: pre[0]!.id };
|
|
76
|
+
if (pre.length > 1) return { id: undefined, warn: `"${id}" matches ${pre.length} sessions — started fresh; use /resume to pick one` };
|
|
77
|
+
return { id };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface TuiAppOptions {
|
|
81
|
+
yolo?: boolean;
|
|
82
|
+
model?: string;
|
|
83
|
+
cwd?: string;
|
|
84
|
+
/** resume an existing session id instead of starting a fresh one */
|
|
85
|
+
sessionId?: string;
|
|
86
|
+
/** injected by tests/smoke (VirtualTerminal-backed renderer, mock stream) */
|
|
87
|
+
renderer?: Renderer;
|
|
88
|
+
stream?: StreamFn | null;
|
|
89
|
+
/** default true: process.exit(0) when the user quits */
|
|
90
|
+
exitOnClose?: boolean;
|
|
91
|
+
/** port #27 test seams, threaded into createRuntime: the process runner behind the rung
|
|
92
|
+
* probe (never a real wsl.exe/docker in tests) and the platform the probe assumes */
|
|
93
|
+
spawnRunner?: SpawnRunner;
|
|
94
|
+
platform?: NodeJS.Platform;
|
|
95
|
+
/** port #44: the sextant pet's name (`--pet <name>`); the classic renderer ignores it */
|
|
96
|
+
pet?: string;
|
|
97
|
+
/** start in the middle permission tier (`--accept-edits`, ROVECODE_ACCEPT_EDITS=1) */
|
|
98
|
+
acceptEdits?: boolean;
|
|
99
|
+
/** the permission level as an explicit ASK from an in-process caller — the same rung as a CLI flag, so it
|
|
100
|
+
* beats ROVECODE_PERMISSION and both settings files. `yolo`/`acceptEdits` can only demand a WIDER
|
|
101
|
+
* level; `yolo: false` is "no flag", and a user settings file saying "auto" then wins. A smoke whose
|
|
102
|
+
* assertion is "an approval card appears" needs this to say "ask" and mean it. Leaving it undefined
|
|
103
|
+
* changes nothing: the env var and the files keep their say. */
|
|
104
|
+
permission?: PermissionLevel;
|
|
105
|
+
/** `--effort <level>`; overrides ROVECODE_EFFORT for this session */
|
|
106
|
+
effort?: ThinkingEffort;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The built-in slash commands, worded in rovecode's voice (core/voice.ts) and tagged with the /help topic
|
|
110
|
+
* they are listed under (info-cmd.ts cmdHelp groups by `group`; the palette shows name + description). */
|
|
111
|
+
export const TUI_COMMANDS: SlashCommand[] = [
|
|
112
|
+
{ name: "help", description: "This list, by topic", group: "start here" },
|
|
113
|
+
CONNECT_COMMAND, // providers-cmd.ts: /connect — bare it is /setup; with an id it takes the answers on the line
|
|
114
|
+
SETUP_COMMAND, // providers-cmd.ts: /setup — pick a provider, name the model, hand over the key, one test call
|
|
115
|
+
{ name: "exit", description: "Quit (alias /quit; Ctrl+C does the same)", group: "start here" },
|
|
116
|
+
{ name: "new", description: "Start over in this session (branch back to the beginning)", group: "session" },
|
|
117
|
+
{ name: "sessions", description: "Pick an earlier session to continue", group: "session" },
|
|
118
|
+
{ name: "resume", description: "Continue a session by id: /resume <id>", group: "session" },
|
|
119
|
+
{ name: "rewind", description: "Go back to an earlier turn and edit it (alias: /tree)", group: "session" },
|
|
120
|
+
{ name: "tree", description: "Alias of /rewind", group: "session" },
|
|
121
|
+
{ name: "export", description: "Save this session as markdown: /export [--json] [path] [--force]", group: "session" },
|
|
122
|
+
MODEL_COMMAND, // providers-cmd.ts: /model <provider/model | model> [--save]
|
|
123
|
+
...PROVIDER_COMMANDS, // /models · /provider — providers-cmd.ts (live registry: no restart after add/key/use)
|
|
124
|
+
{ name: "yolo", description: "Toggle ask first / auto (never asks)", group: "modes & safety" },
|
|
125
|
+
{ name: "accept-edits", description: "Stop asking for writes inside this folder; shell, subagents and writes outside it still ask", group: "modes & safety" },
|
|
126
|
+
{ name: "effort", description: "How hard I think before answering: /effort auto | off | low | medium | high — the note says what the current model actually receives", group: "model & provider", choices: THINKING_EFFORTS },
|
|
127
|
+
{ name: "plan", description: "Plan mode: I only read and plan, nothing changes", group: "modes & safety" },
|
|
128
|
+
{ name: "act", description: "Act mode: I can edit and run again", group: "modes & safety" },
|
|
129
|
+
{ name: "checkpoints", description: "Snapshots I took before each change (shadow git)", group: "files & history" },
|
|
130
|
+
{ name: "restore", description: "Go back to a snapshot: /restore <ref> [files|conversation|both]", group: "files & history" },
|
|
131
|
+
{ ...ATTACH_COMMAND, group: "files & history" }, // port #34: /attach <path> · /attach (list) · /attach clear — attach.ts
|
|
132
|
+
{ ...PASTE_COMMAND, group: "files & history" }, // /paste: the clipboard image as an attachment (⌃v in sextant) — attach.ts
|
|
133
|
+
MCP_COMMAND, // mcp-cmd.ts: /mcp [query] — pick a server in the palette, approve the exact plan on the card, it lands in mcp.json
|
|
134
|
+
{ name: "status", description: "Provider, model, turns, tokens, sandbox", group: "info" },
|
|
135
|
+
{ name: "cost", description: "Tokens, cache hits and the USD estimate (/cost refresh updates prices)", group: "info" },
|
|
136
|
+
{ name: "todos", description: "My step list for the current task", group: "info" },
|
|
137
|
+
{ name: "tasks", description: "Background subagents: /tasks [cancel <id>|cancel all]", group: "info" },
|
|
138
|
+
{ name: "skills", description: "Installed skills", group: "info" },
|
|
139
|
+
{ name: "memory", description: "What I remember across turns (memory blocks)", group: "info" },
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
interface TuiState {
|
|
143
|
+
yolo: boolean;
|
|
144
|
+
/** the middle tier (port: Claude Code's acceptEdits): writes inside the workspace stop asking,
|
|
145
|
+
* shell/spawn/network and writes outside it still do. Ignored while `yolo` is on — auto already
|
|
146
|
+
* covers everything. Turned on by `/accept-edits`, `--accept-edits`, or the `all edits` button on
|
|
147
|
+
* a write approval card. */
|
|
148
|
+
acceptEdits: boolean;
|
|
149
|
+
provider: string; model: string; mode: AgentMode;
|
|
150
|
+
turns: number; tokensIn: number; tokensOut: number;
|
|
151
|
+
busy: boolean;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function runTui(opts: TuiAppOptions = {}): Promise<void> {
|
|
155
|
+
const _t0 = process.env.ROVECODE_TRACE_BOOT === "1" ? (Number(process.env._ROVECODE_BOOT_T0) || Date.now()) : -1;
|
|
156
|
+
const _trace = _t0 >= 0 ? (label: string) => process.stderr.write(`[boot] +${Date.now() - _t0}ms ${label}\n`) : (_: string) => {};
|
|
157
|
+
_trace("runTui entered");
|
|
158
|
+
// opts.sessionId may be a unique id prefix (rovecode --resume <id>): resolved by the /resume rule
|
|
159
|
+
// (session-cmd.ts) — exact/new ids pass, a unique prefix resolves, an ambiguous one starts fresh + warns
|
|
160
|
+
const boot = resolveBootSession(join(opts.cwd ?? process.cwd(), ".rovecode", "sessions"), opts.sessionId);
|
|
161
|
+
// opts.stream passes through verbatim: a StreamFn overrides, explicit null forces
|
|
162
|
+
// "no provider", undefined defers to the runtime's env-resolved provider
|
|
163
|
+
// port #27: a sandbox MISCONFIG throws synchronously here (before any side effect) — a clean
|
|
164
|
+
// one-line startup error (exit 2), never a stack, never a silent direct fallback. The rung
|
|
165
|
+
// PROBE verdict (rt.sandbox.ready) is awaited at the end of boot: runTui must stay
|
|
166
|
+
// synchronous until the renderer's input handlers are wired (tests/smoke send input right
|
|
167
|
+
// after calling runTui), so no await may sit above that point.
|
|
168
|
+
const rt = (() => {
|
|
169
|
+
try {
|
|
170
|
+
_trace("createRuntime start");
|
|
171
|
+
const r = createRuntime({ cwd: opts.cwd, stream: opts.stream, sessionId: boot.id, spawnRunner: opts.spawnRunner, platform: opts.platform });
|
|
172
|
+
_trace("createRuntime done");
|
|
173
|
+
return r;
|
|
174
|
+
}
|
|
175
|
+
catch (e) {
|
|
176
|
+
if (e instanceof SandboxConfigError && opts.exitOnClose !== false) { console.error(`error: ${e.message}`); process.exit(2); }
|
|
177
|
+
throw e;
|
|
178
|
+
}
|
|
179
|
+
})();
|
|
180
|
+
const renderer: Renderer = opts.renderer ?? new (require("./pi-renderer.ts") as PiRendererMod).PiTuiRenderer({ cwd: rt.cwd }); // eslint-disable-line @typescript-eslint/no-require-imports
|
|
181
|
+
_trace("renderer created");
|
|
182
|
+
rt.setAskUser((q, signal) => renderer.askQuestion(q, signal)); // port #33: ask_user → the question overlay (Esc/abort dismisses it via signal)
|
|
183
|
+
const sessionsDir = join(rt.cwd, ".rovecode", "sessions");
|
|
184
|
+
// /cost pricing + context window. Boots from the offline snapshot; the live models.dev
|
|
185
|
+
// half is user-invoked only (/cost refresh), cached to .rovecode/cache with a 24h TTL —
|
|
186
|
+
// lookup() itself never fetches, so the TUI stays network-free unless asked.
|
|
187
|
+
const catalog = new ModelCatalog({ fetchFn: fetch, cacheDir: join(rt.cwd, ".rovecode", "cache") });
|
|
188
|
+
// session-scoped stores are swappable at runtime (/sessions, /rewind-to-root)
|
|
189
|
+
let store = rt.store;
|
|
190
|
+
let blocks = rt.blockStore;
|
|
191
|
+
// port #26: the runtime's ONE steering queue — background-task completion notes land on the
|
|
192
|
+
// next model turn; settled tasks also show in the transcript as they happen (failed → warn)
|
|
193
|
+
const steering = rt.steering;
|
|
194
|
+
rt.tasks.subscribe((t) => { if (isTerminal(t.status)) renderer.addSystemNote(taskNote(t), t.status === "failed" ? "warn" : "info"); });
|
|
195
|
+
// port #20: per-mode model slots from .rovecode/modes.json, restored from session entries
|
|
196
|
+
const modesCfg = loadModesConfig(rt.cwd);
|
|
197
|
+
const modes = new ModeManager(modesCfg, {
|
|
198
|
+
provider: rt.provider?.id ?? "mock",
|
|
199
|
+
model: opts.model ?? process.env.ROVECODE_MODEL ?? rt.defaultModel ?? "",
|
|
200
|
+
});
|
|
201
|
+
modes.restore(modeFromEntries(store.messages()) ?? modes.mode);
|
|
202
|
+
// port #30: custom slash commands — .rovecode/commands/*.md, project shadows ~/.rovecode/commands (commands.ts);
|
|
203
|
+
// LOW-1: /quit is a `case` alias of /exit below, not a TUI_COMMANDS entry — reserve it explicitly;
|
|
204
|
+
// port #44: the sextant surface's own /theme /open /diff /focus /agents never reach handleSlash — reserved too
|
|
205
|
+
// plugins (src/plugins): an ACTIVE plugin's commands folder is one more root, after the folder of its own
|
|
206
|
+
// scope — the same path setCommands feeds the palette and suggestions from, so nothing else changes
|
|
207
|
+
const pluginCommandDirs = rt.plugins.found.flatMap((p) => (p.status === "active" && p.commandsDir ? [{ dir: p.commandsDir, scope: p.scope }] : []));
|
|
208
|
+
const custom = discoverCommands(rt.cwd, { reserved: [...TUI_COMMANDS.map((c) => c.name), "quit", ...SEXTANT_LOCAL_NAMES], extraDirs: pluginCommandDirs });
|
|
209
|
+
if (opts.effort !== undefined) rt.setEffort(opts.effort);
|
|
210
|
+
// one resolved answer instead of two independent booleans: flag → env → project file → user file →
|
|
211
|
+
// "ask" (core/settings.ts). This is what makes `/yolo --save` survive the terminal closing.
|
|
212
|
+
// opts.permission is the flag rung for in-process callers; yolo/acceptEdits stay the boolean flags they were
|
|
213
|
+
// (true = demand, false = say nothing) — see TuiAppOptions.permission for why false cannot mean "ask"
|
|
214
|
+
const flagLevel: PermissionLevel | undefined = opts.permission ?? (opts.yolo === true ? "auto" : opts.acceptEdits === true ? "accept-edits" : undefined);
|
|
215
|
+
const startLevel = resolvePermission(rt.cwd, flagLevel, { ROVECODE_PERMISSION: process.env.ROVECODE_PERMISSION, ROVECODE_YOLO: process.env.ROVECODE_YOLO, ROVECODE_ACCEPT_EDITS: process.env.ROVECODE_ACCEPT_EDITS });
|
|
216
|
+
const state: TuiState = {
|
|
217
|
+
yolo: startLevel === "auto",
|
|
218
|
+
acceptEdits: startLevel === "accept-edits",
|
|
219
|
+
provider: modes.modelFor().provider,
|
|
220
|
+
model: modes.modelFor().model,
|
|
221
|
+
mode: modes.mode,
|
|
222
|
+
turns: 0, tokensIn: 0, tokensOut: 0, busy: false,
|
|
223
|
+
};
|
|
224
|
+
let run: AsyncGenerator<RunEvent> | null = null; let runAbort: AbortController | null = null; // port #21: one controller per run
|
|
225
|
+
let closed = false;
|
|
226
|
+
let resolveClosed: () => void = () => {};
|
|
227
|
+
const closedP = new Promise<void>((r) => { resolveClosed = r; });
|
|
228
|
+
|
|
229
|
+
const status = (): StatusInfo => {
|
|
230
|
+
const todos = todoLabel(join(sessionsDir, store.id)); // port #32: "todos done/total"; key omitted while the list is empty
|
|
231
|
+
return {
|
|
232
|
+
provider: state.provider, model: state.model, yolo: state.yolo, mode: state.mode,
|
|
233
|
+
permission: state.yolo ? "auto" : state.acceptEdits ? "accept-edits" : "ask",
|
|
234
|
+
effort: rt.effort,
|
|
235
|
+
turns: state.turns, tokensIn: state.tokensIn, tokensOut: state.tokensOut,
|
|
236
|
+
...(todos !== undefined ? { todos } : {}),
|
|
237
|
+
};
|
|
238
|
+
};
|
|
239
|
+
const pushStatus = () => renderer.setStatus(status());
|
|
240
|
+
|
|
241
|
+
/** set once the surface owns the screen (below renderer.start); puts Node's warning printer back */
|
|
242
|
+
let restoreWarnings: (() => void) | undefined;
|
|
243
|
+
const close = () => {
|
|
244
|
+
if (closed) return;
|
|
245
|
+
closed = true;
|
|
246
|
+
// port #20 MED-2: /plan then quit resumes in plan (append is sync — lands pre-exit)
|
|
247
|
+
flushModeSwitch(modes, store);
|
|
248
|
+
runAbort?.abort(); // abort kills in-flight fetch/tools; return() settles the generator — kept, so the exit below waits for it
|
|
249
|
+
const settled = run?.return(undefined as never).then(() => undefined, () => undefined) ?? Promise.resolve();
|
|
250
|
+
rt.tasks.cancelAll(); // port #26: background children die with the surface, never after it
|
|
251
|
+
void rt.mcp?.close().catch(() => {}); // stop MCP child processes/connections
|
|
252
|
+
restoreWarnings?.(); // the screen is going away; Node's own printer is the right one again
|
|
253
|
+
renderer.stop();
|
|
254
|
+
// port #29: session_close fires ONCE, after the aborted run settled and its in-flight on_event
|
|
255
|
+
// taps drained (hooks.close() waits for those) — cmdRun's exit() order; the app promise
|
|
256
|
+
// resolves (and the process exits) only after it, so a quit never outruns the hook
|
|
257
|
+
void (async () => {
|
|
258
|
+
await settled;
|
|
259
|
+
await rt.hooks.close().catch(() => {});
|
|
260
|
+
// the sextant renderer's git children (repo watcher) must be GONE before the process exits — on
|
|
261
|
+
// Windows a live child holds its cwd, so a scratch repo removed at quit throws EBUSY (fee2e8c root
|
|
262
|
+
// cause). Optional: the Renderer seam stays untouched; FakeRenderer and pi-tui have no drain()
|
|
263
|
+
await (renderer as { drain?: () => Promise<void> }).drain?.()?.catch(() => {});
|
|
264
|
+
resolveClosed();
|
|
265
|
+
if (opts.exitOnClose !== false) process.exit(0);
|
|
266
|
+
})();
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
// both read the ACTIVE store live — /sessions and a root /rewind swap it (session-cmd.ts helpers)
|
|
270
|
+
const refreshUsage = () => { const u = lazySessionCmd().usageOf(store); state.tokensIn = u.tokensIn; state.tokensOut = u.tokensOut; };
|
|
271
|
+
const replayHistory = () => lazySessionCmd().replayTranscript(renderer, store);
|
|
272
|
+
// port #34: /attach context — the stage lives on the ACTIVE store (read live); the vision check uses the current mode's model
|
|
273
|
+
const attachCtx: AttachCtx = { renderer, cwd: rt.cwd, store: () => store, modelRef: () => modes.modelFor() };
|
|
274
|
+
|
|
275
|
+
const switchSession = (id: string, announce = true) => {
|
|
276
|
+
flushModeSwitch(modes, store); // port #20 MED-2: don't discard a pending switch on /sessions away
|
|
277
|
+
const pending = store.stagedAttachments; // port #34: the stage lives on the instance — re-staged on the new one below
|
|
278
|
+
store = new SessionStore(sessionsDir, id);
|
|
279
|
+
blocks = new BlockStore(join(sessionsDir, id, "memory"));
|
|
280
|
+
// rebind BOTH consumers: the memory tool AND the system prompt's memory block
|
|
281
|
+
// (critic finding: prompt kept reading the boot session's memory after /resume)
|
|
282
|
+
rt.setBlockStore(blocks);
|
|
283
|
+
rt.setSessionStore(store); // port #11: checkpoint entryId capture follows the active session
|
|
284
|
+
// port #20: the switched-to session resumes ITS last recorded mode
|
|
285
|
+
modes.restore(modeFromEntries(store.messages()) ?? modesCfg.defaultMode ?? "act");
|
|
286
|
+
const cur = modes.modelFor();
|
|
287
|
+
state.mode = modes.mode; state.model = cur.model; state.provider = cur.provider;
|
|
288
|
+
state.turns = 0;
|
|
289
|
+
replayHistory();
|
|
290
|
+
refreshUsage();
|
|
291
|
+
pushStatus();
|
|
292
|
+
if (announce) renderer.addSystemNote(`session ${id.slice(0, 8)} (${store.messages().length} messages)`);
|
|
293
|
+
carryOverAttachments(attachCtx, pending); // port #34: a swap must never lose staged images silently
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
// port #11: checkpoint command context (store/busy read live via closures)
|
|
297
|
+
const cpCtx: CheckpointCmdCtx = {
|
|
298
|
+
renderer,
|
|
299
|
+
busy: () => state.busy,
|
|
300
|
+
sessionId: () => store.id,
|
|
301
|
+
checkpointsFor: (sid) => rt.checkpointsFor(sid),
|
|
302
|
+
branchTo: (entryId) => store.branch(entryId),
|
|
303
|
+
replayAndRefresh: () => { replayHistory(); refreshUsage(); pushStatus(); },
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
// port #2: session navigation context (store read live via closure — /sessions and a root /rewind swap it)
|
|
307
|
+
const sessCtx: SessionCmdCtx = {
|
|
308
|
+
renderer,
|
|
309
|
+
sessionsDir,
|
|
310
|
+
busy: () => state.busy,
|
|
311
|
+
store: () => store,
|
|
312
|
+
switchSession,
|
|
313
|
+
replayHistory,
|
|
314
|
+
refreshUsage,
|
|
315
|
+
pushStatus,
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
// read-only info commands (info-cmd.ts) — store/blocks read live, the status slice is `state` itself
|
|
319
|
+
const infoCtx: InfoCmdCtx = {
|
|
320
|
+
renderer, rt, sessionsDir, catalog, state,
|
|
321
|
+
store: () => store, blocks: () => blocks,
|
|
322
|
+
commands: { builtin: TUI_COMMANDS, custom: custom.commands },
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
// port #30: custom command dispatch context (submit = the plain user-turn path, defined below)
|
|
326
|
+
const cmdCtx: CustomCommandCtx = { renderer, modes, state, pushStatus, submit: (t) => submit(t) };
|
|
327
|
+
|
|
328
|
+
// /model /models /provider (providers-cmd.ts) read the live registry; built lazily so pushStatus is bound
|
|
329
|
+
const provCtx = (): ProviderCmdCtx => ({ rt, modes, state, renderer, pushStatus });
|
|
330
|
+
/** `--save` writes the level the toggles just produced, so the next launch starts there;
|
|
331
|
+
* `--project` pins it to this checkout instead of to you. Without --save nothing is written —
|
|
332
|
+
* a toggle you meant for one run must not follow you into the next. */
|
|
333
|
+
const persistLevel = (arg: string): string => {
|
|
334
|
+
const words = arg.split(/\s+/).filter((w) => w.length > 0);
|
|
335
|
+
if (!words.includes("--save")) return "this session only — add --save to make it the default (--project pins it to this repo)";
|
|
336
|
+
const scope = words.includes("--project") ? "project" : "user";
|
|
337
|
+
const level: PermissionLevel = state.yolo ? "auto" : state.acceptEdits ? "accept-edits" : "ask";
|
|
338
|
+
try {
|
|
339
|
+
const path = saveSetting("permission", level, scope, rt.cwd);
|
|
340
|
+
return `saved: ${level} is the default now (${path})`;
|
|
341
|
+
} catch (e) {
|
|
342
|
+
return `could not save it: ${e instanceof Error ? e.message : String(e)}`;
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
/** what the CURRENT model's endpoint receives for a level — the /effort note's second line (providers/thinking.ts).
|
|
347
|
+
* Built like buildDef builds a ref (catalog reasoning flag), without touching the runtime's active model. */
|
|
348
|
+
const receives = (level: ThinkingEffort): string => {
|
|
349
|
+
const info = catalog.lookup(state.provider, state.model);
|
|
350
|
+
const ref: ModelRef = { provider: state.provider, model: state.model, effort: level, ...(info?.supportsReasoning !== undefined ? { reasoning: info.supportsReasoning } : {}) };
|
|
351
|
+
return thinkingLine(ref, rt.providers.get(state.provider)?.protocol ?? "openai", { shape: anthropicShapeFor(ref) });
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
const handleSlash = (text: string): boolean => {
|
|
355
|
+
const [cmd, ...rest] = text.slice(1).split(/\s+/);
|
|
356
|
+
const arg = rest.join(" ").trim();
|
|
357
|
+
switch (cmd) {
|
|
358
|
+
case "exit": case "quit": close(); return true;
|
|
359
|
+
case "help": lazyInfoCmd().cmdHelp(infoCtx); return true;
|
|
360
|
+
case "effort": {
|
|
361
|
+
const want = arg.trim();
|
|
362
|
+
if (want.length === 0) { renderer.addSystemNote(effortNote(rt.effort, receives(rt.effort))); return true; }
|
|
363
|
+
const level = parseEffort(want);
|
|
364
|
+
if (level === undefined) { renderer.addSystemNote(`"${want}" is not a level — ${THINKING_EFFORTS.join(" · ")}`, "warn"); return true; }
|
|
365
|
+
rt.setEffort(level);
|
|
366
|
+
renderer.addSystemNote(effortNote(level, receives(level)));
|
|
367
|
+
pushStatus(); return true;
|
|
368
|
+
}
|
|
369
|
+
case "accept-edits":
|
|
370
|
+
state.acceptEdits = !state.acceptEdits;
|
|
371
|
+
renderer.addSystemNote(state.yolo
|
|
372
|
+
? `${acceptEditsNote(state.acceptEdits)} (auto mode is on, so nothing asks either way — /yolo turns it off)`
|
|
373
|
+
: acceptEditsNote(state.acceptEdits));
|
|
374
|
+
renderer.addSystemNote(persistLevel(arg));
|
|
375
|
+
pushStatus(); return true;
|
|
376
|
+
case "yolo":
|
|
377
|
+
state.yolo = !state.yolo;
|
|
378
|
+
renderer.addSystemNote(modeSwitchNote(state.yolo)); // "ask first" / "auto (never asks)" — the flag keeps its name
|
|
379
|
+
renderer.addSystemNote(persistLevel(arg));
|
|
380
|
+
pushStatus(); return true;
|
|
381
|
+
// port #20: model writes land in the CURRENT mode's slot (mirrored to both when
|
|
382
|
+
// planActSeparateModels is off); the selector may name another provider — the registry's
|
|
383
|
+
// dispatcher routes per call, so the switch needs no restart. --save persists the default.
|
|
384
|
+
case "model": cmdModelSwitch(provCtx(), arg); return true;
|
|
385
|
+
case "models": void cmdModels(provCtx(), arg); return true;
|
|
386
|
+
case "provider": void cmdProvider(provCtx(), arg); return true;
|
|
387
|
+
case "setup": void cmdSetup(provCtx()); return true; // guided connect: picker → model → key hand-off → test → default
|
|
388
|
+
// the same job on one line (cli/connect.ts through the live registry); bare, it hands over to /setup
|
|
389
|
+
case "connect": void cmdConnect(provCtx(), arg); return true;
|
|
390
|
+
case "plan": case "act":
|
|
391
|
+
togglePlanAct(modes, cmd as AgentMode, state, renderer, pushStatus);
|
|
392
|
+
return true;
|
|
393
|
+
case "checkpoints": void cmdCheckpoints(cpCtx); return true;
|
|
394
|
+
case "restore": void cmdRestore(cpCtx, arg); return true;
|
|
395
|
+
case "status": lazyInfoCmd().cmdStatus(infoCtx); return true;
|
|
396
|
+
case "cost": lazyInfoCmd().cmdCost(infoCtx, arg); return true;
|
|
397
|
+
case "skills": lazyInfoCmd().cmdSkills(infoCtx); return true;
|
|
398
|
+
case "memory": lazyInfoCmd().cmdMemory(infoCtx); return true;
|
|
399
|
+
case "todos": lazyInfoCmd().cmdTodos(infoCtx); return true; // port #32
|
|
400
|
+
case "tasks": lazyInfoCmd().cmdTasks(infoCtx, arg); return true; // port #26
|
|
401
|
+
case "new": lazySessionCmd().cmdNew(sessCtx); return true;
|
|
402
|
+
case "rewind": case "tree": void lazySessionCmd().cmdRewind(sessCtx); return true;
|
|
403
|
+
case "sessions": void lazySessionCmd().cmdSessions(sessCtx); return true;
|
|
404
|
+
case "resume":
|
|
405
|
+
if (arg) void lazySessionCmd().cmdSessions(sessCtx, arg); else void lazySessionCmd().cmdSessions(sessCtx);
|
|
406
|
+
return true;
|
|
407
|
+
case "export": lazyInfoCmd().cmdExport(infoCtx, arg); return true;
|
|
408
|
+
case "attach": cmdAttach(attachCtx, arg); return true; // port #34
|
|
409
|
+
case "paste": cmdPasteImage(attachCtx); return true; // clipboard image → attachment (⌃v)
|
|
410
|
+
case "mcp": void cmdMcp({ renderer, cwd: rt.cwd }, arg); return true; // the MCP market (mcp-cmd.ts): palette → approval card → mcp.json
|
|
411
|
+
default:
|
|
412
|
+
// port #30: a discovered custom command renders its template and submits it as a user turn.
|
|
413
|
+
// MED-2: it gets the RAW remainder of the line (whitespace runs and pasted newlines intact —
|
|
414
|
+
// renderCommand trims the ends itself); built-ins keep the collapsed `arg` above.
|
|
415
|
+
if (!dispatchCustomCommand(cmdCtx, custom.commands, cmd ?? "", text.slice(1 + (cmd ?? "").length))) renderer.addSystemNote(`unknown command: /${cmd} (try /help)`, "warn");
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
const startRun = async (goal: string) => {
|
|
421
|
+
const stream = rt.stream; // runtime already applied any opts.stream override
|
|
422
|
+
// live check: /provider add + /provider key (or `rovecode provider add` in another terminal) clears
|
|
423
|
+
// it for the next prompt — no restart
|
|
424
|
+
const reason = rt.noProviderReason();
|
|
425
|
+
if (!stream || reason !== null) {
|
|
426
|
+
renderer.addSystemNote(reason !== null ? noModelHint("tui") : "no provider stream", "error");
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
state.busy = true;
|
|
430
|
+
renderer.setBusy(true, "thinking…");
|
|
431
|
+
pushStatus();
|
|
432
|
+
const level: PermissionLevel = state.yolo ? "auto" : state.acceptEdits ? "accept-edits" : "ask";
|
|
433
|
+
const cfg = rt.buildCfg(level, state.yolo ? undefined : async (req) => {
|
|
434
|
+
// port #24: edit/write approvals carry a bounded unified diff of the pending change
|
|
435
|
+
// (in-memory preview; any failure degrades to the plain overlay, never blocks the ask)
|
|
436
|
+
const isEdit = req.tool === "edit" || req.tool === "write";
|
|
437
|
+
// `all edits` pressed DURING this run: the rules were built before it, so the switch is honored
|
|
438
|
+
// here too — otherwise the mode would only start at the next prompt, which is not what the
|
|
439
|
+
// button says. The rules still gate the call; this only skips the card.
|
|
440
|
+
if (isEdit && state.acceptEdits) return "once";
|
|
441
|
+
let detail: string | undefined;
|
|
442
|
+
if (isEdit) {
|
|
443
|
+
try { detail = previewDiff(req.tool as "edit" | "write", req.revisedArgs, rt.cwd).text || undefined; } catch { detail = undefined; }
|
|
444
|
+
}
|
|
445
|
+
const answer = await renderer.askApproval(req.tool, JSON.stringify(req.revisedArgs).slice(0, 140), detail);
|
|
446
|
+
if (answer !== "all-edits") return answer;
|
|
447
|
+
// the surface-level door: flip the session and let THIS call through once. The core approval
|
|
448
|
+
// engine stays a three-verdict system — "all-edits" never crosses into it.
|
|
449
|
+
state.acceptEdits = true;
|
|
450
|
+
renderer.addSystemNote(acceptEditsNote(true));
|
|
451
|
+
pushStatus();
|
|
452
|
+
return "once";
|
|
453
|
+
});
|
|
454
|
+
// port #20: per-mode model resolution + plan-mode rule/prompt enforcement
|
|
455
|
+
const cur = modes.modelFor();
|
|
456
|
+
const def = rt.buildDef({ provider: cur.provider, model: cur.model });
|
|
457
|
+
applyModeToRun(modes, cfg, def);
|
|
458
|
+
const views = new Map<string, AssistantView>();
|
|
459
|
+
let lastView: AssistantView | null = null;
|
|
460
|
+
runAbort = new AbortController();
|
|
461
|
+
rt.tasks.bindRun(runAbort.signal); // port #26: Esc/quit cancel the background tasks THIS run starts; a normal end leaves them running
|
|
462
|
+
run = agentLoop(def, goal, {}, cfg, {
|
|
463
|
+
stream, registry: rt.registry, store,
|
|
464
|
+
tools: rt.registry.list().map((t) => t.schema),
|
|
465
|
+
guard: rt.guard, planReminder: rt.planReminder, signal: runAbort.signal, // port #21: Esc aborts this run's controller
|
|
466
|
+
cwd: rt.cwd, // cwd must be threaded — tools resolve relative paths against it, same as checkpoints/LSP/preview
|
|
467
|
+
hooks: rt.hooks, // port #29: pre_tool/approval/post_tool at dispatch, pre_run/compaction/post_run/on_event via the loop observer
|
|
468
|
+
}, steering);
|
|
469
|
+
try {
|
|
470
|
+
for await (const ev of run) {
|
|
471
|
+
renderer.onEvent?.(ev); // port #44: FIRST — the sextant reducer is its rows' source of truth; the calls below are duplicates it ignores while busy
|
|
472
|
+
if (ev.type === "turn_start") { resetTurnFailureCount(); state.turns++; pushStatus(); } // pushStatus here + in the finally also refreshes the port #32 todo label after a todo_write
|
|
473
|
+
else if (ev.type === "message_update") {
|
|
474
|
+
let v = views.get(ev.messageId);
|
|
475
|
+
if (!v) { v = renderer.beginAssistant(); views.set(ev.messageId, v); lastView?.done(); lastView = v; }
|
|
476
|
+
v.append(ev.delta);
|
|
477
|
+
} else if (ev.type === "tool_execution_start") {
|
|
478
|
+
renderer.toolStart(ev.callId, ev.tool, JSON.stringify(ev.args).slice(0, 120));
|
|
479
|
+
} else if (ev.type === "tool_execution_update") {
|
|
480
|
+
renderer.toolUpdate(ev.callId, ev.note);
|
|
481
|
+
} else if (ev.type === "tool_execution_end") {
|
|
482
|
+
renderer.toolEnd(ev.callId, ev.ok, ev.output.slice(0, 160).replace(/\n/g, " ⏎ "), ev.durationMs);
|
|
483
|
+
} else if (ev.type === "tool_call_failed") {
|
|
484
|
+
renderer.toolEnd(ev.callId, false, `${ev.reason}: ${ev.detail}`.slice(0, 160), 0);
|
|
485
|
+
} else if (ev.type === "compaction") {
|
|
486
|
+
renderer.addSystemNote(compactionNote(ev)); // one wording with the replayed marker (port #25 LOW-4)
|
|
487
|
+
} else if (ev.type === "steer") {
|
|
488
|
+
renderer.addSystemNote("↪ steering applied");
|
|
489
|
+
} else if (ev.type === "verify") {
|
|
490
|
+
// the verify gate (core/verify-gate.ts): say that the check is running — a silent two minutes reads as a hang
|
|
491
|
+
renderer.addSystemNote(ev.state === "running" ? `⧗ verify: ${ev.command.slice(0, 120)}` : `verify ${ev.state}: ${ev.detail ?? ""}`, ev.state === "running" || ev.state === "passed" ? "info" : "warn");
|
|
492
|
+
} else if (ev.type === "run_end") {
|
|
493
|
+
lastView?.done();
|
|
494
|
+
if (ev.status === "error") renderer.addSystemNote(ev.summary, "error");
|
|
495
|
+
else if (ev.status !== "done") renderer.addSystemNote(`run ${ev.status}: ${ev.summary}`, "warn");
|
|
496
|
+
else if (ev.outstanding) { const c = outstandingClause(ev.outstanding); if (c !== null) renderer.addSystemNote(`done · ${c}`, outstandingTone(ev.outstanding)); } // "done" ≠ finished: say what was left (core/loop.ts)
|
|
497
|
+
// if the model produced no streaming deltas, surface the final text
|
|
498
|
+
if (views.size === 0 && ev.status === "done" && ev.summary) {
|
|
499
|
+
const v = renderer.beginAssistant(); v.append(ev.summary); v.done();
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
} finally {
|
|
504
|
+
run = null; runAbort = null;
|
|
505
|
+
state.busy = false;
|
|
506
|
+
refreshUsage();
|
|
507
|
+
// port #14: surface any fallback-chain advances the router made during the run
|
|
508
|
+
for (const n of rt.drainRouterNotes()) renderer.addSystemNote(n, "warn");
|
|
509
|
+
renderer.setBusy(false);
|
|
510
|
+
pushStatus();
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
/** A plain user turn — also the path custom commands submit their rendered prompt through (port #30). */
|
|
515
|
+
const submit = (text: string): Promise<void> => {
|
|
516
|
+
// port #34: text is required. The editor drops an empty Enter before onSubmit (pi-renderer.ts),
|
|
517
|
+
// but a custom command whose template renders to "" (`/ask` on a bare `$ARGUMENTS`) lands here:
|
|
518
|
+
// an empty goal never starts a run, is never queued as a steer, and leaves the stage (and any
|
|
519
|
+
// pending mode switch) for the next real message
|
|
520
|
+
if (!text.trim()) {
|
|
521
|
+
const n = store.stagedAttachments.length;
|
|
522
|
+
renderer.addSystemNote(n === 0 ? "nothing to send — the message is empty" : `type a message to send with the attached image${n === 1 ? "" : "s"}`, "warn");
|
|
523
|
+
return Promise.resolve();
|
|
524
|
+
}
|
|
525
|
+
renderer.addUser(userTurnLine(text, store.stagedAttachments)); // port #34: image chips under the text — the stage folds into this message
|
|
526
|
+
// port #20: a pending mode switch becomes a durable session entry on the next
|
|
527
|
+
// submit (round-trip cancellation: toggling back before submitting records nothing)
|
|
528
|
+
flushModeSwitch(modes, store);
|
|
529
|
+
if (state.busy) { steering.push(text); renderer.addSystemNote(`queued as steering (applies before the next model turn)${queuedAttachNote(store)}`); return Promise.resolve(); }
|
|
530
|
+
return startRun(text);
|
|
531
|
+
};
|
|
532
|
+
// port #44: a renderer with panels (sextant) reads the runtime through this handle — once, before start()
|
|
533
|
+
_trace("renderer.attach");
|
|
534
|
+
renderer.attach?.(buildSextantAttach({ cwd: rt.cwd, sessionsDir, store: () => store, tasks: rt.tasks, model: () => modes.modelFor(), catalog, runtime: () => rt, petName: opts.pet }));
|
|
535
|
+
// /model suggestions: the ids of every configured provider's models, fetched off the boot path and again
|
|
536
|
+
// whenever the registry changes; the sextant reads the list at suggestion time (SlashCommand.choices)
|
|
537
|
+
const modelChoices: string[] = [];
|
|
538
|
+
const refreshModelChoices = (): void => { void listModelIds(rt.providers).then((ids) => { modelChoices.splice(0, modelChoices.length, ...ids); }).catch(() => {}); };
|
|
539
|
+
renderer.setCommands([...TUI_COMMANDS.map((c) => (c.name === MODEL_COMMAND.name ? { ...c, choices: () => modelChoices } : c)), ...commandsForPalette(custom.commands)]);
|
|
540
|
+
setTimeout(refreshModelChoices, 0);
|
|
541
|
+
// Node prints warnings on stderr, and stderr goes straight onto the alternate screen. A
|
|
542
|
+
// MaxListenersExceededWarning does not just say its sentence — it dumps the emitter it is complaining
|
|
543
|
+
// about, which for a stream is pages of `[Function: …]`, over the panels. Berkay hit exactly that
|
|
544
|
+
// during a Playwright MCP session (the leak itself is fixed in mcp/client.ts; this is the other half:
|
|
545
|
+
// no warning from anywhere should be able to garble the screen). Node's own printer is removed and the
|
|
546
|
+
// warning becomes a note — still said, never drawn over anything — and put back on the way out.
|
|
547
|
+
const nodeWarnListeners = process.listeners("warning");
|
|
548
|
+
// assigned here, read by `close` above (declared before this point, called only after start)
|
|
549
|
+
process.removeAllListeners("warning");
|
|
550
|
+
const onWarning = (w: Error): void => {
|
|
551
|
+
// the first line only: a MaxListenersExceededWarning's body is the emitter it is complaining about
|
|
552
|
+
const first = w.message.split("\n")[0] ?? w.message;
|
|
553
|
+
renderer.addSystemNote(`node: ${w.name === "Warning" ? "" : `${w.name}: `}${first}`, "warn");
|
|
554
|
+
};
|
|
555
|
+
process.on("warning", onWarning);
|
|
556
|
+
restoreWarnings = (): void => {
|
|
557
|
+
process.off("warning", onWarning);
|
|
558
|
+
for (const l of nodeWarnListeners) process.on("warning", l as (w: Error) => void);
|
|
559
|
+
};
|
|
560
|
+
_trace("renderer.start");
|
|
561
|
+
renderer.start({
|
|
562
|
+
onSubmit: (text) => { if (text.startsWith("/")) handleSlash(text); else void submit(text); },
|
|
563
|
+
// port #21: abort FIRST (kills in-flight fetch/subprocesses), then return() settles the generator
|
|
564
|
+
onInterrupt: () => { runAbort?.abort(); void run?.return(undefined as never); renderer.addSystemNote("run interrupted", "warn"); },
|
|
565
|
+
onExit: close,
|
|
566
|
+
});
|
|
567
|
+
rt.warmRepoMap(); // the repo map builds on the next tick, behind this first frame, not inside the first submit (runtime.ts)
|
|
568
|
+
// resumed boot: restore the transcript and usage counters (a bare session open left both blank)
|
|
569
|
+
if (boot.id !== undefined) { replayHistory(); refreshUsage(); }
|
|
570
|
+
// the welcome card (core/voice.ts): a fresh session opens with rovecode's card — connected, or the /setup
|
|
571
|
+
// pointer when no model is configured; a resumed session keeps its transcript and gets one line
|
|
572
|
+
const connected = rt.stream && rt.noProviderReason() === null ? { provider: state.provider, model: state.model } : null;
|
|
573
|
+
if (boot.id !== undefined) renderer.addSystemNote(resumedLine(store.id, rt.cwd, state.yolo));
|
|
574
|
+
else {
|
|
575
|
+
// What the card says about this session is counted, not assumed: skills and plugins are already
|
|
576
|
+
// loaded by now, and MCP servers are the entries the runtime actually accepted (an unfilled or
|
|
577
|
+
// untrusted one is not in this number, which is the point — the card must not claim it).
|
|
578
|
+
const loaded = { skills: rt.skillStore.list().length, plugins: rt.plugins.found.filter((p) => p.status === "active").length, mcp: rt.mcp?.serverNames().length ?? 0 };
|
|
579
|
+
renderer.addSystemNote(welcomeCard({ connected, cwd: rt.cwd, yolo: state.yolo, mode: state.mode, version: pkg.version, loaded, width: process.stdout.columns ?? 80 }));
|
|
580
|
+
// The update check is fire-and-forget on purpose: it never blocks the card, never throws, and says
|
|
581
|
+
// nothing at all unless there is genuinely a newer release (core/update-check.ts). A startup screen
|
|
582
|
+
// that reports its own plumbing every time teaches people to stop reading it.
|
|
583
|
+
void checkForUpdate(pkg.version).then((s) => { const l = updateLine(s); if (l !== null) renderer.addSystemNote(l); }).catch(() => {});
|
|
584
|
+
}
|
|
585
|
+
if (boot.warn) renderer.addSystemNote(boot.warn, "warn");
|
|
586
|
+
for (const w of custom.warnings) renderer.addSystemNote(w, "warn"); // port #30: skipped/shadowed command files
|
|
587
|
+
for (const w of rt.providers.warnings()) renderer.addSystemNote(`providers: ${w}`, "warn"); // malformed providers.json entries
|
|
588
|
+
rt.hooks.onWarning((w) => renderer.addSystemNote(`hooks: ${w}`, "warn")); // port #29: hook load/runtime notes (buffered ones replay first)
|
|
589
|
+
rt.plugins.onWarning((w) => renderer.addSystemNote(`plugins: ${w}`, "warn")); // plugin discovery/activation notes, the same way
|
|
590
|
+
const pluginLine = summarizePlugins(rt.plugins.found); // one line when there is at least one plugin: what loaded, what stayed off
|
|
591
|
+
if (pluginLine !== null) renderer.addSystemNote(pluginLine);
|
|
592
|
+
// a retry notice while the backoff waits ("anthropic: overloaded — retrying in 4 s (2/4)"), not after the run: the
|
|
593
|
+
// drain in the run's finally still runs and finds nothing once this listener exists
|
|
594
|
+
rt.onRouterNote((n) => renderer.addSystemNote(n, "warn"));
|
|
595
|
+
pushStatus();
|
|
596
|
+
watchProviders(provCtx()); // follow a default-model change made elsewhere; announce the first provider
|
|
597
|
+
// port #27: an unavailable configured rung (probe failed) is a clean one-line startup
|
|
598
|
+
// error — stop the renderer first so the terminal is restored, reap the MCP children
|
|
599
|
+
// construction spawned (LOW-3, as bootRuntime does), then exit 2 (embedders: rethrow)
|
|
600
|
+
await rt.sandbox.ready.catch(async (e: unknown) => {
|
|
601
|
+
renderer.stop();
|
|
602
|
+
await rt.hooks.close().catch(() => {}); // port #29: a runtime exists (session_open fired) — session_close before this exit too
|
|
603
|
+
await rt.mcp?.close().catch(() => {});
|
|
604
|
+
if (e instanceof SandboxConfigError && opts.exitOnClose !== false) { console.error(`error: ${e.message}`); process.exit(2); }
|
|
605
|
+
throw e;
|
|
606
|
+
});
|
|
607
|
+
await closedP;
|
|
608
|
+
}
|