rovecode 0.3.3 → 0.4.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +512 -0
- package/README.md +33 -48
- package/bin/rovecode.js +24 -0
- package/dist/cli/app-dybnr56b.js +2 -0
- package/dist/cli/ask-user-p8hq4xgj.js +2 -0
- package/dist/cli/auth-login-ewpgw5sm.js +2 -0
- package/dist/cli/auth-m8p9grty.js +2 -0
- package/dist/cli/bench-xv3ypwev.js +9 -0
- package/dist/cli/catalog-737wb2s0.js +2 -0
- package/dist/cli/cli-arhg40m0.js +2 -0
- package/dist/cli/client-cf2pxx8q.js +2 -0
- package/dist/cli/commands-3p7e4xxs.js +2 -0
- package/dist/cli/connect-3q93d7cb.js +2 -0
- package/dist/cli/context-cmd-eqxmxhzq.js +2 -0
- package/dist/cli/context-report-hbw9zfes.js +2 -0
- package/dist/cli/count-remote-mby98cd0.js +2 -0
- package/dist/cli/design-122y0axd.js +2 -0
- package/dist/cli/dispatch-b4egzvvh.js +2 -0
- package/dist/cli/doctor-x4jkv72e.js +3 -0
- package/dist/cli/executor-ftvg6tsy.js +2 -0
- package/dist/cli/export-pdgdhkch.js +2 -0
- package/dist/cli/files-cez9a96p.js +2 -0
- package/dist/cli/gauntlet-r3xxaszc.js +2 -0
- package/dist/cli/gauntlet-runner-r515m7kk.js +10 -0
- package/dist/cli/gauntlet-wave3-bnkjk2v2.js +5 -0
- package/dist/cli/gauntlet-wave4-acs9s60q.js +14 -0
- package/dist/cli/hashline-ewg5hbe3.js +2 -0
- package/dist/cli/http-n0kehsk8.js +5 -0
- package/dist/cli/index-z5qt1s76.js +2 -0
- package/dist/cli/install-80mp63kx.js +2 -0
- package/dist/cli/loop-12twjcat.js +2 -0
- package/dist/cli/main-01pv9206.js +4 -0
- package/dist/cli/main-0jys2ccn.js +3 -0
- package/dist/cli/main-1ztz6fkj.js +10 -0
- package/dist/cli/main-23q7cmww.js +9 -0
- package/dist/cli/main-2rzbexn2.js +3 -0
- package/dist/cli/main-2wyax8k9.js +9 -0
- package/dist/cli/main-2yeveeve.js +6 -0
- package/dist/cli/main-2z3dek0b.js +3 -0
- package/dist/cli/main-2zgsknth.js +3 -0
- package/dist/cli/main-45ejth3a.js +4 -0
- package/dist/cli/main-45rn3trk.js +22 -0
- package/dist/cli/main-4p4e2w7x.js +4 -0
- package/dist/cli/main-4y0tnfpa.js +16 -0
- package/dist/cli/main-5py0rkmc.js +4 -0
- package/dist/cli/main-6dtqmbt6.js +7 -0
- package/dist/cli/main-6h9x282m.js +4 -0
- package/dist/cli/main-6vjeds42.js +3 -0
- package/dist/cli/main-78gq4bt9.js +6 -0
- package/dist/cli/main-7jd5vh3x.js +4 -0
- package/dist/cli/main-7kt6r53y.js +4 -0
- package/dist/cli/main-8c1tbazx.js +58 -0
- package/dist/cli/main-9a9rnh47.js +19 -0
- package/dist/cli/main-9ht36z12.js +3 -0
- package/dist/cli/main-a2yfvcy9.js +7 -0
- package/dist/cli/main-a3f51n0x.js +5 -0
- package/dist/cli/main-b8zq261k.js +3 -0
- package/dist/cli/main-bxtvnf6d.js +13 -0
- package/dist/cli/main-edxc3yzt.js +4 -0
- package/dist/cli/main-evgz4mp5.js +21 -0
- package/dist/cli/main-f33fc5je.js +9 -0
- package/dist/cli/main-fvnpq46y.js +12 -0
- package/dist/cli/main-gbbty4d4.js +3 -0
- package/dist/cli/main-gth53dnt.js +25 -0
- package/dist/cli/main-hqbz10aw.js +9 -0
- package/dist/cli/main-hrrvcfan.js +38 -0
- package/dist/cli/main-hzwtsb2m.js +5 -0
- package/dist/cli/main-j7ttv0sd.js +34 -0
- package/dist/cli/main-jak598k9.js +5 -0
- package/dist/cli/main-kba6zeyd.js +6 -0
- package/dist/cli/main-kwwsz6rq.js +3 -0
- package/dist/cli/main-m8vm17zq.js +3 -0
- package/dist/cli/main-mg4f96e1.js +3 -0
- package/dist/cli/main-mg9b20ac.js +18 -0
- package/dist/cli/main-mgb9ccnx.js +3 -0
- package/dist/cli/main-mjt2p7aj.js +3 -0
- package/dist/cli/main-n6qrdbmy.js +3 -0
- package/dist/cli/main-na7wse0x.js +5 -0
- package/dist/cli/main-nqveez48.js +4 -0
- package/dist/cli/main-ntqef02r.js +10 -0
- package/dist/cli/main-nvc3yjay.js +136 -0
- package/dist/cli/main-p0cfn6nr.js +16 -0
- package/dist/cli/main-qj2djy17.js +19 -0
- package/dist/cli/main-qsevpgsv.js +3 -0
- package/dist/cli/main-qvarybsp.js +3 -0
- package/dist/cli/main-rebtt91r.js +5 -0
- package/dist/cli/main-rpg7h8mb.js +3 -0
- package/dist/cli/main-rsy72qmw.js +15 -0
- package/dist/cli/main-rvetps99.js +18 -0
- package/dist/cli/main-s4bb0jav.js +3 -0
- package/dist/cli/main-s9v8k74e.js +3 -0
- package/dist/cli/main-tjvwmscs.js +3 -0
- package/dist/cli/main-tkgarpjj.js +4 -0
- package/dist/cli/main-v8y60bb2.js +3 -0
- package/dist/cli/main-vhrrq337.js +3 -0
- package/dist/cli/main-vp2dfb7s.js +4 -0
- package/dist/cli/main-vqbr22sz.js +8 -0
- package/dist/cli/main-vxnwe5xx.js +18 -0
- package/dist/cli/main-wgph00xf.js +5 -0
- package/dist/cli/main-wk2csfnj.js +5 -0
- package/dist/cli/main-wm997zjx.js +3 -0
- package/dist/cli/main-wpkyraxh.js +3 -0
- package/dist/cli/main-wqt32p5x.js +4 -0
- package/dist/cli/main-x9ct6y1a.js +3 -0
- package/dist/cli/main-xfekqh9m.js +7 -0
- package/dist/cli/main-xt9zc3n6.js +7 -0
- package/dist/cli/main-xx2z3zh5.js +4 -0
- package/dist/cli/main-y5c82rxr.js +3 -0
- package/dist/cli/main-yrjt2sqt.js +14 -0
- package/dist/cli/main-ys6zj3yr.js +3 -0
- package/dist/cli/main-ywbxshqc.js +8 -0
- package/dist/cli/main-z13755t8.js +25 -0
- package/dist/cli/main-zc7pyrbj.js +4 -0
- package/dist/cli/main.js +279 -0
- package/dist/cli/market-cmd-bm5xvn9f.js +5 -0
- package/dist/cli/mcp-login-bthtfpt7.js +2 -0
- package/dist/cli/mcp-market-cmd-mbeshfyd.js +2 -0
- package/dist/cli/notify-54v5z9dz.js +2 -0
- package/dist/cli/oauth-g5gme95c.js +2 -0
- package/dist/cli/output-satndjap.js +16 -0
- package/dist/cli/profiles-sfhpbq3m.js +2 -0
- package/dist/cli/provider-config-hv3xtdt4.js +2 -0
- package/dist/cli/provider-kwzq6g84.js +2 -0
- package/dist/cli/registry-fh0hdnyn.js +2 -0
- package/dist/cli/registry-y1y8e94r.js +2 -0
- package/dist/cli/repl-t4z03mqq.js +11 -0
- package/dist/cli/resume-fqt4chg8.js +2 -0
- package/dist/cli/run-flags-rysbag9t.js +2 -0
- package/dist/cli/runtime-j19fjbsa.js +2 -0
- package/dist/cli/sandbox-config-g4qxd7y5.js +2 -0
- package/dist/cli/server-r0b6bksk.js +5 -0
- package/dist/cli/session-arg-txmn5g4x.js +2 -0
- package/dist/cli/session-ed250d9j.js +2 -0
- package/dist/cli/sessions-cmd-adw7svfn.js +7 -0
- package/dist/cli/settings-y9rzcqx8.js +2 -0
- package/dist/cli/setup-jmbr11j0.js +2 -0
- package/dist/cli/sextant-smoke-tcth0vea.js +5 -0
- package/dist/cli/skills-cmd-zbdy99v6.js +2 -0
- package/dist/cli/smoke-1bg937kx.js +8 -0
- package/dist/cli/start-chat-p01cdks3.js +12 -0
- package/dist/cli/stream-4wmyaypz.js +2 -0
- package/dist/cli/task-eg4s093s.js +2 -0
- package/dist/cli/tasks-12v9rr9k.js +2 -0
- package/dist/cli/thinking-a5ngvqyh.js +2 -0
- package/dist/cli/todo-1wxpcecx.js +2 -0
- package/dist/cli/tools-2ftsya7w.js +2 -0
- package/dist/cli/tools-x1tj4fxm.js +2 -0
- package/dist/cli/trust-cmd-hccxehzb.js +2 -0
- package/dist/cli/update-check-ygt3vd7m.js +2 -0
- package/dist/cli/update-cmd-v23qhr8c.js +2 -0
- package/dist/cli/voice-g1gtck92.js +2 -0
- package/dist/cli/webfetch-0nnrjgb5.js +2 -0
- package/dist/cli/websearch-f0vr2p7d.js +2 -0
- package/dist/cli/workspace-9rq1w4ta.js +2 -0
- package/dist/lib/index.js +62 -0
- package/dist/lib/models-index.json +1 -0
- package/dist/lib/plugins.js +6 -0
- package/dist/lib/providers.js +17 -0
- package/dist/lib/public-api.js +20 -0
- package/dist/rovecode.exe +4 -0
- package/package.json +83 -72
- package/bin/rovecode.ts +0 -21
- package/src/acp/server.ts +0 -374
- package/src/cli/auth-login.ts +0 -122
- package/src/cli/connect.ts +0 -244
- package/src/cli/context-cmd.ts +0 -199
- package/src/cli/dispatch.ts +0 -82
- package/src/cli/doctor.ts +0 -362
- package/src/cli/export.ts +0 -276
- package/src/cli/help.ts +0 -293
- package/src/cli/is-tui-invocation.ts +0 -8
- package/src/cli/main.ts +0 -583
- package/src/cli/market-cmd.ts +0 -658
- package/src/cli/mcp-login.ts +0 -141
- package/src/cli/mcp-market-cmd.ts +0 -302
- package/src/cli/output.ts +0 -382
- package/src/cli/repl.ts +0 -250
- package/src/cli/repomap-root.ts +0 -14
- package/src/cli/resume.ts +0 -57
- package/src/cli/run-flags.ts +0 -43
- package/src/cli/run-limits.ts +0 -78
- package/src/cli/runtime.ts +0 -931
- package/src/cli/session-arg.ts +0 -30
- package/src/cli/sessions-cmd.ts +0 -145
- package/src/cli/setup.ts +0 -153
- package/src/cli/skills-cmd.ts +0 -194
- package/src/cli/start-chat.ts +0 -65
- package/src/cli/trust-cmd.ts +0 -52
- package/src/coding/bash.ts +0 -148
- package/src/coding/checkpoints.ts +0 -327
- package/src/coding/diff.ts +0 -138
- package/src/coding/files.ts +0 -341
- package/src/coding/hashline.ts +0 -274
- package/src/coding/lsp-gate.ts +0 -254
- package/src/coding/lsp-servers.ts +0 -147
- package/src/coding/lsp.ts +0 -283
- package/src/coding/repomap-cache.ts +0 -99
- package/src/coding/repomap-files.ts +0 -192
- package/src/coding/repomap.ts +0 -481
- package/src/core/agents.ts +0 -255
- package/src/core/compaction.ts +0 -259
- package/src/core/config.ts +0 -289
- package/src/core/context-report.ts +0 -228
- package/src/core/context.ts +0 -60
- package/src/core/count-remote.ts +0 -107
- package/src/core/execpolicy-rules.ts +0 -196
- package/src/core/execpolicy.ts +0 -385
- package/src/core/executor.ts +0 -454
- package/src/core/guardrails.ts +0 -400
- package/src/core/hooks.ts +0 -411
- package/src/core/images.ts +0 -230
- package/src/core/intro.ts +0 -266
- package/src/core/loop.ts +0 -567
- package/src/core/modes.ts +0 -372
- package/src/core/orchestrator.ts +0 -245
- package/src/core/proc-group.ts +0 -48
- package/src/core/project-trust.ts +0 -98
- package/src/core/reflection.ts +0 -165
- package/src/core/sandbox-config.ts +0 -186
- package/src/core/session-id.ts +0 -24
- package/src/core/session-images.ts +0 -73
- package/src/core/session-ops.ts +0 -183
- package/src/core/session-text.ts +0 -29
- package/src/core/session.ts +0 -469
- package/src/core/settings.ts +0 -170
- package/src/core/tasks.ts +0 -646
- package/src/core/token-scale.ts +0 -108
- package/src/core/tools.ts +0 -309
- package/src/core/trust.ts +0 -104
- package/src/core/types.ts +0 -330
- package/src/core/update-check.ts +0 -171
- package/src/core/usage.ts +0 -204
- package/src/core/validate.ts +0 -121
- package/src/core/verify-gate.ts +0 -159
- package/src/core/verify.ts +0 -236
- package/src/core/voice.ts +0 -158
- package/src/core/win-job.ts +0 -183
- package/src/core/workspace.ts +0 -184
- package/src/design/audit.ts +0 -797
- package/src/design/direction.ts +0 -190
- package/src/design/rules.ts +0 -157
- package/src/eval/bench.ts +0 -150
- package/src/eval/gauntlet-runner.ts +0 -215
- package/src/eval/gauntlet-support.ts +0 -84
- package/src/eval/gauntlet-wave3.ts +0 -269
- package/src/eval/gauntlet-wave4.ts +0 -217
- package/src/eval/gauntlet.ts +0 -253
- package/src/index.ts +0 -17
- package/src/lanes/agy.ts +0 -95
- package/src/lanes/approval.ts +0 -24
- package/src/lanes/claude.ts +0 -129
- package/src/lanes/codex.ts +0 -127
- package/src/lanes/events.ts +0 -130
- package/src/lanes/job.ts +0 -142
- package/src/lanes/opencode.ts +0 -122
- package/src/lanes/process.ts +0 -184
- package/src/lanes/progress.ts +0 -183
- package/src/lanes/registry.ts +0 -178
- package/src/lanes/runner.ts +0 -124
- package/src/lanes/types.ts +0 -112
- package/src/market/catalogs/mcp-docs.json +0 -111
- package/src/market/catalogs/plugins.json +0 -111
- package/src/market/catalogs/skills.json +0 -478
- package/src/market/clone.ts +0 -72
- package/src/market/context-cost.ts +0 -121
- package/src/market/digest.ts +0 -106
- package/src/market/index.ts +0 -22
- package/src/market/install.ts +0 -578
- package/src/market/manifest.ts +0 -187
- package/src/market/prereq.ts +0 -145
- package/src/market/registry.ts +0 -363
- package/src/market/resolve.ts +0 -111
- package/src/market/types.ts +0 -236
- package/src/market/validate.ts +0 -227
- package/src/mcp/client.ts +0 -449
- package/src/mcp/config.ts +0 -252
- package/src/mcp/local-package.ts +0 -211
- package/src/mcp/market-catalog.ts +0 -84
- package/src/mcp/market-install.ts +0 -289
- package/src/mcp/market.ts +0 -362
- package/src/mcp/oauth.ts +0 -251
- package/src/mcp/prompts-resources.ts +0 -249
- package/src/mcp/shared.ts +0 -149
- package/src/mcp/status.ts +0 -67
- package/src/mcp/tools.ts +0 -275
- package/src/mcp/transport.ts +0 -122
- package/src/mcp/trust.ts +0 -25
- package/src/memory/blocks.ts +0 -278
- package/src/memory/recall.ts +0 -355
- package/src/memory/scope.ts +0 -182
- package/src/memory/store.ts +0 -105
- package/src/memory/tools.ts +0 -99
- package/src/plugins/cli.ts +0 -119
- package/src/plugins/discover.ts +0 -108
- package/src/plugins/index.ts +0 -50
- package/src/plugins/install.ts +0 -184
- package/src/plugins/load.ts +0 -124
- package/src/plugins/manifest.ts +0 -92
- package/src/plugins/state.ts +0 -83
- package/src/providers/auth.ts +0 -408
- package/src/providers/cache.ts +0 -223
- package/src/providers/catalog-local.ts +0 -160
- package/src/providers/catalog.ts +0 -421
- package/src/providers/middleware-context.ts +0 -86
- package/src/providers/middleware.ts +0 -373
- package/src/providers/model-list.ts +0 -23
- package/src/providers/oauth/common.ts +0 -105
- package/src/providers/oauth/device-code.ts +0 -107
- package/src/providers/oauth/github-copilot.ts +0 -146
- package/src/providers/oauth/loopback.ts +0 -158
- package/src/providers/oauth/openai.ts +0 -163
- package/src/providers/oauth/openrouter.ts +0 -89
- package/src/providers/oauth/pkce.ts +0 -45
- package/src/providers/oauth/registry.ts +0 -39
- package/src/providers/oauth/seam.ts +0 -89
- package/src/providers/profile-glm53.ts +0 -111
- package/src/providers/profile-sonnet5-persona.ts +0 -65
- package/src/providers/profile-sonnet5-voice.ts +0 -23
- package/src/providers/profiles.ts +0 -156
- package/src/providers/provider-config.ts +0 -311
- package/src/providers/registry.ts +0 -333
- package/src/providers/responses.ts +0 -209
- package/src/providers/retry.ts +0 -234
- package/src/providers/router.ts +0 -294
- package/src/providers/sse.ts +0 -26
- package/src/providers/stream-errors.ts +0 -117
- package/src/providers/stream.ts +0 -566
- package/src/providers/thinking.ts +0 -189
- package/src/providers/wire-messages.ts +0 -129
- package/src/providers/wire-responses.ts +0 -79
- package/src/providers/wire-select.ts +0 -53
- package/src/server/http.ts +0 -291
- package/src/server/openapi.ts +0 -246
- package/src/sextant/card-hits.ts +0 -102
- package/src/sextant/card-keys.ts +0 -55
- package/src/sextant/context-source.ts +0 -157
- package/src/sextant/crew-cards.ts +0 -350
- package/src/sextant/draw-agents.ts +0 -273
- package/src/sextant/draw-code.ts +0 -388
- package/src/sextant/draw-context.ts +0 -222
- package/src/sextant/draw-frame.ts +0 -164
- package/src/sextant/draw-market.ts +0 -573
- package/src/sextant/draw-messages.ts +0 -386
- package/src/sextant/draw-pet.ts +0 -230
- package/src/sextant/draw-plan.ts +0 -187
- package/src/sextant/draw-tabs.ts +0 -85
- package/src/sextant/draw-util.ts +0 -65
- package/src/sextant/draw-wizard.ts +0 -378
- package/src/sextant/engine.ts +0 -230
- package/src/sextant/frame-hits.ts +0 -25
- package/src/sextant/frame.ts +0 -101
- package/src/sextant/git-status.ts +0 -197
- package/src/sextant/grid.ts +0 -59
- package/src/sextant/input.ts +0 -119
- package/src/sextant/keys.ts +0 -521
- package/src/sextant/layout.ts +0 -86
- package/src/sextant/local-commands.ts +0 -169
- package/src/sextant/market-source.ts +0 -287
- package/src/sextant/mentions.ts +0 -200
- package/src/sextant/message-hits.ts +0 -26
- package/src/sextant/model.ts +0 -387
- package/src/sextant/overlays.ts +0 -456
- package/src/sextant/panel-hits.ts +0 -38
- package/src/sextant/pet.ts +0 -399
- package/src/sextant/screen.ts +0 -324
- package/src/sextant/scroll-hits.ts +0 -66
- package/src/sextant/scrollbar.ts +0 -82
- package/src/sextant/sextant-bridge.ts +0 -174
- package/src/sextant/sextant-cards.ts +0 -142
- package/src/sextant/sextant-diff-base.ts +0 -63
- package/src/sextant/sextant-files.ts +0 -154
- package/src/sextant/sextant-frame-loop.ts +0 -335
- package/src/sextant/sextant-renderer.ts +0 -574
- package/src/sextant/sextant-repo.ts +0 -140
- package/src/sextant/theme.ts +0 -66
- package/src/sextant/tool-rows.ts +0 -189
- package/src/sextant/types.ts +0 -493
- package/src/skills/index.ts +0 -387
- package/src/skills/pack.ts +0 -220
- package/src/skills/spec.ts +0 -162
- package/src/skills/tools.ts +0 -69
- package/src/skills/versioned.ts +0 -227
- package/src/telemetry/otel-export.ts +0 -122
- package/src/telemetry/otel-lanes.ts +0 -89
- package/src/telemetry/otel-logs.ts +0 -131
- package/src/telemetry/otel-metrics.ts +0 -136
- package/src/telemetry/otel.ts +0 -397
- package/src/telemetry/otlp.ts +0 -76
- package/src/tools/ask-user.ts +0 -156
- package/src/tools/bash-bg.ts +0 -94
- package/src/tools/bash-jobs.ts +0 -237
- package/src/tools/design.ts +0 -151
- package/src/tools/evalcell.ts +0 -338
- package/src/tools/html-text.ts +0 -139
- package/src/tools/provider.ts +0 -149
- package/src/tools/task.ts +0 -250
- package/src/tools/todo.ts +0 -320
- package/src/tools/webfetch.ts +0 -332
- package/src/tools/websearch.ts +0 -359
- package/src/tui/agents-cmd.ts +0 -41
- package/src/tui/app.ts +0 -749
- package/src/tui/attach.ts +0 -127
- package/src/tui/boot-notes.ts +0 -41
- package/src/tui/builtin-prompts.ts +0 -59
- package/src/tui/checkpoints-cmd.ts +0 -70
- package/src/tui/clipboard-image.ts +0 -81
- package/src/tui/clipboard.ts +0 -78
- package/src/tui/commands.ts +0 -283
- package/src/tui/config-view.ts +0 -53
- package/src/tui/context-cmds.ts +0 -282
- package/src/tui/cost.ts +0 -108
- package/src/tui/crash-guard.ts +0 -173
- package/src/tui/focus-terminal.ts +0 -34
- package/src/tui/git-cmds.ts +0 -273
- package/src/tui/git-plain.ts +0 -58
- package/src/tui/info-cmd.ts +0 -150
- package/src/tui/input-plain.ts +0 -76
- package/src/tui/mcp-cmd.ts +0 -128
- package/src/tui/memory-note.ts +0 -77
- package/src/tui/modes-cmd.ts +0 -45
- package/src/tui/notify-seq.ts +0 -100
- package/src/tui/notify.ts +0 -318
- package/src/tui/overlays.ts +0 -97
- package/src/tui/pi-renderer.ts +0 -428
- package/src/tui/providers-cmd.ts +0 -377
- package/src/tui/reasoning-view.ts +0 -56
- package/src/tui/renderer.ts +0 -128
- package/src/tui/replay-marker.ts +0 -29
- package/src/tui/session-cmd.ts +0 -148
- package/src/tui/session-manage.ts +0 -95
- package/src/tui/sextant-attach.ts +0 -102
- package/src/tui/sextant-io.ts +0 -202
- package/src/tui/sextant-smoke.ts +0 -110
- package/src/tui/shell-cmd.ts +0 -158
- package/src/tui/smoke.ts +0 -72
- package/src/tui/staged-terminal.ts +0 -50
- package/src/tui/startup.ts +0 -12
- package/src/tui/theme.ts +0 -59
- package/src/tui/todo-label.ts +0 -7
- package/src/tui/trust-card.ts +0 -107
- package/src/tui/tui-commands.ts +0 -87
- package/tsconfig.json +0 -30
- package/vendor/pi-tui/LICENSE +0 -21
- package/vendor/pi-tui/PATCHES.md +0 -12
- package/vendor/pi-tui/PROVENANCE.md +0 -12
- package/vendor/pi-tui/README.upstream.md +0 -854
- 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 +0 -158
- package/vendor/pi-tui/src/autocomplete.ts +0 -827
- package/vendor/pi-tui/src/components/alt-screen-flash.ts +0 -52
- package/vendor/pi-tui/src/components/box.ts +0 -138
- package/vendor/pi-tui/src/components/cancellable-loader.ts +0 -41
- package/vendor/pi-tui/src/components/editor.ts +0 -2364
- package/vendor/pi-tui/src/components/h-stack.ts +0 -45
- package/vendor/pi-tui/src/components/image.ts +0 -128
- package/vendor/pi-tui/src/components/input.ts +0 -448
- package/vendor/pi-tui/src/components/loader.ts +0 -93
- package/vendor/pi-tui/src/components/markdown.ts +0 -1016
- package/vendor/pi-tui/src/components/scroll-view.ts +0 -217
- package/vendor/pi-tui/src/components/select-list.ts +0 -230
- package/vendor/pi-tui/src/components/settings-list.ts +0 -277
- package/vendor/pi-tui/src/components/spacer.ts +0 -29
- package/vendor/pi-tui/src/components/stack.ts +0 -155
- package/vendor/pi-tui/src/components/text.ts +0 -108
- package/vendor/pi-tui/src/components/truncated-text.ts +0 -66
- package/vendor/pi-tui/src/components/v-stack.ts +0 -34
- package/vendor/pi-tui/src/editor-component.ts +0 -75
- package/vendor/pi-tui/src/fuzzy.ts +0 -138
- package/vendor/pi-tui/src/index.ts +0 -149
- package/vendor/pi-tui/src/keybindings.ts +0 -321
- package/vendor/pi-tui/src/keys.ts +0 -1402
- package/vendor/pi-tui/src/kill-ring.ts +0 -47
- package/vendor/pi-tui/src/latex.ts +0 -1381
- package/vendor/pi-tui/src/layout-node.ts +0 -52
- package/vendor/pi-tui/src/layout.ts +0 -411
- package/vendor/pi-tui/src/native-modifiers.ts +0 -60
- package/vendor/pi-tui/src/native-module-path.ts +0 -32
- package/vendor/pi-tui/src/stdin-buffer.ts +0 -445
- package/vendor/pi-tui/src/terminal-colors.ts +0 -74
- package/vendor/pi-tui/src/terminal-image.ts +0 -701
- package/vendor/pi-tui/src/terminal.ts +0 -554
- package/vendor/pi-tui/src/tui-alt-screen.ts +0 -1379
- package/vendor/pi-tui/src/tui-main-screen.ts +0 -655
- package/vendor/pi-tui/src/tui.ts +0 -1264
- package/vendor/pi-tui/src/undo-stack.ts +0 -29
- package/vendor/pi-tui/src/utils.ts +0 -1327
- package/vendor/pi-tui/src/word-navigation.ts +0 -118
- package/vendor/pi-tui/test/test-themes.ts +0 -39
- package/vendor/pi-tui/test/virtual-terminal.ts +0 -219
- /package/{src/providers → dist/cli}/models-index.json +0 -0
package/src/cli/runtime.ts
DELETED
|
@@ -1,931 +0,0 @@
|
|
|
1
|
-
/** Shared runtime construction for CLI surfaces (repl, run, tui): stores, tool
|
|
2
|
-
* registration, skills/memory indexes, provider resolution, RunConfig defaults.
|
|
3
|
-
* Extracted from repl.ts/main.ts so every surface builds the same agent. */
|
|
4
|
-
|
|
5
|
-
import type { AgentDefinition, ApprovalFn, Message, ModelRef, PermissionLevel, RunConfig, StreamFn, ThinkingEffort, TokenUsage, Tool } from "../core/types.ts";
|
|
6
|
-
import { parseEffort } from "../core/types.ts";
|
|
7
|
-
import { SessionStore } from "../core/session.ts";
|
|
8
|
-
import { ToolRegistry } from "../core/tools.ts";
|
|
9
|
-
import { SkillStore } from "../skills/index.ts";
|
|
10
|
-
import { createSkillTools, buildSkillsIndex } from "../skills/tools.ts";
|
|
11
|
-
import { BlockStore } from "../memory/blocks.ts";
|
|
12
|
-
import { openScopedMemory } from "../memory/scope.ts";
|
|
13
|
-
import { memoryEditTool } from "../memory/tools.ts";
|
|
14
|
-
import type { ProviderConfig } from "../providers/stream.ts";
|
|
15
|
-
import { ProviderRegistry } from "../providers/registry.ts";
|
|
16
|
-
import { providerEditTool, providerListTool } from "../tools/provider.ts";
|
|
17
|
-
import { designAuditTool, designDirectionTool } from "../tools/design.ts";
|
|
18
|
-
import { designPromptSection } from "../design/rules.ts";
|
|
19
|
-
import { withToolCallParsing, toolPromptBlock } from "../providers/middleware.ts";
|
|
20
|
-
import { ModelCatalog } from "../providers/catalog.ts";
|
|
21
|
-
import { GLM_53_AGENT_CONTRACT, profileFor, profilePromptSection } from "../providers/profiles.ts";
|
|
22
|
-
import { loadProjectContext, type ProjectContext } from "../core/config.ts";
|
|
23
|
-
import { estimateTokens, type ContextChunk } from "../core/context.ts";
|
|
24
|
-
import { parseCompactionStrategy } from "../core/compaction.ts";
|
|
25
|
-
import { ToolGuard } from "../core/guardrails.ts";
|
|
26
|
-
import { HookRunner } from "../core/hooks.ts";
|
|
27
|
-
import { settingsTrustNotes } from "../core/settings.ts";
|
|
28
|
-
import { createReflectionHooks, reflectionEnabled } from "../core/reflection.ts";
|
|
29
|
-
import type { McpManager } from "../mcp/client.ts";
|
|
30
|
-
import { activatePlugins, discoverPlugins, loadState as loadPluginState, type DiscoveredPlugin, type LoadedPlugin } from "../plugins/index.ts";
|
|
31
|
-
import type { McpServerConfig } from "../mcp/config.ts";
|
|
32
|
-
import { trustedPredicate } from "../mcp/trust.ts";
|
|
33
|
-
import { positiveInt, positiveUsd, type RunLimits } from "./run-limits.ts";
|
|
34
|
-
import { costUsdTiered } from "../core/usage.ts";
|
|
35
|
-
import { ratesFor } from "../providers/catalog.ts";
|
|
36
|
-
import { contextBudgetFor } from "../core/context-report.ts";
|
|
37
|
-
import { tokenScaleFor } from "../core/token-scale.ts";
|
|
38
|
-
import { laneApprover } from "../lanes/approval.ts";
|
|
39
|
-
import { BashJobManager, installJobManager } from "../tools/bash-jobs.ts";
|
|
40
|
-
import { bashJobTools } from "../tools/bash-bg.ts";
|
|
41
|
-
import { isAdapterId } from "../lanes/types.ts";
|
|
42
|
-
import { createWebSearchTool } from "../tools/websearch.ts";
|
|
43
|
-
import type { LaneJobDeps } from "../lanes/job.ts";
|
|
44
|
-
import { rovecodeHome } from "../providers/auth.ts";
|
|
45
|
-
import { readTool, editTool, writeTool, bashTool } from "../coding/hashline.ts";
|
|
46
|
-
import { globTool, grepTool, lsTool } from "../coding/files.ts";
|
|
47
|
-
import { withLspGate, lspGateNote, lspAvailabilityNote } from "../coding/lsp.ts";
|
|
48
|
-
import { EXTERNAL_ACTION, WorkspaceRoots, resolveRoots } from "../core/workspace.ts"; // --add-dir roots + the file.external boundary
|
|
49
|
-
import type { buildRepoMapChunk as BuildRepoMapChunkFn, buildRepoMapChunkAsync as BuildRepoMapChunkAsyncFn } from "../coding/repomap.ts";
|
|
50
|
-
import { anchorEntryId, Checkpoints, MUTATING_KINDS } from "../coding/checkpoints.ts";
|
|
51
|
-
import { createRouter, parseModelRef, roleTableFromEnv, type Router } from "../providers/router.ts";
|
|
52
|
-
import { agentRows, discoverAgents, modelProblemFor, restrictTools, type CustomAgent, type DiscoveredAgents } from "../core/agents.ts";
|
|
53
|
-
import { describeGiveUp, describeRetry, retryOptionsFromEnv, withRetry } from "../providers/retry.ts";
|
|
54
|
-
import { createEvalCellTool } from "../tools/evalcell.ts";
|
|
55
|
-
import { webFetchTool } from "../tools/webfetch.ts";
|
|
56
|
-
import { askUserTool, type AskFn } from "../tools/ask-user.ts";
|
|
57
|
-
import { execPolicyApprover } from "../core/execpolicy.ts";
|
|
58
|
-
import { recallTool } from "../memory/recall.ts";
|
|
59
|
-
import { configureExecutor, type SpawnRunner } from "../core/executor.ts";
|
|
60
|
-
import { loadSandboxConfig, unavailableRungError, type SandboxConfig } from "../core/sandbox-config.ts";
|
|
61
|
-
import { loadTodos, planReminder, todoTools } from "../tools/todo.ts";
|
|
62
|
-
import { noteVerifyCost, resolveForGate, runVerify, VERIFY_TIMEOUT_MS, type VerifyResolver } from "../core/verify-gate.ts";
|
|
63
|
-
import { SteeringQueue } from "../core/loop.ts";
|
|
64
|
-
import { TaskManager } from "../core/tasks.ts";
|
|
65
|
-
import { createTaskTool, createTaskStatusTool } from "../tools/task.ts";
|
|
66
|
-
import type { ChildContext, ChildRunnerDeps } from "../core/orchestrator.ts";
|
|
67
|
-
import { existsSync, mkdirSync } from "node:fs";
|
|
68
|
-
import { sep, join } from "node:path";
|
|
69
|
-
import { shouldWarmRepoMap } from "./repomap-root.ts";
|
|
70
|
-
import { randomUUID } from "node:crypto";
|
|
71
|
-
import { noModelHint } from "../core/voice.ts";
|
|
72
|
-
|
|
73
|
-
/** upper bound on the per-model max_tokens buildDef derives from the catalog: enough for a long page or
|
|
74
|
-
* plan, not the 128K some models advertise — a runaway answer should stop before it costs that much */
|
|
75
|
-
export const MAX_OUTPUT_CAP = 32_768;
|
|
76
|
-
|
|
77
|
-
export interface RuntimeOptions {
|
|
78
|
-
cwd?: string;
|
|
79
|
-
sessionId?: string;
|
|
80
|
-
/** override the provider-derived stream (tests); null forces "no stream" */
|
|
81
|
-
stream?: StreamFn | null;
|
|
82
|
-
/** port #27 test seam: process runner behind the executor rung (probe AND
|
|
83
|
-
* commands); default Bun.spawn. Tests must never probe a real wsl.exe/docker. */
|
|
84
|
-
spawnRunner?: SpawnRunner;
|
|
85
|
-
/** test seam for the verify gate: which check a run that wrote files must pass. Default: core/verify.ts
|
|
86
|
-
* resolveVerify through verify-gate.ts resolveForGate (settings, then a manifest's unambiguous check script,
|
|
87
|
-
* never a guess). Returning null or no commands makes the gate report "not verified" instead of running. */
|
|
88
|
-
verifyResolver?: VerifyResolver;
|
|
89
|
-
/** `--add-dir <dir>` values (absolute, or relative to process.cwd()) — extra workspace roots beside the cwd
|
|
90
|
-
* (core/workspace.ts): visible to every permission decision as `allow file.external <root>\*` (and `allow
|
|
91
|
-
* file.write <root>\*` under accept-edits), named in the system prompt, each edit under one LSP-diagnosed against
|
|
92
|
-
* it. Resolved before any side effect; a value that is not a directory or is a filesystem root throws
|
|
93
|
-
* WorkspaceRootError (the surfaces print it and exit 2). No settings key, no env: a repo must never widen its own roots. */
|
|
94
|
-
addDirs?: readonly string[];
|
|
95
|
-
/** port #27 test seam: platform the rung probe assumes; default process.platform */
|
|
96
|
-
platform?: NodeJS.Platform;
|
|
97
|
-
/** port #47: what an external agentic-CLI lane needs — the env its knobs are read from and the spawn
|
|
98
|
-
* seam (tests hand in a fake CLI; production spawns the real one). Omitted, lanes stay off, because
|
|
99
|
-
* the allow-list is read from the environment and an unset list allows nothing. */
|
|
100
|
-
lanes?: LaneJobDeps;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** port #27: the rung this runtime asked the executor seam for, plus its probe. */
|
|
104
|
-
export interface SandboxState extends SandboxConfig {
|
|
105
|
-
/** settles once the rung is probed + installed behind getExecutor(); rejects
|
|
106
|
-
* with SandboxConfigError (one line). Await it before the first tool call —
|
|
107
|
-
* bootRuntime does; until then a non-direct rung is "desired, not yet met"
|
|
108
|
-
* and bashTool would get the seam's RungUnavailableError, never lazy direct. */
|
|
109
|
-
ready: Promise<void>;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export interface Runtime {
|
|
113
|
-
cwd: string;
|
|
114
|
-
sessionId: string;
|
|
115
|
-
store: SessionStore;
|
|
116
|
-
registry: ToolRegistry;
|
|
117
|
-
skillStore: SkillStore;
|
|
118
|
-
blockStore: BlockStore;
|
|
119
|
-
/** the live provider registry (providers/registry.ts): providers.json (user + project) + stored
|
|
120
|
-
* credentials + env, hot-reloaded on file change — `rovecode provider add`, `/provider …` in the
|
|
121
|
-
* TUI and the agent's provider_edit tool all land here and serve the next model call, no restart */
|
|
122
|
-
providers: ProviderRegistry;
|
|
123
|
-
/** the default provider as a stream.ts config — LIVE (re-resolved on every read); null when none */
|
|
124
|
-
provider: ProviderConfig | null;
|
|
125
|
-
/** the registry's dispatching stream (router → retry → middleware → per-provider adapter) or the
|
|
126
|
-
* opts.stream override; null only when opts.stream was explicitly null */
|
|
127
|
-
stream: StreamFn | null;
|
|
128
|
-
/** env ROVECODE_MODEL ?? providers.json `default` ?? the default provider's model ?? "" — LIVE */
|
|
129
|
-
defaultModel: string;
|
|
130
|
-
/** null when a run can start; else the one-line reason (no provider configured yet). Live: adding
|
|
131
|
-
* a provider through the CLI, the TUI or the agent flips it back to null without a restart.
|
|
132
|
-
* Always null when the runtime was built with an injected stream (tests, smoke). */
|
|
133
|
-
noProviderReason(): string | null;
|
|
134
|
-
/** interactive system prompt incl. skills index + memory index (indexes rebuilt per call). `cwdOverride`
|
|
135
|
-
* names another directory in the identity sentence — the live gauntlet points the model at its scratch
|
|
136
|
-
* workspace while everything else (indexes, profile override lookup) stays on the runtime's cwd */
|
|
137
|
-
systemPrompt(cwdOverride?: string): string;
|
|
138
|
-
/** the run's definition. `agent` (core/agents.ts): the CHILD definition for that custom agent — its body as the prompt
|
|
139
|
-
* (empty → the runtime's), its allow-list, its mode — and it MUST NOT re-point the active model; `child`: a task
|
|
140
|
-
* child's def, left without the non-native tool-calling block (orchestrator runChild renders it from the child's own
|
|
141
|
-
* registry). Every surface calls buildDef(model) right before its agentLoop. */
|
|
142
|
-
buildDef(model: ModelRef, opts?: { cwd?: string; agent?: CustomAgent; child?: boolean }): AgentDefinition;
|
|
143
|
-
/** Build the repo map on the next turn of the event loop instead of inside the first buildDef. The TUI
|
|
144
|
-
* calls this right after its first paint: the build took 720–850 ms in this repository (2026-09-06),
|
|
145
|
-
* all of it on the first request's latency when it ran at submit time. A buildDef that arrives while
|
|
146
|
-
* the timer is still pending gets a definition WITHOUT the map — the map is not in that prompt, the
|
|
147
|
-
* request never waits — and every later run has it. Headless callers do not call this and keep the
|
|
148
|
-
* synchronous build: a one-shot `run` wants the map in its only prompt. Idempotent. */
|
|
149
|
-
warmRepoMap(): void;
|
|
150
|
-
/** Cancel interactive warmup and wait for its file/git handles before the surface closes. */
|
|
151
|
-
stopRepoMapWarmup(): Promise<void>;
|
|
152
|
-
/** `true`/`false` still mean auto/ask — every existing caller keeps working */
|
|
153
|
-
buildCfg(permission: PermissionLevel | boolean, approval?: ApprovalFn): RunConfig;
|
|
154
|
-
/** swap the session-scoped memory store — rebinds the memory tool AND the prompt (port #2 fix) */
|
|
155
|
-
setBlockStore(b: BlockStore): void;
|
|
156
|
-
/** tool-loop guardrails (port #4), one per runtime, thread into LoopDeps.guard */
|
|
157
|
-
guard: ToolGuard;
|
|
158
|
-
/** port #32: the open todo list, re-sent once per turn — thread into LoopDeps.planReminder */
|
|
159
|
-
planReminder: (history: readonly Message[]) => string | null;
|
|
160
|
-
/** How hard the model thinks before answering. Stamped onto every ModelRef buildDef hands out, so
|
|
161
|
-
* ONE setting reaches every surface (TUI, one-shot, serve, acp) without each threading a flag.
|
|
162
|
-
* A ref that already names an effort keeps it. */
|
|
163
|
-
effort: ThinkingEffort;
|
|
164
|
-
setEffort(e: ThinkingEffort): void;
|
|
165
|
-
/** the two ceilings on a run (cli/run-limits.ts): buildCfg reads them ahead of ROVECODE_MAX_TURNS /
|
|
166
|
-
* ROVECODE_MAX_SECONDS and the 60-turn default — `rovecode run` sets its flags and headless default here */
|
|
167
|
-
setRunLimits(limits: RunLimits): void;
|
|
168
|
-
/** MCP server manager (port #3); null when no servers were configured at boot AND none has been
|
|
169
|
-
* installed since — `reloadMcp` creates it on demand. */
|
|
170
|
-
mcp: McpManager | null;
|
|
171
|
-
/** Re-read the MCP files and bring the session in line with them, connecting anything new. What
|
|
172
|
-
* `market install mcp:<id>` calls so a fresh server is usable in the session that installed it
|
|
173
|
-
* rather than after a restart. Never throws: a server that will not connect comes back in `failed`
|
|
174
|
-
* and simply stays unavailable, exactly as at boot. */
|
|
175
|
-
reloadMcp(): Promise<{ added: string[]; removed: string[]; failed: { name: string; error: string }[]; skipped: string[] }>;
|
|
176
|
-
/** port #8 config snapshot (AGENTS.md/CLAUDE.md/… harvested cwd-upward ONCE
|
|
177
|
-
* at construction, for prompt-cache stability) incl. dropped/truncated
|
|
178
|
-
* source stubs for /status. Mid-session config edits are intentionally not
|
|
179
|
-
* picked up — restart rovecode (a new runtime) to refresh. */
|
|
180
|
-
projectContext: ProjectContext;
|
|
181
|
-
/** port #14: role→model router with fallback chains (env ROVECODE_MODEL_<ROLE>). */
|
|
182
|
-
router: Router;
|
|
183
|
-
/** port #14: fallback-advance notes accumulated since the last drain. */
|
|
184
|
-
drainRouterNotes(): string[];
|
|
185
|
-
/** live delivery of the same notes (retry "retrying in 4 s (2/4)", give-up, chain advance): buffered ones replay
|
|
186
|
-
* first, then each new note arrives as it happens — the TUI shows a notice while the backoff waits, cmdRun prints a
|
|
187
|
-
* stderr line. With a listener registered, drainRouterNotes has nothing left to drain. */
|
|
188
|
-
onRouterNote(fn: (note: string) => void): void;
|
|
189
|
-
/** port #11: shadow-git checkpoints for a session (lazy; null when git is absent
|
|
190
|
-
* or ROVECODE_NO_CHECKPOINTS=1). Snapshots land automatically after mutating tools. */
|
|
191
|
-
checkpointsFor(sessionId: string): Promise<Checkpoints | null>;
|
|
192
|
-
/** port #11: point checkpoint entryId capture at the ACTIVE session store after
|
|
193
|
-
* a TUI session switch (pairs with setBlockStore). */
|
|
194
|
-
setSessionStore(s: SessionStore): void;
|
|
195
|
-
/** port #27: executor rung selected by .rovecode/sandbox.json / ROVECODE_SANDBOX (+ probe) */
|
|
196
|
-
sandbox: SandboxState;
|
|
197
|
-
/** the workspace roots — canonical cwd + the `--add-dir` dirs (deduplicated, nested ones dropped with a note in
|
|
198
|
-
* `roots.notes`, surfaced as plugin warnings); their ladder rules are already inside buildCfg's permissionRules */
|
|
199
|
-
roots: WorkspaceRoots;
|
|
200
|
-
/** port #33: bind (or unbind with undefined) the interactive asker behind the ask_user
|
|
201
|
-
* tool — the TUI hands in its question overlay; headless surfaces (run/serve/acp) never
|
|
202
|
-
* call this, so the tool fails closed for them. setBlockStore idiom: registered once,
|
|
203
|
-
* dependency rebound late. */
|
|
204
|
-
setAskUser(fn: AskFn | undefined): void;
|
|
205
|
-
/** port #26: the ONE steering queue for this runtime's runs — hand it to agentLoop
|
|
206
|
-
* (in place of a fresh SteeringQueue) so background-task completion notes reach the
|
|
207
|
-
* parent's next turn. Surfaces with their own queue: rt.tasks.attach(queue). */
|
|
208
|
-
steering: SteeringQueue;
|
|
209
|
-
/** port #26: background subagents (bounded FIFO jobs over orchestrator runChild) */
|
|
210
|
-
tasks: TaskManager;
|
|
211
|
-
/** port #55: this runtime's background shell jobs — a surface calls dispose() at teardown so no
|
|
212
|
-
* backgrounded command outlives the session that started it */
|
|
213
|
-
bashJobs: BashJobManager;
|
|
214
|
-
/** the custom subagent definitions loaded at boot (core/agents.ts: `.rovecode/agents/*.md` + `<home>/agents/*.md`);
|
|
215
|
-
* `warnings` were also sent through the plugin-warning channel as `agents: …` lines */
|
|
216
|
-
agents: DiscoveredAgents;
|
|
217
|
-
/** port #29: typed hook set — `.rovecode/hooks.{ts,js}` (+ `~/.rovecode`, ROVECODE_HOME) loaded at construction
|
|
218
|
-
* (background import; every run() waits for it, so no surface can race the load), session_open
|
|
219
|
-
* fired once loaded. Thread into LoopDeps.hooks; attach more sets programmatically via hooks.add()
|
|
220
|
-
* (port #39 OTel); surfaces call hooks.close() at teardown → session_close once. Load + runtime
|
|
221
|
-
* notes (import failure, wrong version, timeout, throw) land in hooks.warnings / onWarning(). */
|
|
222
|
-
hooks: HookRunner;
|
|
223
|
-
/** plugins (src/plugins, docs/plugins.md): discovered synchronously at construction — manifests and
|
|
224
|
-
* statuses only, no code run — so an ACTIVE plugin's skills, commands and MCP servers wire in with
|
|
225
|
-
* their file-based twins; the entry modules (tools + hooks) import in the background and `ready`
|
|
226
|
-
* joins them (bootRuntime awaits it, so no surface's first prompt can miss a plugin tool). A
|
|
227
|
-
* PROJECT plugin stays `untrusted` — nothing of it loads — until `rovecode plugin trust`. */
|
|
228
|
-
plugins: RuntimePlugins;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
export interface RuntimePlugins {
|
|
232
|
-
/** every plugin found at construction, with its status (`rovecode plugin list` shows the same) */
|
|
233
|
-
found: readonly DiscoveredPlugin[];
|
|
234
|
-
/** settles when the active entry modules are imported and their tools/hooks attached */
|
|
235
|
-
ready: Promise<void>;
|
|
236
|
-
/** the plugins as activated — empty until `ready` */
|
|
237
|
-
readonly loaded: readonly LoadedPlugin[];
|
|
238
|
-
/** discovery + activation notes; a listener gets the buffered ones first (hooks.onWarning idiom) */
|
|
239
|
-
readonly warnings: readonly string[];
|
|
240
|
-
onWarning(fn: (note: string) => void): void;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// lazy module helpers — loaded on first use so boot pays nothing for features that are not configured
|
|
244
|
-
type OtelMod = typeof import("../telemetry/otel.ts");
|
|
245
|
-
let _otelMod: OtelMod | null = null;
|
|
246
|
-
function lazyOtel(): OtelMod | null {
|
|
247
|
-
if (!process.env.ROVECODE_OTEL_ENDPOINT) return null;
|
|
248
|
-
if (_otelMod === null) {
|
|
249
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
250
|
-
_otelMod = require("../telemetry/otel.ts") as OtelMod;
|
|
251
|
-
}
|
|
252
|
-
return _otelMod;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
type McpClientMod = typeof import("../mcp/client.ts");
|
|
256
|
-
type McpToolsMod = typeof import("../mcp/tools.ts");
|
|
257
|
-
let _mcpMod: { client: McpClientMod; tools: McpToolsMod } | null = null;
|
|
258
|
-
function lazyMcp(): { client: McpClientMod; tools: McpToolsMod } {
|
|
259
|
-
if (_mcpMod === null) {
|
|
260
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
261
|
-
_mcpMod = {
|
|
262
|
-
client: require("../mcp/client.ts") as McpClientMod,
|
|
263
|
-
tools: require("../mcp/tools.ts") as McpToolsMod,
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
return _mcpMod;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
export function createRuntime(opts: RuntimeOptions = {}): Runtime {
|
|
270
|
-
const cwd = opts.cwd ?? process.cwd();
|
|
271
|
-
// port #27: sandbox rung selection comes FIRST — a config error throws before any
|
|
272
|
-
// side effect (no sessions dir, no MCP children). The probe (wsl/docker trial
|
|
273
|
-
// spawn, 500ms cap) runs concurrently with the rest of construction; its verdict
|
|
274
|
-
// is `sandbox.ready`. The seam records the DESIRED rung synchronously (#10 G7),
|
|
275
|
-
// so an unmet wsl/docker desire is loud at bashTool, never a silent direct.
|
|
276
|
-
const sandboxCfg = loadSandboxConfig(cwd, process.env, { home: rovecodeHome() }); // an UNTRUSTED project sandbox.json contributes nothing (core/trust.ts); its note is pushed once pluginWarn exists
|
|
277
|
-
const ready = configureExecutor(sandboxCfg.rung, {
|
|
278
|
-
runner: opts.spawnRunner, dockerImage: sandboxCfg.dockerImage, platform: opts.platform,
|
|
279
|
-
}).then(() => undefined, (e: unknown) => { throw unavailableRungError(sandboxCfg, e); });
|
|
280
|
-
void ready.catch(() => {}); // verdict is read via bootRuntime / await — never an unhandled rejection
|
|
281
|
-
const sandbox: SandboxState = { ...sandboxCfg, ready };
|
|
282
|
-
// the `--add-dir` roots (core/workspace.ts): canonical, deduplicated (nested / in-cwd values dropped with a note); a
|
|
283
|
-
// value that is not a directory or is a filesystem root throws WorkspaceRootError HERE — before the sessions mkdir
|
|
284
|
-
// and every other side effect, the same startup-error class as a bad sandbox config (exit 2 on every surface)
|
|
285
|
-
const roots = new WorkspaceRoots(cwd, resolveRoots(cwd, opts.addDirs ?? []));
|
|
286
|
-
const sessionsDir = join(cwd, ".rovecode", "sessions");
|
|
287
|
-
mkdirSync(sessionsDir, { recursive: true });
|
|
288
|
-
const sessionId = opts.sessionId ?? randomUUID();
|
|
289
|
-
const store = new SessionStore(sessionsDir, sessionId);
|
|
290
|
-
|
|
291
|
-
// port #11: shadow-git checkpoints — one repo per session under .rovecode/checkpoints/,
|
|
292
|
-
// snapshot after every SUCCESSFUL mutating tool call (kinds write/execute). Lazy
|
|
293
|
-
// per-session init; git absent or ROVECODE_NO_CHECKPOINTS=1 → silently off.
|
|
294
|
-
const cpBySession = new Map<string, Promise<Checkpoints | null>>();
|
|
295
|
-
const checkpointsFor = (sid: string): Promise<Checkpoints | null> => {
|
|
296
|
-
if (process.env.ROVECODE_NO_CHECKPOINTS === "1") return Promise.resolve(null);
|
|
297
|
-
let p = cpBySession.get(sid);
|
|
298
|
-
if (!p) { p = Checkpoints.init({ workspace: cwd, sessionId: sid }).then((c) => c, () => null); cpBySession.set(sid, p); }
|
|
299
|
-
return p;
|
|
300
|
-
};
|
|
301
|
-
let activeStore = store; // TUI session switches re-point it via setSessionStore
|
|
302
|
-
const withCheckpoint = (t: Tool): Tool => !MUTATING_KINDS.has(t.kind) ? t : {
|
|
303
|
-
...t,
|
|
304
|
-
execute: async (a, c) => {
|
|
305
|
-
const out = await t.execute(a, c);
|
|
306
|
-
if (out.ok) {
|
|
307
|
-
const cp = await checkpointsFor(c.sessionId);
|
|
308
|
-
// conversation-restore anchor: last USER message (HIGH-2 — the tail entry is the
|
|
309
|
-
// assistant message that ISSUED this very tool call; branching there strands its
|
|
310
|
-
// tool_calls with no replies -> provider 400), when the active store IS this session
|
|
311
|
-
const entryId = activeStore.id === c.sessionId ? anchorEntryId(activeStore.messages()) : undefined;
|
|
312
|
-
await cp?.snapshot(t.schema.name, entryId).catch(() => {});
|
|
313
|
-
}
|
|
314
|
-
return out;
|
|
315
|
-
},
|
|
316
|
-
};
|
|
317
|
-
|
|
318
|
-
const registry = new ToolRegistry();
|
|
319
|
-
// plugins (src/plugins, docs/plugins.md): discovery runs no code — manifests, statuses, digests — so
|
|
320
|
-
// it can happen here, synchronously, and the ACTIVE plugins' declarative halves (skills dirs,
|
|
321
|
-
// command dirs, MCP servers) join their file-based twins below as if they had been in .rovecode/.
|
|
322
|
-
// The entry modules import after construction (see the activation block after hooks.open).
|
|
323
|
-
const pluginHome = rovecodeHome();
|
|
324
|
-
const pluginState = loadPluginState(pluginHome); // one trust store for project plugins AND project MCP files
|
|
325
|
-
const pluginsFound = discoverPlugins(cwd, { home: pluginHome, state: pluginState });
|
|
326
|
-
// ONE startup-note channel, not a plugin channel. It began as the plugins' own and kept the name
|
|
327
|
-
// while lsp, workspace roots, agents and mcp all started pushing into it — and every consumer was
|
|
328
|
-
// prefixing "plugins: " on the way out, so an agent file's problem reached stderr as
|
|
329
|
-
// "plugins: agents: explore.md: skipped …". Three sessions reported it in one night and none owned
|
|
330
|
-
// it. Now each note carries its OWN subsystem word at the push site and consumers print it as-is.
|
|
331
|
-
const pluginWarnings: string[] = pluginsFound.warnings.map((w) => `plugins: ${w}`);
|
|
332
|
-
const pluginListeners: ((note: string) => void)[] = [];
|
|
333
|
-
const pluginWarn = (note: string): void => { pluginWarnings.push(note); for (const l of pluginListeners) l(note); };
|
|
334
|
-
const activePlugins = pluginsFound.plugins.filter((p) => p.status === "active"); // untrusted/disabled/broken contribute NOTHING
|
|
335
|
-
// port #13: successful edits/writes get LSP diagnostics appended within a ≤2s
|
|
336
|
-
// settle window (typescript-language-server on PATH; absent → silently off).
|
|
337
|
-
const lspNote = (p: string): Promise<string> => lspGateNote(p, roots.rootOf(p) ?? cwd); // an edit under an added root is diagnosed against THAT root (its own tsconfig)
|
|
338
|
-
// "absent → silently off" is right for the tool result and wrong for the person: say once, at boot, that the
|
|
339
|
-
// diagnostics loop is not running here (TypeScript projects only — coding/lsp.ts lspAvailabilityNote)
|
|
340
|
-
const lspGap = lspAvailabilityNote(cwd);
|
|
341
|
-
if (lspGap !== null) pluginWarn(lspGap);
|
|
342
|
-
// the roots, said once on the same channel (TUI warn note / headless stderr): dropped --add-dir values, and the limit
|
|
343
|
-
// that comes with roots — checkpoints snapshot the cwd only (core/workspace.ts checkpointNote; also the doctor row)
|
|
344
|
-
for (const n of roots.notes) pluginWarn(n);
|
|
345
|
-
if (roots.dirs.length > 0) pluginWarn(roots.checkpointNote());
|
|
346
|
-
registry.register(readTool, withCheckpoint(withLspGate(editTool, lspNote)), withCheckpoint(withLspGate(writeTool, lspNote)), withCheckpoint(bashTool));
|
|
347
|
-
registry.register(globTool, grepTool, lsTool); // port #22: bounded, gitignore-aware search/list (kind read → file.read auto-allow; non-mutating, no checkpoint)
|
|
348
|
-
registry.register(webFetchTool, createWebSearchTool({ apiKey: process.env.EXA_API_KEY })); // port #31 web_fetch + #56 web_search: kind network → net.fetch, PROMPT by default (rule below); SSRF-guarded, bounded; no checkpoint. The key is read HERE and nowhere else — the module never touches process.env, and every URL it echoes back has its query string dropped
|
|
349
|
-
// a plugin's skills dir joins the store as one more root: a user plugin's as global, a project
|
|
350
|
-
// plugin's as project (the same precedence its own files would have had)
|
|
351
|
-
const skillStore = new SkillStore(cwd, { extraDirs: activePlugins.flatMap((p) => (p.skillsDir ? [{ dir: p.skillsDir, scope: p.scope === "project" ? "project" as const : "global" as const }] : [])) });
|
|
352
|
-
skillStore.scan();
|
|
353
|
-
registry.register(...createSkillTools(skillStore));
|
|
354
|
-
// memory is PROJECT-scoped, not per-session (memory/scope.ts): MEMORY under <cwd>/.rovecode/memory, USER under the
|
|
355
|
-
// user home. Until 2026-09-07 both lived in <sessions>/<id>/memory, so every fact died with the session id and the
|
|
356
|
-
// USER block — preferences meant to follow the person across projects — was written inside the repository. The
|
|
357
|
-
// one-time copy-forward of a legacy store happens here, before the boot snapshot, so a resumed session keeps its text.
|
|
358
|
-
const scopedMemory = openScopedMemory({ cwd, sessionsDir, sessionId, home: pluginHome });
|
|
359
|
-
let blocks = scopedMemory.blocks;
|
|
360
|
-
if (scopedMemory.note !== null) for (const line of scopedMemory.note.split("\n")) pluginWarn(line);
|
|
361
|
-
registry.register(memoryEditTool(blocks));
|
|
362
|
-
// port #18: persistent eval cell — registered ONLY when ROVECODE_EVAL_CELL=1
|
|
363
|
-
const evalCell = createEvalCellTool();
|
|
364
|
-
if (evalCell) registry.register(withCheckpoint(evalCell));
|
|
365
|
-
// port #17: cross-session recall (kind read → file.read gate; pure transcript search)
|
|
366
|
-
registry.register(recallTool(sessionsDir));
|
|
367
|
-
registry.register(...todoTools(sessionsDir)); // port #32: per-session todo list at <session>/todos.json (todo_write kind memory → memory.write allow; todo_read kind read)
|
|
368
|
-
|
|
369
|
-
/** LoopDeps.planReminder: while a plan is open, re-send it as the LAST thing in the request. Read
|
|
370
|
-
* from disk every turn, so the agent's own todo_write (and a /todos edit, and a second surface on
|
|
371
|
-
* the same session) are all reflected. Skipped when the model just wrote the list — it is already
|
|
372
|
-
* looking at that tool result, and a copy right under it teaches nothing. */
|
|
373
|
-
const planReminderFor = (history: readonly Message[]): string | null => {
|
|
374
|
-
const last = history.at(-1);
|
|
375
|
-
if (last?.parts.some((p) => p.kind === "tool_result" && p.output.startsWith("todos:"))) return null;
|
|
376
|
-
const dir = join(sessionsDir, store.id);
|
|
377
|
-
try { return planReminder(loadTodos(dir).items); } catch { return null; } // a missing/corrupt list never blocks a turn
|
|
378
|
-
};
|
|
379
|
-
// providers: ONE live registry per runtime (providers/registry.ts) — providers.json (user + project),
|
|
380
|
-
// stored credentials and env, re-read when a source file changes. provider_list is kind read (always
|
|
381
|
-
// allowed); provider_edit is kind custom → tool.provider_edit, PROMPT under the gated rules below
|
|
382
|
-
const providers = new ProviderRegistry(cwd);
|
|
383
|
-
// custom subagent definitions (core/agents.ts): scanned ONCE at boot; every file that cannot be honoured is refused
|
|
384
|
-
// here with a line naming it (never at run time inside a child) — a reserved name, a bad mode, a model on another
|
|
385
|
-
// provider than the one this session streams over, a YAML list or an empty `tools:`. The lines ride the plugin-
|
|
386
|
-
// warning channel like the LSP and roots notes (TUI warn note / headless stderr).
|
|
387
|
-
const agents = discoverAgents(cwd, { validateModel: modelProblemFor(providers.defaultConfig()?.id ?? null) });
|
|
388
|
-
for (const w of agents.warnings) pluginWarn(`agents: ${w}`);
|
|
389
|
-
const agentRowsList = agentRows(agents);
|
|
390
|
-
registry.register(providerListTool(providers), providerEditTool(providers));
|
|
391
|
-
// design protocol (design/rules.ts): design_audit is kind read (free, never prompts -- checking your
|
|
392
|
-
// own work must cost nothing); design_direction is kind custom -> tool.design_direction, PROMPT under
|
|
393
|
-
// the gated rules, because it records the project's design identity and is asked once per project.
|
|
394
|
-
registry.register(designAuditTool(), designDirectionTool());
|
|
395
|
-
// port #33: ask_user on EVERY surface (kind read → auto-runs under gated/plan rules); only an
|
|
396
|
-
// interactive surface binds an asker via setAskUser — unbound, the tool fails closed
|
|
397
|
-
let askUser: AskFn | undefined;
|
|
398
|
-
registry.register(askUserTool(() => askUser));
|
|
399
|
-
const guard = new ToolGuard(); // port #4: loop signatures + duplicate-result stubs
|
|
400
|
-
// port #29: hooks v2 — the runner exists synchronously (createRuntime stays sync); open() imports
|
|
401
|
-
// .rovecode/hooks.{ts,js} (+ user scope) in the background and fires session_open; run() awaits it
|
|
402
|
-
const hooks = new HookRunner({ cwd, sessionId });
|
|
403
|
-
void hooks.open(cwd);
|
|
404
|
-
|
|
405
|
-
// plugin entry modules: imported in the background like the hook files, joined by bootRuntime through
|
|
406
|
-
// plugins.ready. Tools land on THIS registry (the same permission path as every built-in: kind → action)
|
|
407
|
-
// and are refused loudly when the name is taken — a plugin cannot replace `bash`. Hooks join the runner
|
|
408
|
-
// after the hook files (they miss session_open; pre_run is theirs). Activation failures are notes.
|
|
409
|
-
let loadedPlugins: LoadedPlugin[] = [];
|
|
410
|
-
const pluginsReady = activatePlugins(pluginsFound.plugins, { cwd, home: pluginHome }).then((a) => {
|
|
411
|
-
for (const w of a.warnings) pluginWarn(`plugins: ${w}`);
|
|
412
|
-
const taken = new Set(registry.list().map((t) => t.schema.name));
|
|
413
|
-
for (const p of a.plugins) {
|
|
414
|
-
if (p.status !== "active") continue;
|
|
415
|
-
for (const t of p.tools) {
|
|
416
|
-
if (taken.has(t.schema.name)) { pluginWarn(`plugins: plugin ${p.name}: tool "${t.schema.name}" is already registered — refused (a plugin cannot replace a built-in or another plugin's tool)`); continue; }
|
|
417
|
-
taken.add(t.schema.name);
|
|
418
|
-
registry.register(t);
|
|
419
|
-
}
|
|
420
|
-
if (p.hooks) hooks.add(p.hooks, `plugin:${p.name}`);
|
|
421
|
-
}
|
|
422
|
-
loadedPlugins = a.plugins;
|
|
423
|
-
}, (e: unknown) => { pluginWarn(`plugins: activation failed — ${e instanceof Error ? e.message : String(e)}`); });
|
|
424
|
-
const plugins: RuntimePlugins = {
|
|
425
|
-
found: pluginsFound.plugins,
|
|
426
|
-
ready: pluginsReady,
|
|
427
|
-
get loaded() { return loadedPlugins; },
|
|
428
|
-
warnings: pluginWarnings,
|
|
429
|
-
onWarning(fn) { for (const w of pluginWarnings) fn(w); pluginListeners.push(fn); },
|
|
430
|
-
};
|
|
431
|
-
|
|
432
|
-
// port #3: MCP servers from .rovecode/mcp.json + harvested .mcp.json; two lazy tools only.
|
|
433
|
-
// connect() is fire-and-forget; tool executes await first-connect before dispatching.
|
|
434
|
-
// plugin MCP servers first, then the project's own files — .rovecode/mcp.json keeps the last word on a name
|
|
435
|
-
const mcpByName = new Map<string, McpServerConfig>();
|
|
436
|
-
for (const p of activePlugins) for (const c of p.mcp) {
|
|
437
|
-
if (mcpByName.has(c.name)) { pluginWarn(`plugins: plugin ${p.name}: MCP server "${c.name}" is also declared by another plugin — first kept`); continue; }
|
|
438
|
-
mcpByName.set(c.name, c);
|
|
439
|
-
}
|
|
440
|
-
// the user file (~/.rovecode/mcp.json — where `rovecode mcp add` writes) is the lowest of the three layers.
|
|
441
|
-
// Project files (.rovecode/mcp.json, .mcp.json) pass the same trust gate as project plugins (mcp/trust.ts):
|
|
442
|
-
// unapproved on this machine → nothing of theirs loads, one `mcp: …` note names the file and the command.
|
|
443
|
-
const mcpWarnings: string[] = [];
|
|
444
|
-
const hasMcpFiles = existsSync(join(pluginHome, "mcp.json"))
|
|
445
|
-
|| existsSync(join(cwd, ".rovecode", "mcp.json"))
|
|
446
|
-
|| existsSync(join(cwd, ".mcp.json"));
|
|
447
|
-
if (hasMcpFiles) {
|
|
448
|
-
for (const c of lazyMcp().client.loadMcpConfig(cwd, mcpWarnings, { home: pluginHome, trusted: trustedPredicate(pluginState) })) { if (mcpByName.has(c.name)) pluginWarn(`mcp: mcp.json server "${c.name}" overrides a plugin's entry of the same name`); mcpByName.set(c.name, c); }
|
|
449
|
-
}
|
|
450
|
-
for (const w of mcpWarnings) pluginWarn(`mcp: ${w}`);
|
|
451
|
-
// the trust gate's other two boot lines (core/trust.ts): a project settings.json whose command-bearing keys were
|
|
452
|
-
// dropped, and a project sandbox.json that was ignored — hooks.ts says its own through hooks.warnings
|
|
453
|
-
for (const n of settingsTrustNotes(cwd, pluginHome)) pluginWarn(`trust: ${n}`);
|
|
454
|
-
if (sandboxCfg.note !== undefined) pluginWarn(`trust: ${sandboxCfg.note}`);
|
|
455
|
-
// Reading the three files is its own function because it happens twice: once here, and again whenever
|
|
456
|
-
// something installs a server and wants it usable without a restart (reloadMcp below).
|
|
457
|
-
// `warn` is an OUT parameter rather than a swallowed local: a server that is skipped — an unset ${NAME},
|
|
458
|
-
// an unfilled <placeholder>, an untrusted project file — is the one thing the human most needs to hear
|
|
459
|
-
// after installing something, and dropping the reason here is what made a failed install look like a
|
|
460
|
-
// successful one that simply did nothing.
|
|
461
|
-
const readMcpConfigs = (warn: string[] = []): McpServerConfig[] => {
|
|
462
|
-
const byName = new Map<string, McpServerConfig>();
|
|
463
|
-
for (const p of activePlugins) for (const c of p.mcp) if (!byName.has(c.name)) byName.set(c.name, c);
|
|
464
|
-
const files = existsSync(join(pluginHome, "mcp.json"))
|
|
465
|
-
|| existsSync(join(cwd, ".rovecode", "mcp.json"))
|
|
466
|
-
|| existsSync(join(cwd, ".mcp.json"));
|
|
467
|
-
// the trust gate is re-read too: a project file approved since boot starts counting from now on,
|
|
468
|
-
// and one whose contents changed is untrusted again, exactly as it would be on a fresh start
|
|
469
|
-
if (files) for (const c of lazyMcp().client.loadMcpConfig(cwd, warn, { home: pluginHome, trusted: trustedPredicate(loadPluginState(pluginHome)) })) byName.set(c.name, c);
|
|
470
|
-
return [...byName.values()];
|
|
471
|
-
};
|
|
472
|
-
|
|
473
|
-
const mcpConfigs = [...mcpByName.values()];
|
|
474
|
-
let mcp: McpManager | null = null;
|
|
475
|
-
let mcpReady: Promise<void> = Promise.resolve();
|
|
476
|
-
/** register mcp_list/mcp_call once; they dispatch by server name, so a new server needs no new tool */
|
|
477
|
-
const registerMcpTools = (manager: McpManager): void => {
|
|
478
|
-
for (const t of lazyMcp().tools.createMcpTools(manager)) {
|
|
479
|
-
registry.register({ ...t, execute: async (a, c) => { await mcpReady; return t.execute(a, c); } });
|
|
480
|
-
}
|
|
481
|
-
};
|
|
482
|
-
if (mcpConfigs.length > 0) {
|
|
483
|
-
const mcpMod = lazyMcp();
|
|
484
|
-
const manager = new mcpMod.client.McpManager(mcpConfigs);
|
|
485
|
-
mcp = manager;
|
|
486
|
-
// The connect starts on the NEXT turn of the event loop, not here: runTui is synchronous from
|
|
487
|
-
// createRuntime through renderer.start(), which paints the first frame, and connect()'s first
|
|
488
|
-
// step is loading the MCP SDK (~200 ms of module evaluation). Kicked off inline that load ran on
|
|
489
|
-
// the first microtask — still ahead of the first paint. A zero timer puts it behind it. Nothing
|
|
490
|
-
// at boot awaits mcpReady; mcp_list/mcp_call do (registerMcpTools), so a tool call may wait —
|
|
491
|
-
// the terminal must not. Measured: two npx servers, createRuntime 254 ms → 30 ms.
|
|
492
|
-
// The result is not discarded: a server the loader accepted but that never answers (a command that does not
|
|
493
|
-
// exist, a package npx cannot resolve) is counted on the startup card as configured, and until it is named
|
|
494
|
-
// here the only sign of it was a tool call that found nothing. Same channel as the loader's own "skipped"
|
|
495
|
-
// lines (pluginWarn → the TUI's warn notes, stderr headless), same shape as reloadMcp's `failed`.
|
|
496
|
-
mcpReady = new Promise<void>((resolve) => {
|
|
497
|
-
setTimeout(() => {
|
|
498
|
-
manager.connect().then(
|
|
499
|
-
(r) => { for (const f of r.failed) pluginWarn(`mcp: server "${f.name}" did not connect — ${f.error}`); resolve(); },
|
|
500
|
-
() => resolve(),
|
|
501
|
-
);
|
|
502
|
-
}, 0);
|
|
503
|
-
});
|
|
504
|
-
registerMcpTools(manager);
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
/** Pick up mcp.json changes in a live session — what `market install mcp:<id>` calls so the answer is
|
|
508
|
-
* "ready" instead of "restart rovecode".
|
|
509
|
-
*
|
|
510
|
-
* Two cases, and the second is the one that matters most: when a session started with NO servers there
|
|
511
|
-
* is no manager and `mcp_list`/`mcp_call` were never registered, so installing your first server used
|
|
512
|
-
* to leave the model with no way to reach it at all. Here the manager is created and the two tools are
|
|
513
|
-
* registered at that moment. Servers already connected are left alone (see McpManager.sync). */
|
|
514
|
-
const reloadMcp = async (): Promise<{ added: string[]; removed: string[]; failed: { name: string; error: string }[]; skipped: string[] }> => {
|
|
515
|
-
const skipped: string[] = [];
|
|
516
|
-
const configs = readMcpConfigs(skipped);
|
|
517
|
-
if (mcp === null) {
|
|
518
|
-
if (configs.length === 0) return { added: [], removed: [], failed: [], skipped };
|
|
519
|
-
const manager = new (lazyMcp().client.McpManager)(configs);
|
|
520
|
-
mcp = manager;
|
|
521
|
-
registerMcpTools(manager);
|
|
522
|
-
const r = await manager.connect();
|
|
523
|
-
mcpReady = Promise.resolve();
|
|
524
|
-
return { added: configs.map((c) => c.name), removed: [], failed: r.failed, skipped };
|
|
525
|
-
}
|
|
526
|
-
const { added, removed } = await mcp.sync(configs);
|
|
527
|
-
const r = added.length > 0 ? await mcp.connect() : { failed: [] as { name: string; error: string }[] };
|
|
528
|
-
return { added, removed, failed: r.failed, skipped };
|
|
529
|
-
};
|
|
530
|
-
|
|
531
|
-
// boot-time view of the default provider — only the router's role table is pinned to it; every
|
|
532
|
-
// other reader goes through the LIVE getters on the returned Runtime (provider / defaultModel)
|
|
533
|
-
const bootDefault = providers.defaultRef();
|
|
534
|
-
// port #14: role router + fallback chains (env ROVECODE_MODEL_DEFAULT/SMOL/PLAN/COMMIT/TASK,
|
|
535
|
-
// comma-separated provider/model chains). The registry's dispatcher routes every candidate to
|
|
536
|
-
// ITS OWN provider's endpoint, so a cross-provider chain really fails over.
|
|
537
|
-
const routerNotes: string[] = [];
|
|
538
|
-
const routerListeners: ((note: string) => void)[] = [];
|
|
539
|
-
/** a note goes to every live listener at once (the TUI's notice, cmdRun's stderr line); with no listener it
|
|
540
|
-
* waits in the buffer for drainRouterNotes — so a retry is visible WHILE it waits, not after the run */
|
|
541
|
-
const pushRouterNote = (note: string): void => { if (routerListeners.length === 0) routerNotes.push(note); else for (const fn of routerListeners) fn(note); };
|
|
542
|
-
const fallbackRef: ModelRef = { provider: bootDefault?.provider ?? "mock", model: bootDefault?.model || "default" };
|
|
543
|
-
const router = createRouter({
|
|
544
|
-
roles: roleTableFromEnv(fallbackRef),
|
|
545
|
-
// MED-3: an explicitly configured default chain is the fallback pool even for models
|
|
546
|
-
// outside it (requested model prepended as primary); the synthesized single-model
|
|
547
|
-
// default (env unset) must NOT capture loose models — hence the env gate.
|
|
548
|
-
looseFallback: (process.env.ROVECODE_MODEL_DEFAULT ?? "").trim().length > 0,
|
|
549
|
-
onNote: (n) => pushRouterNote(
|
|
550
|
-
`router: ${n.chain} ${n.from.provider}/${n.from.model} → ${n.to ? `${n.to.provider}/${n.to.model}` : "chain exhausted"} (${n.reason})`),
|
|
551
|
-
});
|
|
552
|
-
// port #7: provider streams get the non-native tool-call parser (strict-gated passthrough
|
|
553
|
-
// for native turns); injected test streams stay untouched. Kill switch: ROVECODE_NO_TOOL_MIDDLEWARE=1
|
|
554
|
-
// port #14: the router wraps OUTERMOST (chain advance re-drives the whole turn).
|
|
555
|
-
// port #23: same-model retry sits INSIDE the router — backoff retries exhaust on candidate N
|
|
556
|
-
// before the chain advances (ROVECODE_RETRY_MAX / ROVECODE_RETRY_BASE_MS; providers/retry.ts header).
|
|
557
|
-
// The raw stream is the registry's DISPATCHER: it resolves model.provider on every call against the
|
|
558
|
-
// live snapshot, so the wrapped stream below never needs rebuilding when providers change. With
|
|
559
|
-
// nothing configured it yields a `config:` error turn (non-retryable) — surfaces consult
|
|
560
|
-
// noProviderReason() first and cmdRun keeps its mock fallback.
|
|
561
|
-
const rawStream = providers.stream();
|
|
562
|
-
const middlewared = process.env.ROVECODE_NO_TOOL_MIDDLEWARE !== "1" ? withToolCallParsing(rawStream) : rawStream;
|
|
563
|
-
// retries and give-ups surface as notes in the human's words (providers/retry.ts describeRetry/describeGiveUp):
|
|
564
|
-
// "anthropic: overloaded — retrying in 4 s (2/4)" — live to onRouterNote listeners, else buffered for drainRouterNotes
|
|
565
|
-
const stream = opts.stream !== undefined ? opts.stream : router.wrap(withRetry(middlewared, { ...retryOptionsFromEnv(), onRetry: (n) => pushRouterNote(describeRetry(n)), onGiveUp: (n) => pushRouterNote(describeGiveUp(n)) }));
|
|
566
|
-
const catalog = new ModelCatalog(); // offline models.dev snapshot (port #6)
|
|
567
|
-
// port #39: OTel span export rides the hook seam — attached ONLY when ROVECODE_OTEL_ENDPOINT is set (off:
|
|
568
|
-
// nothing constructed, no on_event tap → zero cost); export failures surface through hooks.warnings
|
|
569
|
-
const otelMod = lazyOtel();
|
|
570
|
-
// held so the TaskManager below can be handed to it: lane spans need the manager, which does not
|
|
571
|
-
// exist yet here, and the hook set is the only thing that knows how to open and close them
|
|
572
|
-
let otelHooks: ReturnType<OtelMod["createOtelHooks"]> | null = null;
|
|
573
|
-
if (otelMod) {
|
|
574
|
-
const otel = otelMod.otelOptionsFromEnv();
|
|
575
|
-
if (otel) {
|
|
576
|
-
// #47 × #39: isAdapterId is what makes a task a LANE on the dashboard. The default is "nothing is
|
|
577
|
-
// a lane" on purpose — with a looser predicate every ordinary subagent would be exported as an
|
|
578
|
-
// external CLI, which is a lie a dashboard cannot be talked out of once it has drawn it.
|
|
579
|
-
otelHooks = otelMod.createOtelHooks({ ...otel, pricing: catalog, messages: () => activeStore.messages(), isLane: isAdapterId });
|
|
580
|
-
hooks.add(otelHooks, "otel");
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
// port #8: harvest AGENTS.md / CLAUDE.md / .cursor / copilot instructions
|
|
585
|
-
// cwd-UPWARD (OMP ancestor-walk pattern) ONCE per runtime — a snapshot, like
|
|
586
|
-
// BlockStore, so the system prompt stays byte-stable for prompt caching
|
|
587
|
-
// (MED-4). It reaches the model as an ADR-007 "config" chunk (priority 70)
|
|
588
|
-
// via buildDef → assembleContext, never a second prompt-assembly path.
|
|
589
|
-
const projectContext = loadProjectContext(cwd);
|
|
590
|
-
const configText = `# Project context${projectContext.text}`;
|
|
591
|
-
const configChunk: ContextChunk | null = projectContext.text
|
|
592
|
-
? { name: "config", text: configText, priority: 70, tokens: estimateTokens(configText) }
|
|
593
|
-
: null;
|
|
594
|
-
|
|
595
|
-
// port #12: repo-map fills the reserved ADR-007 chunk (priority 80, set by the
|
|
596
|
-
// module: system>files>repo-map>skills/config>history). Built LAZILY at the
|
|
597
|
-
// first buildDef() and memoized — createRuntime stays sync-cheap (`rovecode tools`,
|
|
598
|
-
// ACP session setup pay nothing) and per-file tags persist under
|
|
599
|
-
// .rovecode/cache/repomap.json, so warm launches skip extraction. Frozen after
|
|
600
|
-
// the first build, like config, for prompt-cache stability. ROVECODE_NO_REPOMAP=1
|
|
601
|
-
// disables; budget override via ROVECODE_REPOMAP_TOKENS (default 1024, aider's).
|
|
602
|
-
let extraChunksMemo: ContextChunk[] | null = null;
|
|
603
|
-
const extraChunks = (): ContextChunk[] => {
|
|
604
|
-
if (extraChunksMemo !== null) return extraChunksMemo;
|
|
605
|
-
let repoMapChunk: ContextChunk | null = null;
|
|
606
|
-
if (process.env.ROVECODE_NO_REPOMAP !== "1") {
|
|
607
|
-
const budget = Number(process.env.ROVECODE_REPOMAP_TOKENS ?? "") || 1024;
|
|
608
|
-
try {
|
|
609
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
610
|
-
const { buildRepoMapChunk } = require("../coding/repomap.ts") as { buildRepoMapChunk: typeof BuildRepoMapChunkFn };
|
|
611
|
-
repoMapChunk = buildRepoMapChunk(cwd, budget);
|
|
612
|
-
} catch { repoMapChunk = null; }
|
|
613
|
-
}
|
|
614
|
-
extraChunksMemo = [configChunk, repoMapChunk].filter((c): c is ContextChunk => c !== null);
|
|
615
|
-
return extraChunksMemo;
|
|
616
|
-
};
|
|
617
|
-
// warmRepoMap (Runtime interface): the map builds behind the TUI's first frame — COOPERATIVELY
|
|
618
|
-
// (repomap.ts buildRepoMapChunkAsync: awaited enumeration, sliced extraction, a yield per budget-search
|
|
619
|
-
// step), so the frame loop keeps painting, reading keys and following resizes while it runs. The
|
|
620
|
-
// synchronous build here used to block the event loop for 10 s in a 650-file repo with a cold tags cache
|
|
621
|
-
// (a home directory: far longer) and the sextant sat frozen at its first reveal step. While the build is
|
|
622
|
-
// pending, buildDef hands out the cheap chunks only — "the map is not in this prompt", never "the request
|
|
623
|
-
// waits" — and does NOT memoize, so the warm build's result is the one that freezes the map.
|
|
624
|
-
let warmPending = false;
|
|
625
|
-
const warmAbort = new AbortController();
|
|
626
|
-
let warmTask: Promise<void> | null = null;
|
|
627
|
-
const stopRepoMapWarmup = async (): Promise<void> => { warmAbort.abort(); await warmTask; };
|
|
628
|
-
const warmRepoMap = (): void => {
|
|
629
|
-
if (extraChunksMemo !== null || warmPending || warmAbort.signal.aborted) return;
|
|
630
|
-
warmPending = true;
|
|
631
|
-
const settle = (repoMapChunk: ContextChunk | null): void => {
|
|
632
|
-
warmPending = false;
|
|
633
|
-
if (extraChunksMemo === null) extraChunksMemo = [configChunk, repoMapChunk].filter((c): c is ContextChunk => c !== null);
|
|
634
|
-
};
|
|
635
|
-
if (process.env.ROVECODE_NO_REPOMAP === "1" || !shouldWarmRepoMap(cwd)) { settle(null); return; }
|
|
636
|
-
const budget = Number(process.env.ROVECODE_REPOMAP_TOKENS ?? "") || 1024;
|
|
637
|
-
let build: Promise<ContextChunk | null>;
|
|
638
|
-
try {
|
|
639
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
640
|
-
const { buildRepoMapChunkAsync } = require("../coding/repomap.ts") as { buildRepoMapChunkAsync: typeof BuildRepoMapChunkAsyncFn };
|
|
641
|
-
build = buildRepoMapChunkAsync(cwd, budget, { signal: warmAbort.signal });
|
|
642
|
-
} catch { build = Promise.resolve(null); }
|
|
643
|
-
// A background failure is a cache miss, not a reason to freeze the next interactive submit.
|
|
644
|
-
// Headless runtimes that never warm still build synchronously in extraChunks().
|
|
645
|
-
warmTask = build.then(settle, () => settle(null));
|
|
646
|
-
};
|
|
647
|
-
const chunksForDef = (): ContextChunk[] => extraChunksMemo ?? (warmPending ? [configChunk].filter((c): c is ContextChunk => c !== null) : extraChunks());
|
|
648
|
-
|
|
649
|
-
const systemPrompt = (cwdOverride?: string): string => {
|
|
650
|
-
const skillsIndex = buildSkillsIndex(skillStore);
|
|
651
|
-
const memoryIndex = blocks.renderForPrompt();
|
|
652
|
-
return `You are Rovecode, an interactive coding agent in ${cwdOverride ?? cwd}. Use read/edit/write/bash tools. Edits require line hashes from read output. Match the length of an answer to the task: a line for a lookup, the full thing for a plan, a design or a review — never pad, never truncate work that was asked for.${skillsIndex ? "\n\n# Skills\n" + skillsIndex : ""}${memoryIndex ? "\n\n# Memory\n" + memoryIndex : ""}${roots.promptLine()}`;
|
|
653
|
-
};
|
|
654
|
-
|
|
655
|
-
// ROVECODE_EFFORT is the boot default; /effort and --effort move it at runtime
|
|
656
|
-
// default "auto": no thinking field on the wire, the provider's own default stands (Claude 5: adaptive,
|
|
657
|
-
// high). The old default "off" sent an explicit `thinking: disabled` and switched off the reasoning the
|
|
658
|
-
// model does by itself — most of "we are not getting the model's real performance" (Berkay, 2026-09-04).
|
|
659
|
-
let effort: ThinkingEffort = parseEffort(process.env.ROVECODE_EFFORT) ?? "auto";
|
|
660
|
-
|
|
661
|
-
/** models the catalog knows CANNOT do native tool calling get the senpi-format prompt block (port #7); unknown models
|
|
662
|
-
* attempt native first. Force: ROVECODE_TOOL_MIDDLEWARE=1. One rule for the root def and for a child's (runChild). */
|
|
663
|
-
const nonNativeFor = (m: ModelRef): boolean => catalog.lookup(m.provider, m.model)?.supportsTools === false || process.env.ROVECODE_TOOL_MIDDLEWARE === "1";
|
|
664
|
-
const buildDef = (model: ModelRef, opts: { cwd?: string; agent?: CustomAgent; child?: boolean } = {}): AgentDefinition => {
|
|
665
|
-
if (model.effort === undefined) model = { ...model, effort }; // one dial, every surface
|
|
666
|
-
if (!opts.agent) activeModel = model; // port #26: children run the model of the run that started them — a custom agent's own model never re-points it
|
|
667
|
-
// models the catalog knows CANNOT do native tool calling get the senpi-format
|
|
668
|
-
// prompt block (port #7); unknown models attempt native first. Force: ROVECODE_TOOL_MIDDLEWARE=1
|
|
669
|
-
const info = catalog.lookup(model.provider, model.model);
|
|
670
|
-
// the answer's room comes from the catalog (models.dev maxOutput), capped: the old flat 4096 default
|
|
671
|
-
// truncated long outputs — a whole page of UI, a long plan — mid-sentence, and the model was blamed
|
|
672
|
-
if (model.maxTokens === undefined && info?.maxOutput) model = { ...model, maxTokens: Math.min(info.maxOutput, MAX_OUTPUT_CAP) };
|
|
673
|
-
// the catalog's word on a reasoning mode rides with the ref: thinking.ts sends no dial to a model listed without one
|
|
674
|
-
if (model.reasoning === undefined && info?.supportsReasoning !== undefined) model = { ...model, reasoning: info.supportsReasoning };
|
|
675
|
-
const nonNative = nonNativeFor(model);
|
|
676
|
-
// model profile (providers/profiles.ts): a per-family behavioral section rides AFTER the base prompt
|
|
677
|
-
// and its indexes and BEFORE the tool-calling block — one string for the whole run (prompt cache);
|
|
678
|
-
// .rovecode/profiles/<id>.md replaces the built-in text, ROVECODE_PROFILE=off drops it
|
|
679
|
-
const profile = profileFor(model);
|
|
680
|
-
// A model WITHOUT a profile gets the working agreement too (profile-glm53.ts names no model or vendor
|
|
681
|
-
// — it is the harness's contract: read before edit, verify before "done", parallel calls, scope).
|
|
682
|
-
// Until now only GLM received it and Claude/GPT got one sentence; that asymmetry cost quality.
|
|
683
|
-
const section = profile === null ? GLM_53_AGENT_CONTRACT : profilePromptSection(profile, cwd); // "" = an empty override file: no section, no separator
|
|
684
|
-
// design protocol (design/rules.ts): the ban list plus this project's recorded direction, read
|
|
685
|
-
// once per run start like the profile so the system prefix stays byte-stable (prompt cache).
|
|
686
|
-
// ROVECODE_DESIGN=off drops it for a run that has nothing to do with interfaces.
|
|
687
|
-
const design = process.env.ROVECODE_DESIGN === "off" ? "" : designPromptSection(opts.cwd ?? cwd);
|
|
688
|
-
// a custom definition's body IS its system prompt (core/agents.ts); an empty body inherits the runtime's whole prompt
|
|
689
|
-
const base = opts.agent?.body || [systemPrompt(opts.cwd), section, design].filter((p) => p.length > 0).join("\n\n");
|
|
690
|
-
return {
|
|
691
|
-
name: opts.agent?.name ?? "main", model, tools: opts.agent ? [...opts.agent.tools] : ["*"], ...(opts.agent?.mode !== undefined ? { mode: opts.agent.mode } : {}),
|
|
692
|
-
// a child def stays bare: orchestrator runChild appends the block from the CHILD's own registry, never the root's list
|
|
693
|
-
systemPrompt: nonNative && !opts.child
|
|
694
|
-
? `${base}\n\n# Tool calling\n${toolPromptBlock(registry.list().map((t) => t.schema))}`
|
|
695
|
-
: base,
|
|
696
|
-
...(chunksForDef().length > 0 ? { contextChunks: chunksForDef() } : {}),
|
|
697
|
-
};
|
|
698
|
-
};
|
|
699
|
-
/** the workspace as a rule resource: every path resource is absolute (tools.ts describeResource),
|
|
700
|
-
* so `<cwd><sep>*` is "inside this repository" and nothing else — a sibling directory whose name
|
|
701
|
-
* merely STARTS with the cwd (…/repo-backup) does not match, because the separator is in the glob. */
|
|
702
|
-
const insideCwd = `${cwd.replace(/[\/]$/, "")}${sep}*`;
|
|
703
|
-
|
|
704
|
-
// run ceilings (cli/run-limits.ts): a surface's explicit limits, else the environment, else 60 turns and no
|
|
705
|
-
// clock — the TUI and serve/acp get the env knobs for free, `rovecode run` adds its flags + a 20-minute default
|
|
706
|
-
let runLimits: RunLimits = {};
|
|
707
|
-
const buildCfg = (permission: PermissionLevel | boolean, approval?: ApprovalFn): RunConfig => {
|
|
708
|
-
const level: PermissionLevel = permission === true ? "auto" : permission === false ? "ask" : permission;
|
|
709
|
-
const yolo = level === "auto";
|
|
710
|
-
const maxSeconds = runLimits.maxSeconds ?? positiveInt(process.env.ROVECODE_MAX_SECONDS);
|
|
711
|
-
const maxCostUsd = runLimits.maxCostUsd ?? positiveUsd(process.env.ROVECODE_MAX_COST);
|
|
712
|
-
// the same arithmetic /cost and the headless result use (tui/cost.ts, cli/output.ts): the catalog's price
|
|
713
|
-
// for the model that served the turn, tiered by the prompt the turn actually carried; no price → undefined
|
|
714
|
-
const priceUsd = (usage: TokenUsage, origin: ModelRef): number | undefined => {
|
|
715
|
-
const info = catalog.lookup(origin.provider, origin.model);
|
|
716
|
-
if (!info?.pricing) return undefined;
|
|
717
|
-
const n = { input: usage.input, output: usage.output, cacheRead: usage.cacheRead ?? 0, cacheWrite: usage.cacheWrite ?? 0 };
|
|
718
|
-
return costUsdTiered(n, ratesFor(info, n.input + n.cacheRead + n.cacheWrite));
|
|
719
|
-
};
|
|
720
|
-
// the finish check (core/loop.ts "done" exit): on by default, ROVECODE_FINISH_CHECK=0 is the escape hatch;
|
|
721
|
-
// the todo state is read from disk at the exit so a list the model wrote this run is what gets reported
|
|
722
|
-
const todoState = (): { open: number; total: number } | null => {
|
|
723
|
-
try {
|
|
724
|
-
const items = loadTodos(join(sessionsDir, activeStore.id)).items;
|
|
725
|
-
return items.length === 0 ? null : { open: items.filter((i) => i.status !== "completed").length, total: items.length };
|
|
726
|
-
} catch { return null; }
|
|
727
|
-
};
|
|
728
|
-
// the verify gate (core/verify-gate.ts): OFF unless ROVECODE_VERIFY=1. Measured 2026-09-06 (nimbus-6f): the
|
|
729
|
-
// projects actually edited with rovecode have no check command at all (23 of 23 edits), and where one exists it
|
|
730
|
-
// costs 3 s to 170 s — a gate that spends three minutes on a six-line CSS edit gets turned off and never comes
|
|
731
|
-
// back. The default lives in this ONE comparison so flipping it later is editing this line. The check is
|
|
732
|
-
// resolved per run (a key added to settings mid-session counts next run); ROVECODE_VERIFY_TIMEOUT=<seconds>
|
|
733
|
-
// bounds one command, default 120.
|
|
734
|
-
const verifyOn = process.env.ROVECODE_VERIFY === "1";
|
|
735
|
-
const verifyTimeoutMs = (positiveInt(process.env.ROVECODE_VERIFY_TIMEOUT) ?? VERIFY_TIMEOUT_MS / 1000) * 1000;
|
|
736
|
-
const verifyGate = (): RunConfig["verify"] => {
|
|
737
|
-
const resolution = (opts.verifyResolver ?? resolveForGate)(cwd);
|
|
738
|
-
return {
|
|
739
|
-
resolution, timeoutMs: verifyTimeoutMs,
|
|
740
|
-
run: async (signal) => { const o = await runVerify(resolution ?? { commands: [] }, cwd, { signal, timeoutMs: verifyTimeoutMs }); noteVerifyCost(cwd, o); return o; },
|
|
741
|
-
};
|
|
742
|
-
};
|
|
743
|
-
return (activeCfg = {
|
|
744
|
-
maxTurns: runLimits.maxTurns ?? positiveInt(process.env.ROVECODE_MAX_TURNS) ?? 60,
|
|
745
|
-
...(verifyOn ? { verify: verifyGate() } : {}),
|
|
746
|
-
...(maxSeconds !== undefined ? { maxSeconds } : {}),
|
|
747
|
-
...(maxCostUsd !== undefined ? { maxCostUsd, priceUsd } : {}),
|
|
748
|
-
finishCheck: process.env.ROVECODE_FINISH_CHECK !== "0",
|
|
749
|
-
todoState,
|
|
750
|
-
// the history budget follows the model's window: a flat 200k spent a fifth of a 1M window and
|
|
751
|
-
// overflowed a 128k one. ROVECODE_CONTEXT_BUDGET overrides; an unknown window keeps the old default.
|
|
752
|
-
contextBudgetTokens: (() => {
|
|
753
|
-
const ref = activeModel ?? fallbackRef;
|
|
754
|
-
const cur = catalog.lookup(ref.provider, ref.model);
|
|
755
|
-
return contextBudgetFor({
|
|
756
|
-
...(cur?.contextWindow !== undefined ? { window: cur.contextWindow } : {}),
|
|
757
|
-
...(cur?.maxOutput !== undefined ? { maxOutput: cur.maxOutput } : {}),
|
|
758
|
-
...(positiveInt(process.env.ROVECODE_CONTEXT_BUDGET) !== undefined ? { override: positiveInt(process.env.ROVECODE_CONTEXT_BUDGET) as number } : {}),
|
|
759
|
-
// our estimator is not this model's tokenizer, so a budget taken at face value compacts too
|
|
760
|
-
// late and the request that follows is rejected. charScale, not scale: what this budget is
|
|
761
|
-
// compared against is estimateTokens (chars/4) in loop.ts and compaction.ts, never countTokens.
|
|
762
|
-
scale: tokenScaleFor(ref).charScale,
|
|
763
|
-
});
|
|
764
|
-
})(),
|
|
765
|
-
compactionThreshold: 0.8,
|
|
766
|
-
compactionStrategy: parseCompactionStrategy(process.env.ROVECODE_COMPACTION) ?? "head-summarize", // port #25: ROVECODE_COMPACTION=head-summarize|keep-window|provider-native
|
|
767
|
-
parallelTools: true,
|
|
768
|
-
permissionRules: yolo
|
|
769
|
-
? [{ action: "*", resource: "*", effect: "allow" }]
|
|
770
|
-
: [
|
|
771
|
-
{ action: "file.read", resource: "*", effect: "allow" },
|
|
772
|
-
// the workspace boundary (core/workspace.ts; tools.ts dispatch step 2a): a file tool aimed OUTSIDE the cwd
|
|
773
|
-
// asks once per directory, on a card that names the path, the cwd, the roots and the remedy. Inside the cwd
|
|
774
|
-
// this rule is never consulted, so a person who stays in their project cannot tell it exists. The
|
|
775
|
-
// `--add-dir` roots follow as allows, so nothing under a root reaches the card.
|
|
776
|
-
{ action: EXTERNAL_ACTION, resource: "*", effect: "prompt" },
|
|
777
|
-
...roots.rules(),
|
|
778
|
-
{ action: "memory.write", resource: "*", effect: "allow" },
|
|
779
|
-
{ action: "tool.skill_view", resource: "*", effect: "allow" },
|
|
780
|
-
{ action: "tool.skills_list", resource: "*", effect: "allow" },
|
|
781
|
-
// mcp_list is kind:"read" → action "file.read"; the allow above already covers it
|
|
782
|
-
{ action: "file.write", resource: "*", effect: "prompt" },
|
|
783
|
-
{ action: "shell.exec", resource: "*", effect: "prompt" },
|
|
784
|
-
{ action: "spawn", resource: "*", effect: "prompt" },
|
|
785
|
-
{ action: "tool.mcp_call", resource: "*", effect: "prompt" },
|
|
786
|
-
// provider_edit (tools/provider.ts) rewrites providers.json / the default model: ask first.
|
|
787
|
-
// provider_list is kind read → covered by the file.read allow above
|
|
788
|
-
{ action: "tool.provider_edit", resource: "*", effect: "prompt" },
|
|
789
|
-
// design_direction set writes .rovecode/design.json -- the once-per-project design identity,
|
|
790
|
-
// and that ONE card is the point: it is where the human sees what is recorded for them.
|
|
791
|
-
// `get` only reads that file, so it is allowed (last match wins): making the human approve
|
|
792
|
-
// the read costs an interruption before every UI task AND trains them to allow the card
|
|
793
|
-
// reflexively, which is the card that matters. The two modes are told apart by the tool's
|
|
794
|
-
// own resource() (tools/design.ts), not by the tool name.
|
|
795
|
-
{ action: "tool.design_direction", resource: "*", effect: "prompt" },
|
|
796
|
-
{ action: "tool.design_direction", resource: "get", effect: "allow" },
|
|
797
|
-
// port #31: resource = canonical host (lowercased, no trailing dot), so `allow
|
|
798
|
-
// net.fetch <host>` auto-runs THAT host only; web_fetch stops at a redirect to
|
|
799
|
-
// another host and reports it, so the new host gets its own decision here
|
|
800
|
-
{ action: "net.fetch", resource: "*", effect: "prompt" },
|
|
801
|
-
// accept-edits: writing INSIDE the workspace stops asking. Placed last of the file.write
|
|
802
|
-
// rules because the last match wins (tools.ts evaluatePermissions) — a write outside the
|
|
803
|
-
// repo still hits the prompt rule above, and any deny rule a surface appends still wins.
|
|
804
|
-
...(level === "accept-edits" ? [{ action: "file.write", resource: insideCwd, effect: "allow" as const }, ...roots.acceptEditsRules()] : []), // a root is inside the workspace here too
|
|
805
|
-
],
|
|
806
|
-
// port #9: execpolicy refines the PROMPT branch only (allow-listed argv →
|
|
807
|
-
// "once", forbidden → deny before any human); rules above stay the outer gate.
|
|
808
|
-
// The wrap is UNCONDITIONAL on gated configs (R2 #9 LOW-3): headless surfaces
|
|
809
|
-
// (run/serve pass no approver) get allow-list auto-run + forbidden hard-stop,
|
|
810
|
-
// and prompt-classified argv fails closed instead of "no approver connected".
|
|
811
|
-
// yolo stays approver-free — its allow-all rules never reach the prompt branch.
|
|
812
|
-
// port #29: the approval hook sits INSIDE the wrap, where the human would — a
|
|
813
|
-
// forbidden argv never reaches a hook, an allow-listed one never asks (hooks.ts).
|
|
814
|
-
// port #47: outermost, laneApprover turns a `task start` naming an external CLI lane into the card
|
|
815
|
-
// text BEFORE it starts — the flags that lane will run with, named where a person can still say no
|
|
816
|
-
approval: yolo ? undefined : laneApprover(execPolicyApprover(hooks.approver(approval)), opts.lanes?.env),
|
|
817
|
-
});
|
|
818
|
-
};
|
|
819
|
-
|
|
820
|
-
// port #26: background subagents. Children run through orchestrator runChild (the ONE
|
|
821
|
-
// agentLoop) with deps resolved at each start: the def/config of the run that STARTED
|
|
822
|
-
// the task (buildDef/buildCfg record them — every surface calls both right before its
|
|
823
|
-
// agentLoop, so a child inherits its parent's model and policy; deriveChildRules turns
|
|
824
|
-
// prompt→deny). ONE SteeringQueue per runtime: surfaces hand it to agentLoop and
|
|
825
|
-
// completion notes land in the parent's next turn (loop.ts:136). Children get the core
|
|
826
|
-
// coding/search/skill tools (no MCP/memory/eval-cell/checkpoints in v1) plus nested
|
|
827
|
-
// `task` (kind spawn, bound to THEIR depth + steering queue, so the depth cap governs
|
|
828
|
-
// nesting) and `task_status` (kind read: a child collects ITS children's results without
|
|
829
|
-
// a prompt nobody could answer — MED-2 split, tools/task.ts header).
|
|
830
|
-
let activeCfg: RunConfig | null = null;
|
|
831
|
-
let activeModel: ModelRef | null = null;
|
|
832
|
-
const steering = new SteeringQueue();
|
|
833
|
-
const childRegistry = (def: AgentDefinition, childCwd: string, child?: ChildContext): ToolRegistry => {
|
|
834
|
-
const reg = new ToolRegistry();
|
|
835
|
-
const table: Tool[] = [readTool, editTool, writeTool, bashTool, globTool, grepTool, lsTool, ...createSkillTools(skillStore), recallTool(sessionsDir)];
|
|
836
|
-
// a nested `task` reports THIS registry's names (lazily — it is filled below) so a grandchild is clamped to them
|
|
837
|
-
// `parentDir` is this child's own working tree: an external lane it starts must build its worktree
|
|
838
|
-
// there and merge back there, or an isolated child's lane writes into the user's live tree past the
|
|
839
|
-
// isolation the child itself is held to. ChildContext.dir is the same value runChild passed as cwd.
|
|
840
|
-
if (child) table.push(createTaskTool(tasks, { parentDepth: child.depth, notify: child.steering, caller: child.taskId, owner: child.signal, agents: agentRowsList, parentDir: child.dir ?? childCwd, parentTools: () => new Set(reg.list().map((t) => t.schema.name)) }), createTaskStatusTool(tasks, { caller: child.taskId }));
|
|
841
|
-
// the definition's allow-list is a FILTER over this table ∩ the STARTING registry's names — never wider (main's "*" keeps
|
|
842
|
-
// the table). The starting registry is the root's for a root start, the spawning CHILD's for a nested one
|
|
843
|
-
// (ChildContext.parentTools): a restricted agent that has `task` cannot hand `main` — or any definition — a wider set than its own.
|
|
844
|
-
reg.register(...restrictTools(table, def.tools, child?.parentTools ?? new Set(registry.list().map((t) => t.schema.name))));
|
|
845
|
-
return reg;
|
|
846
|
-
};
|
|
847
|
-
/** the definitions a task may start, built beside `main` from the SAME buildDef at each start: a definition inherits the
|
|
848
|
-
* starting run's prompt/model unless it names its own ("provider/model" = the router grammar; a bare id runs on the
|
|
849
|
-
* starting run's provider). `child: true` leaves them without the root's tool block (runChild renders the child's). */
|
|
850
|
-
const defsFor = (parent: ModelRef): Map<string, AgentDefinition> => {
|
|
851
|
-
const defs = new Map<string, AgentDefinition>([["main", buildDef(parent, { child: true })]]);
|
|
852
|
-
for (const a of agents.agents) defs.set(a.name, buildDef(a.model !== undefined ? parseModelRef(a.model, parent.provider) : parent, { agent: a, child: true }));
|
|
853
|
-
return defs;
|
|
854
|
-
};
|
|
855
|
-
const tasks = new TaskManager({
|
|
856
|
-
deps: (): ChildRunnerDeps | null => stream ? {
|
|
857
|
-
defs: defsFor(activeModel ?? fallbackRef),
|
|
858
|
-
stream, registryFactory: childRegistry, rootDir: cwd, sessionsDir,
|
|
859
|
-
toolPrompt: (m, tools) => (nonNativeFor(m) ? toolPromptBlock(tools) : ""), // the child's own restricted set, never the root's list
|
|
860
|
-
baseConfig: activeCfg ?? buildCfg(false),
|
|
861
|
-
hooks, // port #29: children run under the runtime's hooks (a veto cannot be dodged by delegation)
|
|
862
|
-
} : null,
|
|
863
|
-
lanes: opts.lanes, // port #47: external CLI lanes are jobs of this same manager
|
|
864
|
-
});
|
|
865
|
-
tasks.attach(steering);
|
|
866
|
-
// #39: finished external lanes become their own spans (open on "running", closed on the terminal
|
|
867
|
-
// transition). Off unless OTel is configured — with no endpoint nothing above was constructed.
|
|
868
|
-
otelHooks?.observeTasks(tasks);
|
|
869
|
-
// port #28: built-in reflection set (core/reflection.ts) — a failed edit/write (or an LSP-diagnosed one) nudges the model once via steering, capped per run (ROVECODE_REFLECTION_MAX); ROVECODE_REFLECTION=0 disables.
|
|
870
|
-
// owns: the ACTIVE session's runs only — a task child (own store id, same hooks) must neither nudge nor sweep this queue (#26 MED-A)
|
|
871
|
-
if (reflectionEnabled()) hooks.add(createReflectionHooks({ steering, owns: (c) => c.sessionId === activeStore.id }), "reflection");
|
|
872
|
-
registry.register(createTaskTool(tasks, { parentDepth: 0, agents: agentRowsList }), createTaskStatusTool(tasks)); // task: kind spawn → gated rules prompt once per start, yolo allows; task_status: kind read → allowed everywhere
|
|
873
|
-
|
|
874
|
-
// port #55: ONE background-shell-job manager per runtime, installed where bashTool can reach it
|
|
875
|
-
// without every call site threading it. Notes land in the SAME steering queue task completions use,
|
|
876
|
-
// so a finished job is drained before the next model call instead of interrupting a turn. It is
|
|
877
|
-
// installed process-wide because bashTool is a module-level constant shared by every surface; a
|
|
878
|
-
// second runtime replaces it, which is what a second runtime should mean.
|
|
879
|
-
const bashJobs = new BashJobManager({ notify: steering });
|
|
880
|
-
installJobManager(bashJobs);
|
|
881
|
-
registry.register(...bashJobTools); // bash_list / bash_output (kind read) · bash_kill (kind execute)
|
|
882
|
-
|
|
883
|
-
return {
|
|
884
|
-
cwd, sessionId, store, registry, skillStore,
|
|
885
|
-
get blockStore() { return blocks; },
|
|
886
|
-
setBlockStore(b: BlockStore) { blocks = b; registry.register(memoryEditTool(b)); },
|
|
887
|
-
guard, planReminder: planReminderFor, get mcp() { return mcp; }, reloadMcp, projectContext, router, roots, agents,
|
|
888
|
-
get effort() { return effort; },
|
|
889
|
-
setEffort(e: ThinkingEffort) { effort = e; },
|
|
890
|
-
setRunLimits(l: RunLimits) { runLimits = l; },
|
|
891
|
-
drainRouterNotes: () => routerNotes.splice(0),
|
|
892
|
-
onRouterNote(fn) { for (const n of routerNotes.splice(0)) fn(n); routerListeners.push(fn); },
|
|
893
|
-
checkpointsFor,
|
|
894
|
-
setSessionStore(s: SessionStore) { activeStore = s; },
|
|
895
|
-
sandbox,
|
|
896
|
-
setAskUser(fn: AskFn | undefined) { askUser = fn; },
|
|
897
|
-
hooks,
|
|
898
|
-
plugins,
|
|
899
|
-
providers,
|
|
900
|
-
get provider() { return providers.defaultConfig(); },
|
|
901
|
-
stream,
|
|
902
|
-
get defaultModel() { return providers.defaultRef()?.model ?? process.env.ROVECODE_MODEL ?? ""; },
|
|
903
|
-
noProviderReason: () => (opts.stream === undefined && !providers.configured() ? NO_PROVIDER_HINT : null),
|
|
904
|
-
systemPrompt,
|
|
905
|
-
buildDef, buildCfg, warmRepoMap, stopRepoMapWarmup,
|
|
906
|
-
steering, tasks, bashJobs,
|
|
907
|
-
};
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
/** The one sentence every surface shows when nothing is configured (Runtime.noProviderReason). */
|
|
911
|
-
export const NO_PROVIDER_HINT = noModelHint("cli");
|
|
912
|
-
|
|
913
|
-
/** port #27: construct + await the sandbox probe — the boot path for every
|
|
914
|
-
* entrypoint that must fail CLEANLY at startup (run/repl/tui/acp/serve).
|
|
915
|
-
* createRuntime stays sync (its many callers/tests build synchronously); this
|
|
916
|
-
* is the one place the async verdict is joined. Throws SandboxConfigError
|
|
917
|
-
* (one actionable line) for a bad config or a configured rung the machine
|
|
918
|
-
* cannot provide; MCP children spawned during construction are reaped first,
|
|
919
|
-
* so a failed boot leaves no processes behind. */
|
|
920
|
-
export async function bootRuntime(opts: RuntimeOptions = {}): Promise<Runtime> {
|
|
921
|
-
const rt = createRuntime(opts);
|
|
922
|
-
try {
|
|
923
|
-
await rt.sandbox.ready;
|
|
924
|
-
await rt.hooks.ready; // port #29: hook files + session_open joined here too (notes recorded before the first prompt)
|
|
925
|
-
await rt.plugins.ready; // plugin entry modules imported, their tools and hooks attached — before any surface's first prompt
|
|
926
|
-
} catch (e) {
|
|
927
|
-
await rt.mcp?.close().catch(() => {});
|
|
928
|
-
throw e;
|
|
929
|
-
}
|
|
930
|
-
return rt;
|
|
931
|
-
}
|