rovecode 0.4.0-beta.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -69
- package/THIRD_PARTY_NOTICES.md +0 -44
- package/bin/rovecode.ts +21 -0
- package/package.json +16 -37
- package/src/account/keys.ts +97 -0
- package/src/account/login.ts +158 -0
- package/src/account/provision.ts +47 -0
- package/src/account/store.ts +63 -0
- package/src/acp/server.ts +373 -0
- package/src/cli/account-cmd.ts +116 -0
- package/src/cli/connect.ts +244 -0
- package/src/cli/context-cmd.ts +199 -0
- package/src/cli/dispatch.ts +109 -0
- package/src/cli/doctor.ts +324 -0
- package/src/cli/export.ts +278 -0
- package/src/cli/help.ts +240 -0
- package/src/cli/is-tui-invocation.ts +8 -0
- package/src/cli/main.ts +599 -0
- package/src/cli/market-cmd.ts +658 -0
- package/src/cli/mcp-market-cmd.ts +299 -0
- package/src/cli/output.ts +382 -0
- package/src/cli/repl.ts +172 -0
- package/src/cli/resume.ts +32 -0
- package/src/cli/run-limits.ts +78 -0
- package/src/cli/runtime.ts +792 -0
- package/src/cli/setup.ts +187 -0
- package/src/cli/update-cmd.ts +78 -0
- package/src/cli/workflow-cmd.ts +100 -0
- package/src/coding/checkpoints.ts +270 -0
- package/src/coding/diff.ts +136 -0
- package/src/coding/files.ts +339 -0
- package/src/coding/hashline.ts +319 -0
- package/src/coding/lsp.ts +406 -0
- package/src/coding/repomap-cache.ts +99 -0
- package/src/coding/repomap-files.ts +110 -0
- package/src/coding/repomap.ts +392 -0
- package/src/core/compaction.ts +399 -0
- package/src/core/config.ts +289 -0
- package/src/core/context-report.ts +228 -0
- package/src/core/context.ts +60 -0
- package/src/core/count-remote.ts +107 -0
- package/src/core/execpolicy-rules.ts +196 -0
- package/src/core/execpolicy.ts +385 -0
- package/src/core/executor.ts +397 -0
- package/src/core/guardrails.ts +400 -0
- package/src/core/hooks.ts +398 -0
- package/src/core/images.ts +230 -0
- package/src/core/intro.ts +236 -0
- package/src/core/loop.ts +621 -0
- package/src/core/modes.ts +372 -0
- package/src/core/orchestrator.ts +207 -0
- package/src/core/reflection.ts +165 -0
- package/src/core/sandbox-config.ts +167 -0
- package/src/core/session-images.ts +73 -0
- package/src/core/session.ts +398 -0
- package/src/core/settings.ts +98 -0
- package/src/core/stuck-detector.ts +273 -0
- package/src/core/tasks.ts +374 -0
- package/src/core/token-scale.ts +108 -0
- package/src/core/tool-output-budget.ts +166 -0
- package/src/core/tools.ts +288 -0
- package/src/core/types.ts +330 -0
- package/src/core/update-check.ts +171 -0
- package/src/core/update.ts +158 -0
- package/src/core/usage.ts +204 -0
- package/src/core/validate.ts +121 -0
- package/src/core/verify-gate.ts +159 -0
- package/src/core/verify.ts +237 -0
- package/src/core/voice.ts +158 -0
- package/src/core/win-job.ts +183 -0
- package/src/design/audit.ts +797 -0
- package/src/design/direction.ts +190 -0
- package/src/design/rules.ts +157 -0
- package/src/eval/bench.ts +150 -0
- package/src/eval/gauntlet-runner.ts +218 -0
- package/src/eval/gauntlet.ts +226 -0
- package/src/eval/grader.ts +186 -0
- package/src/eval/record.ts +202 -0
- package/src/eval/redact.ts +141 -0
- package/src/eval/replay.ts +147 -0
- package/src/eval/trajectory.ts +373 -0
- package/src/index.ts +17 -0
- package/src/market/catalogs/mcp-docs.json +111 -0
- package/src/market/catalogs/plugins.json +111 -0
- package/src/market/catalogs/skills.json +478 -0
- package/src/market/clone.ts +72 -0
- package/src/market/context-cost.ts +121 -0
- package/src/market/digest.ts +106 -0
- package/src/market/index.ts +22 -0
- package/src/market/install.ts +578 -0
- package/src/market/manifest.ts +187 -0
- package/src/market/prereq.ts +145 -0
- package/src/market/registry.ts +363 -0
- package/src/market/resolve.ts +111 -0
- package/src/market/types.ts +236 -0
- package/src/market/validate.ts +227 -0
- package/src/mcp/client.ts +431 -0
- package/src/mcp/config.ts +239 -0
- package/src/mcp/local-package.ts +211 -0
- package/src/mcp/market-catalog.ts +84 -0
- package/src/mcp/market-install.ts +289 -0
- package/src/mcp/market.ts +0 -0
- package/src/mcp/tools.ts +131 -0
- package/src/mcp/trust.ts +49 -0
- package/src/memory/blocks.ts +175 -0
- package/src/memory/recall.ts +355 -0
- package/src/memory/store.ts +105 -0
- package/src/memory/tools.ts +99 -0
- package/src/plugins/cli.ts +123 -0
- package/src/plugins/discover.ts +108 -0
- package/src/plugins/index.ts +50 -0
- package/src/plugins/init.ts +140 -0
- package/src/plugins/install.ts +184 -0
- package/src/plugins/load.ts +149 -0
- package/src/plugins/manifest.ts +106 -0
- package/src/plugins/state.ts +83 -0
- package/src/providers/auth.ts +293 -0
- package/src/providers/cache.ts +223 -0
- package/src/providers/catalog-local.ts +160 -0
- package/src/providers/catalog.ts +408 -0
- package/src/providers/middleware-context.ts +86 -0
- package/src/providers/middleware.ts +373 -0
- package/src/providers/profile-glm53.ts +111 -0
- package/src/providers/profile-sonnet5-persona.ts +65 -0
- package/src/providers/profile-sonnet5-voice.ts +23 -0
- package/src/providers/profiles.ts +156 -0
- package/src/providers/provider-config.ts +311 -0
- package/src/providers/registry.ts +302 -0
- package/src/providers/response-validation.ts +80 -0
- package/src/providers/retry.ts +234 -0
- package/src/providers/router.ts +294 -0
- package/src/providers/sse.ts +26 -0
- package/src/providers/stream-errors.ts +117 -0
- package/src/providers/stream.ts +569 -0
- package/src/providers/thinking.ts +189 -0
- package/src/providers/wire-messages.ts +129 -0
- package/src/sdk/client.ts +225 -0
- package/src/sdk/index.ts +3 -0
- package/src/server/dashboard.ts +144 -0
- package/src/server/http.ts +343 -0
- package/src/server/openapi.ts +246 -0
- package/src/sextant/card-hits.ts +102 -0
- package/src/sextant/card-keys.ts +55 -0
- package/src/sextant/context-source.ts +157 -0
- package/src/sextant/draw-agents.ts +273 -0
- package/src/sextant/draw-code.ts +388 -0
- package/src/sextant/draw-context.ts +222 -0
- package/src/sextant/draw-frame.ts +164 -0
- package/src/sextant/draw-market.ts +573 -0
- package/src/sextant/draw-messages.ts +386 -0
- package/src/sextant/draw-pet.ts +230 -0
- package/src/sextant/draw-plan.ts +159 -0
- package/src/sextant/draw-tabs.ts +85 -0
- package/src/sextant/draw-util.ts +65 -0
- package/src/sextant/engine.ts +230 -0
- package/src/sextant/frame-hits.ts +25 -0
- package/src/sextant/frame.ts +101 -0
- package/src/sextant/git-status.ts +197 -0
- package/src/sextant/grid.ts +59 -0
- package/src/sextant/input.ts +119 -0
- package/src/sextant/keys.ts +488 -0
- package/src/sextant/layout.ts +86 -0
- package/src/sextant/local-commands.ts +156 -0
- package/src/sextant/market-source.ts +287 -0
- package/src/sextant/mentions.ts +141 -0
- package/src/sextant/message-hits.ts +26 -0
- package/src/sextant/model.ts +387 -0
- package/src/sextant/overlays.ts +451 -0
- package/src/sextant/panel-hits.ts +38 -0
- package/src/sextant/pet.ts +399 -0
- package/src/sextant/screen.ts +324 -0
- package/src/sextant/scroll-hits.ts +66 -0
- package/src/sextant/scrollbar.ts +82 -0
- package/src/sextant/selection.ts +123 -0
- package/src/sextant/sextant-bridge.ts +174 -0
- package/src/sextant/sextant-cards.ts +142 -0
- package/src/sextant/sextant-diff-base.ts +63 -0
- package/src/sextant/sextant-files.ts +154 -0
- package/src/sextant/sextant-frame-loop.ts +314 -0
- package/src/sextant/sextant-renderer.ts +478 -0
- package/src/sextant/sextant-repo.ts +131 -0
- package/src/sextant/theme.ts +66 -0
- package/src/sextant/tool-rows.ts +189 -0
- package/src/sextant/types.ts +473 -0
- package/src/skills/index.ts +306 -0
- package/src/skills/tools.ts +69 -0
- package/src/skills/versioned.ts +227 -0
- package/src/telemetry/otel.ts +353 -0
- package/src/telemetry/otlp.ts +68 -0
- package/src/tools/ask-user.ts +156 -0
- package/src/tools/design.ts +151 -0
- package/src/tools/evalcell.ts +338 -0
- package/src/tools/html-text.ts +139 -0
- package/src/tools/provider.ts +149 -0
- package/src/tools/task.ts +216 -0
- package/src/tools/todo.ts +320 -0
- package/src/tools/webfetch.ts +331 -0
- package/src/tui/app.ts +608 -0
- package/src/tui/attach.ts +127 -0
- package/src/tui/checkpoints-cmd.ts +70 -0
- package/src/tui/clipboard-image.ts +81 -0
- package/src/tui/commands.ts +277 -0
- package/src/tui/cost.ts +108 -0
- package/src/tui/info-cmd.ts +144 -0
- package/src/tui/mcp-cmd.ts +128 -0
- package/src/tui/modes-cmd.ts +45 -0
- package/src/tui/overlays.ts +97 -0
- package/src/tui/pi-renderer.ts +424 -0
- package/src/tui/providers-cmd.ts +366 -0
- package/src/tui/renderer.ts +101 -0
- package/src/tui/replay-marker.ts +29 -0
- package/src/tui/session-cmd.ts +146 -0
- package/src/tui/sextant-attach.ts +68 -0
- package/src/tui/sextant-io.ts +184 -0
- package/src/tui/sextant-smoke.ts +110 -0
- package/src/tui/smoke.ts +72 -0
- package/src/tui/theme.ts +59 -0
- package/src/tui/todo-label.ts +7 -0
- package/src/workflow/engine.ts +266 -0
- package/tsconfig.json +30 -0
- package/vendor/pi-tui/LICENSE +21 -0
- package/vendor/pi-tui/PATCHES.md +12 -0
- package/vendor/pi-tui/PROVENANCE.md +12 -0
- package/vendor/pi-tui/README.upstream.md +854 -0
- package/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
- package/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
- package/vendor/pi-tui/src/alt-screen-search.ts +158 -0
- package/vendor/pi-tui/src/autocomplete.ts +827 -0
- package/vendor/pi-tui/src/components/alt-screen-flash.ts +52 -0
- package/vendor/pi-tui/src/components/box.ts +138 -0
- package/vendor/pi-tui/src/components/cancellable-loader.ts +41 -0
- package/vendor/pi-tui/src/components/editor.ts +2364 -0
- package/vendor/pi-tui/src/components/h-stack.ts +45 -0
- package/vendor/pi-tui/src/components/image.ts +128 -0
- package/vendor/pi-tui/src/components/input.ts +448 -0
- package/vendor/pi-tui/src/components/loader.ts +93 -0
- package/vendor/pi-tui/src/components/markdown.ts +1016 -0
- package/vendor/pi-tui/src/components/scroll-view.ts +217 -0
- package/vendor/pi-tui/src/components/select-list.ts +230 -0
- package/vendor/pi-tui/src/components/settings-list.ts +277 -0
- package/vendor/pi-tui/src/components/spacer.ts +29 -0
- package/vendor/pi-tui/src/components/stack.ts +155 -0
- package/vendor/pi-tui/src/components/text.ts +108 -0
- package/vendor/pi-tui/src/components/truncated-text.ts +66 -0
- package/vendor/pi-tui/src/components/v-stack.ts +34 -0
- package/vendor/pi-tui/src/editor-component.ts +75 -0
- package/vendor/pi-tui/src/fuzzy.ts +138 -0
- package/vendor/pi-tui/src/index.ts +149 -0
- package/vendor/pi-tui/src/keybindings.ts +321 -0
- package/vendor/pi-tui/src/keys.ts +1402 -0
- package/vendor/pi-tui/src/kill-ring.ts +47 -0
- package/vendor/pi-tui/src/latex.ts +1381 -0
- package/vendor/pi-tui/src/layout-node.ts +52 -0
- package/vendor/pi-tui/src/layout.ts +411 -0
- package/vendor/pi-tui/src/native-modifiers.ts +60 -0
- package/vendor/pi-tui/src/native-module-path.ts +32 -0
- package/vendor/pi-tui/src/stdin-buffer.ts +445 -0
- package/vendor/pi-tui/src/terminal-colors.ts +74 -0
- package/vendor/pi-tui/src/terminal-image.ts +701 -0
- package/vendor/pi-tui/src/terminal.ts +554 -0
- package/vendor/pi-tui/src/tui-alt-screen.ts +1379 -0
- package/vendor/pi-tui/src/tui-main-screen.ts +655 -0
- package/vendor/pi-tui/src/tui.ts +1264 -0
- package/vendor/pi-tui/src/undo-stack.ts +29 -0
- package/vendor/pi-tui/src/utils.ts +1327 -0
- package/vendor/pi-tui/src/word-navigation.ts +118 -0
- package/vendor/pi-tui/test/test-themes.ts +39 -0
- package/vendor/pi-tui/test/virtual-terminal.ts +219 -0
- package/CHANGELOG.md +0 -512
- package/bin/rovecode.js +0 -24
- package/dist/cli/app-dybnr56b.js +0 -2
- package/dist/cli/ask-user-p8hq4xgj.js +0 -2
- package/dist/cli/auth-login-ewpgw5sm.js +0 -2
- package/dist/cli/auth-m8p9grty.js +0 -2
- package/dist/cli/bench-xv3ypwev.js +0 -9
- package/dist/cli/catalog-737wb2s0.js +0 -2
- package/dist/cli/cli-arhg40m0.js +0 -2
- package/dist/cli/client-cf2pxx8q.js +0 -2
- package/dist/cli/commands-3p7e4xxs.js +0 -2
- package/dist/cli/connect-3q93d7cb.js +0 -2
- package/dist/cli/context-cmd-eqxmxhzq.js +0 -2
- package/dist/cli/context-report-hbw9zfes.js +0 -2
- package/dist/cli/count-remote-mby98cd0.js +0 -2
- package/dist/cli/design-122y0axd.js +0 -2
- package/dist/cli/dispatch-b4egzvvh.js +0 -2
- package/dist/cli/doctor-x4jkv72e.js +0 -3
- package/dist/cli/executor-ftvg6tsy.js +0 -2
- package/dist/cli/export-pdgdhkch.js +0 -2
- package/dist/cli/files-cez9a96p.js +0 -2
- package/dist/cli/gauntlet-r3xxaszc.js +0 -2
- package/dist/cli/gauntlet-runner-r515m7kk.js +0 -10
- package/dist/cli/gauntlet-wave3-bnkjk2v2.js +0 -5
- package/dist/cli/gauntlet-wave4-acs9s60q.js +0 -14
- package/dist/cli/hashline-ewg5hbe3.js +0 -2
- package/dist/cli/http-n0kehsk8.js +0 -5
- package/dist/cli/index-z5qt1s76.js +0 -2
- package/dist/cli/install-80mp63kx.js +0 -2
- package/dist/cli/loop-12twjcat.js +0 -2
- package/dist/cli/main-01pv9206.js +0 -4
- package/dist/cli/main-0jys2ccn.js +0 -3
- package/dist/cli/main-1ztz6fkj.js +0 -10
- package/dist/cli/main-23q7cmww.js +0 -9
- package/dist/cli/main-2rzbexn2.js +0 -3
- package/dist/cli/main-2wyax8k9.js +0 -9
- package/dist/cli/main-2yeveeve.js +0 -6
- package/dist/cli/main-2z3dek0b.js +0 -3
- package/dist/cli/main-2zgsknth.js +0 -3
- package/dist/cli/main-45ejth3a.js +0 -4
- package/dist/cli/main-45rn3trk.js +0 -22
- package/dist/cli/main-4p4e2w7x.js +0 -4
- package/dist/cli/main-4y0tnfpa.js +0 -16
- package/dist/cli/main-5py0rkmc.js +0 -4
- package/dist/cli/main-6dtqmbt6.js +0 -7
- package/dist/cli/main-6h9x282m.js +0 -4
- package/dist/cli/main-6vjeds42.js +0 -3
- package/dist/cli/main-78gq4bt9.js +0 -6
- package/dist/cli/main-7jd5vh3x.js +0 -4
- package/dist/cli/main-7kt6r53y.js +0 -4
- package/dist/cli/main-8c1tbazx.js +0 -58
- package/dist/cli/main-9a9rnh47.js +0 -19
- package/dist/cli/main-9ht36z12.js +0 -3
- package/dist/cli/main-a2yfvcy9.js +0 -7
- package/dist/cli/main-a3f51n0x.js +0 -5
- package/dist/cli/main-b8zq261k.js +0 -3
- package/dist/cli/main-bxtvnf6d.js +0 -13
- package/dist/cli/main-edxc3yzt.js +0 -4
- package/dist/cli/main-evgz4mp5.js +0 -21
- package/dist/cli/main-f33fc5je.js +0 -9
- package/dist/cli/main-fvnpq46y.js +0 -12
- package/dist/cli/main-gbbty4d4.js +0 -3
- package/dist/cli/main-gth53dnt.js +0 -25
- package/dist/cli/main-hqbz10aw.js +0 -9
- package/dist/cli/main-hrrvcfan.js +0 -38
- package/dist/cli/main-hzwtsb2m.js +0 -5
- package/dist/cli/main-j7ttv0sd.js +0 -34
- package/dist/cli/main-jak598k9.js +0 -5
- package/dist/cli/main-kba6zeyd.js +0 -6
- package/dist/cli/main-kwwsz6rq.js +0 -3
- package/dist/cli/main-m8vm17zq.js +0 -3
- package/dist/cli/main-mg4f96e1.js +0 -3
- package/dist/cli/main-mg9b20ac.js +0 -18
- package/dist/cli/main-mgb9ccnx.js +0 -3
- package/dist/cli/main-mjt2p7aj.js +0 -3
- package/dist/cli/main-n6qrdbmy.js +0 -3
- package/dist/cli/main-na7wse0x.js +0 -5
- package/dist/cli/main-nqveez48.js +0 -4
- package/dist/cli/main-ntqef02r.js +0 -10
- package/dist/cli/main-nvc3yjay.js +0 -136
- package/dist/cli/main-p0cfn6nr.js +0 -16
- package/dist/cli/main-qj2djy17.js +0 -19
- package/dist/cli/main-qsevpgsv.js +0 -3
- package/dist/cli/main-qvarybsp.js +0 -3
- package/dist/cli/main-rebtt91r.js +0 -5
- package/dist/cli/main-rpg7h8mb.js +0 -3
- package/dist/cli/main-rsy72qmw.js +0 -15
- package/dist/cli/main-rvetps99.js +0 -18
- package/dist/cli/main-s4bb0jav.js +0 -3
- package/dist/cli/main-s9v8k74e.js +0 -3
- package/dist/cli/main-tjvwmscs.js +0 -3
- package/dist/cli/main-tkgarpjj.js +0 -4
- package/dist/cli/main-v8y60bb2.js +0 -3
- package/dist/cli/main-vhrrq337.js +0 -3
- package/dist/cli/main-vp2dfb7s.js +0 -4
- package/dist/cli/main-vqbr22sz.js +0 -8
- package/dist/cli/main-vxnwe5xx.js +0 -18
- package/dist/cli/main-wgph00xf.js +0 -5
- package/dist/cli/main-wk2csfnj.js +0 -5
- package/dist/cli/main-wm997zjx.js +0 -3
- package/dist/cli/main-wpkyraxh.js +0 -3
- package/dist/cli/main-wqt32p5x.js +0 -4
- package/dist/cli/main-x9ct6y1a.js +0 -3
- package/dist/cli/main-xfekqh9m.js +0 -7
- package/dist/cli/main-xt9zc3n6.js +0 -7
- package/dist/cli/main-xx2z3zh5.js +0 -4
- package/dist/cli/main-y5c82rxr.js +0 -3
- package/dist/cli/main-yrjt2sqt.js +0 -14
- package/dist/cli/main-ys6zj3yr.js +0 -3
- package/dist/cli/main-ywbxshqc.js +0 -8
- package/dist/cli/main-z13755t8.js +0 -25
- package/dist/cli/main-zc7pyrbj.js +0 -4
- package/dist/cli/main.js +0 -279
- package/dist/cli/market-cmd-bm5xvn9f.js +0 -5
- package/dist/cli/mcp-login-bthtfpt7.js +0 -2
- package/dist/cli/mcp-market-cmd-mbeshfyd.js +0 -2
- package/dist/cli/notify-54v5z9dz.js +0 -2
- package/dist/cli/oauth-g5gme95c.js +0 -2
- package/dist/cli/output-satndjap.js +0 -16
- package/dist/cli/profiles-sfhpbq3m.js +0 -2
- package/dist/cli/provider-config-hv3xtdt4.js +0 -2
- package/dist/cli/provider-kwzq6g84.js +0 -2
- package/dist/cli/registry-fh0hdnyn.js +0 -2
- package/dist/cli/registry-y1y8e94r.js +0 -2
- package/dist/cli/repl-t4z03mqq.js +0 -11
- package/dist/cli/resume-fqt4chg8.js +0 -2
- package/dist/cli/run-flags-rysbag9t.js +0 -2
- package/dist/cli/runtime-j19fjbsa.js +0 -2
- package/dist/cli/sandbox-config-g4qxd7y5.js +0 -2
- package/dist/cli/server-r0b6bksk.js +0 -5
- package/dist/cli/session-arg-txmn5g4x.js +0 -2
- package/dist/cli/session-ed250d9j.js +0 -2
- package/dist/cli/sessions-cmd-adw7svfn.js +0 -7
- package/dist/cli/settings-y9rzcqx8.js +0 -2
- package/dist/cli/setup-jmbr11j0.js +0 -2
- package/dist/cli/sextant-smoke-tcth0vea.js +0 -5
- package/dist/cli/skills-cmd-zbdy99v6.js +0 -2
- package/dist/cli/smoke-1bg937kx.js +0 -8
- package/dist/cli/start-chat-p01cdks3.js +0 -12
- package/dist/cli/stream-4wmyaypz.js +0 -2
- package/dist/cli/task-eg4s093s.js +0 -2
- package/dist/cli/tasks-12v9rr9k.js +0 -2
- package/dist/cli/thinking-a5ngvqyh.js +0 -2
- package/dist/cli/todo-1wxpcecx.js +0 -2
- package/dist/cli/tools-2ftsya7w.js +0 -2
- package/dist/cli/tools-x1tj4fxm.js +0 -2
- package/dist/cli/trust-cmd-hccxehzb.js +0 -2
- package/dist/cli/update-check-ygt3vd7m.js +0 -2
- package/dist/cli/update-cmd-v23qhr8c.js +0 -2
- package/dist/cli/voice-g1gtck92.js +0 -2
- package/dist/cli/webfetch-0nnrjgb5.js +0 -2
- package/dist/cli/websearch-f0vr2p7d.js +0 -2
- package/dist/cli/workspace-9rq1w4ta.js +0 -2
- package/dist/lib/index.js +0 -62
- package/dist/lib/models-index.json +0 -1
- package/dist/lib/plugins.js +0 -6
- package/dist/lib/providers.js +0 -17
- package/dist/lib/public-api.js +0 -20
- package/dist/rovecode.exe +0 -4
- /package/{dist/cli → src/providers}/models-index.json +0 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/** Rovecode Mission Control — the /ui dashboard (F2, sdk-blueprint.md §5b).
|
|
2
|
+
*
|
|
3
|
+
* ONE self-contained HTML document, no build step, no CDN: the serve command's
|
|
4
|
+
* hand-authored-OpenAPI tradition applied to the page. It reads GET /events (SSE)
|
|
5
|
+
* and /sessions, renders sessions, live run activity and the agent tree; it holds
|
|
6
|
+
* no state of its own beyond what the bus told it (render-only, §5 kuralı). */
|
|
7
|
+
|
|
8
|
+
export function dashboardHtml(): string {
|
|
9
|
+
return `<!doctype html>
|
|
10
|
+
<html lang="en">
|
|
11
|
+
<head>
|
|
12
|
+
<meta charset="utf-8">
|
|
13
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
14
|
+
<title>rovecode · mission control</title>
|
|
15
|
+
<style>
|
|
16
|
+
:root {
|
|
17
|
+
--bg: #0b0e14; --panel: #12161f; --edge: #1f2633; --text: #d7dde8; --dim: #7d8698;
|
|
18
|
+
--queued: #8a93a5; --running: #4da3ff; --done: #3ecf8e; --failed: #ff6b6b;
|
|
19
|
+
--cancelled: #c9a227; --accent: #b48cff;
|
|
20
|
+
}
|
|
21
|
+
* { box-sizing: border-box; margin: 0; }
|
|
22
|
+
body { background: var(--bg); color: var(--text); font: 13px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; height: 100vh; display: flex; flex-direction: column; }
|
|
23
|
+
header { padding: 10px 16px; border-bottom: 1px solid var(--edge); display: flex; align-items: baseline; gap: 12px; }
|
|
24
|
+
header h1 { font-size: 14px; font-weight: 600; letter-spacing: .04em; }
|
|
25
|
+
header h1 em { color: var(--accent); font-style: normal; }
|
|
26
|
+
#conn { color: var(--dim); font-size: 11px; }
|
|
27
|
+
#conn.live { color: var(--done); }
|
|
28
|
+
main { flex: 1; display: grid; grid-template-columns: 260px 1fr 320px; min-height: 0; }
|
|
29
|
+
section { border-right: 1px solid var(--edge); overflow-y: auto; padding: 10px; }
|
|
30
|
+
section:last-child { border-right: 0; }
|
|
31
|
+
h2 { font-size: 11px; text-transform: uppercase; letter-spacing: .12em; color: var(--dim); margin: 4px 2px 8px; }
|
|
32
|
+
.sess { padding: 6px 8px; border: 1px solid var(--edge); border-radius: 6px; margin-bottom: 6px; cursor: pointer; background: var(--panel); }
|
|
33
|
+
.sess.sel { border-color: var(--accent); }
|
|
34
|
+
.sess .id { font-size: 11px; color: var(--dim); }
|
|
35
|
+
.sess .st { float: right; font-size: 10px; }
|
|
36
|
+
.ev { padding: 2px 6px; border-radius: 4px; white-space: pre-wrap; word-break: break-word; }
|
|
37
|
+
.ev .t { color: var(--dim); margin-right: 6px; }
|
|
38
|
+
.ev.run_start, .ev.run_end { background: var(--panel); }
|
|
39
|
+
.ev.tool_execution_end .t { color: var(--running); }
|
|
40
|
+
.node { padding: 5px 8px; border: 1px solid var(--edge); border-left-width: 3px; border-radius: 6px; margin: 4px 0; background: var(--panel); }
|
|
41
|
+
.node .lbl { font-weight: 600; }
|
|
42
|
+
.node .meta { font-size: 11px; color: var(--dim); }
|
|
43
|
+
.st-queued { border-left-color: var(--queued); } .st-queued .badge { color: var(--queued); }
|
|
44
|
+
.st-running { border-left-color: var(--running); } .st-running .badge { color: var(--running); }
|
|
45
|
+
.st-done { border-left-color: var(--done); } .st-done .badge { color: var(--done); }
|
|
46
|
+
.st-failed { border-left-color: var(--failed); } .st-failed .badge { color: var(--failed); }
|
|
47
|
+
.st-cancelled { border-left-color: var(--cancelled); } .st-cancelled .badge { color: var(--cancelled); }
|
|
48
|
+
.empty { color: var(--dim); padding: 8px; font-size: 12px; }
|
|
49
|
+
</style>
|
|
50
|
+
</head>
|
|
51
|
+
<body>
|
|
52
|
+
<header>
|
|
53
|
+
<h1>rovecode <em>mission control</em></h1>
|
|
54
|
+
<span id="conn">connecting…</span>
|
|
55
|
+
</header>
|
|
56
|
+
<main>
|
|
57
|
+
<section><h2>Sessions</h2><div id="sessions"></div></section>
|
|
58
|
+
<section><h2>Live events</h2><div id="feed"></div></section>
|
|
59
|
+
<section><h2>Agent tree</h2><div id="tree"></div></section>
|
|
60
|
+
</main>
|
|
61
|
+
<script>
|
|
62
|
+
const $ = (id) => document.getElementById(id);
|
|
63
|
+
const state = { sessions: [], sel: null, trees: {}, running: {} };
|
|
64
|
+
const short = (id) => id.length > 8 ? id.slice(0, 8) : id;
|
|
65
|
+
const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c]));
|
|
66
|
+
|
|
67
|
+
async function refreshSessions() {
|
|
68
|
+
try {
|
|
69
|
+
const r = await fetch("/sessions");
|
|
70
|
+
const list = await r.json();
|
|
71
|
+
state.sessions = (Array.isArray(list) ? list : []).map((s) => s.id ?? s);
|
|
72
|
+
} catch { /* server restarting */ }
|
|
73
|
+
renderSessions();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function renderSessions() {
|
|
77
|
+
const el = $("sessions");
|
|
78
|
+
if (state.sessions.length === 0) { el.innerHTML = '<div class="empty">no sessions yet</div>'; return; }
|
|
79
|
+
el.innerHTML = state.sessions.map((id) => {
|
|
80
|
+
const running = state.running[id] ? '<span class="st" style="color:var(--running)">● running</span>' : "";
|
|
81
|
+
return '<div class="sess' + (state.sel === id ? " sel" : "") + '" data-id="' + esc(id) + '">' + running +
|
|
82
|
+
'<div>' + esc(short(id)) + '</div><div class="id">' + esc(id) + '</div></div>';
|
|
83
|
+
}).join("");
|
|
84
|
+
for (const d of el.querySelectorAll(".sess")) d.onclick = () => { state.sel = d.dataset.id; renderSessions(); renderTree(); };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function renderTree() {
|
|
88
|
+
const el = $("tree");
|
|
89
|
+
const id = state.sel ?? state.sessions[0];
|
|
90
|
+
const tree = (id && state.trees[id]) || [];
|
|
91
|
+
if (tree.length === 0) { el.innerHTML = '<div class="empty">no subagents or background tasks yet</div>'; return; }
|
|
92
|
+
const depthPad = (n) => "margin-left:" + Math.min(n.depth - 1, 6) * 14 + "px";
|
|
93
|
+
el.innerHTML = tree.map((n) =>
|
|
94
|
+
'<div class="node st-' + esc(n.status) + '" style="' + depthPad(n) + '">' +
|
|
95
|
+
'<span class="badge">●</span> <span class="lbl">' + esc(n.label) + '</span>' +
|
|
96
|
+
(n.isolated ? ' <span class="meta">[worktree]</span>' : "") +
|
|
97
|
+
'<div class="meta">' + esc(n.agent) + " · depth " + n.depth + (n.parent ? " · ↑ " + esc(n.parent) : "") +
|
|
98
|
+
(n.summary ? "<br>" + esc(n.summary) : "") + (n.error ? "<br>" + esc(n.error) : "") +
|
|
99
|
+
"</div></div>"
|
|
100
|
+
).join("");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function feed(ev) {
|
|
104
|
+
const el = $("feed");
|
|
105
|
+
const d = document.createElement("div");
|
|
106
|
+
d.className = "ev " + ev.type;
|
|
107
|
+
let text = ev.type;
|
|
108
|
+
if (ev.type === "run_start") text = "run start — " + (ev.goal || "").slice(0, 120);
|
|
109
|
+
else if (ev.type === "run_end") text = "run end: " + ev.status + " — " + (ev.summary || "").slice(0, 160);
|
|
110
|
+
else if (ev.type === "tool_execution_start") text = "→ " + ev.tool;
|
|
111
|
+
else if (ev.type === "tool_execution_end") text = (ev.ok ? "✓ " : "✗ ") + ev.callId + " " + ev.durationMs + "ms";
|
|
112
|
+
else if (ev.type === "compaction") text = "compaction " + ev.strategy + " " + ev.tokensBefore + "→" + ev.tokensAfter;
|
|
113
|
+
d.innerHTML = '<span class="t">' + esc(ev.type) + '</span>' + esc(text === ev.type ? "" : text);
|
|
114
|
+
el.appendChild(d);
|
|
115
|
+
while (el.childElementCount > 400) el.removeChild(el.firstChild);
|
|
116
|
+
el.scrollTop = el.scrollHeight;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function connect() {
|
|
120
|
+
const es = new EventSource("/events");
|
|
121
|
+
es.onopen = () => { $("conn").textContent = "live"; $("conn").className = "live"; };
|
|
122
|
+
es.onerror = () => { $("conn").textContent = "reconnecting…"; $("conn").className = ""; };
|
|
123
|
+
es.onmessage = (m) => {
|
|
124
|
+
let msg; try { msg = JSON.parse(m.data); } catch { return; }
|
|
125
|
+
if (msg.type === "hello") { for (const id of msg.sessions) if (!state.sessions.includes(id)) state.sessions.push(id); renderSessions(); }
|
|
126
|
+
else if (msg.type === "session_created") { if (!state.sessions.includes(msg.sessionId)) state.sessions.push(msg.sessionId); renderSessions(); }
|
|
127
|
+
else if (msg.type === "agent_tree_update") { state.trees[msg.sessionId] = msg.tree; renderTree(); }
|
|
128
|
+
else if (msg.type === "run_event") {
|
|
129
|
+
const ev = msg.event;
|
|
130
|
+
if (ev.type === "run_start") state.running[msg.sessionId] = true;
|
|
131
|
+
if (ev.type === "run_end") state.running[msg.sessionId] = false;
|
|
132
|
+
renderSessions();
|
|
133
|
+
if (!state.sel || state.sel === msg.sessionId) feed(ev);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
refreshSessions();
|
|
139
|
+
setInterval(refreshSessions, 5000);
|
|
140
|
+
connect();
|
|
141
|
+
</script>
|
|
142
|
+
</body>
|
|
143
|
+
</html>`;
|
|
144
|
+
}
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/** Headless HTTP server (port #19): pattern-level port of opencode's client/server
|
|
2
|
+
* split (MIT, snapshot research/source_snapshots/opencode-2026).
|
|
3
|
+
*
|
|
4
|
+
* Patterns ported (citations against the snapshot):
|
|
5
|
+
* - client/server split: the TUI/CLI are HTTP clients of a headless server —
|
|
6
|
+
* packages/opencode/src/cli/cmd/serve.ts:6-24 (serve command → Server.listen),
|
|
7
|
+
* packages/sdk/js (client generated from the server's OpenAPI spec),
|
|
8
|
+
* packages/opencode/src/cli/cmd/tui.ts:11 (TUI imports @opencode-ai/sdk).
|
|
9
|
+
* - session surface: routes/instance/httpapi/groups/session.ts — root "/session"
|
|
10
|
+
* (:29), create POST (:203), prompt POST (:316), list GET (:111).
|
|
11
|
+
* - GET /doc serving the OpenAPI JSON: routes/instance/httpapi/server.ts:190
|
|
12
|
+
* (docRoute) + :188 (lazy spec build). We hand-author the object (openapi.ts).
|
|
13
|
+
* - SSE event streaming: routes/instance/httpapi/handlers/event.ts:69-85
|
|
14
|
+
* (text/event-stream response, no-cache headers).
|
|
15
|
+
* - loopback bind by default: cli/network.ts:12-15 (hostname default "127.0.0.1").
|
|
16
|
+
*
|
|
17
|
+
* NOT ported: opencode's implementation is Effect-based (server.ts imports
|
|
18
|
+
* effect / @effect/platform-node / effect/unstable/httpapi). ADR-001 rejects
|
|
19
|
+
* Effect idioms — this file is plain Bun.serve + the ONE agentLoop (ADR-003)
|
|
20
|
+
* reused via createRuntime. v1 approvals are policy-only: no interactive
|
|
21
|
+
* approver over HTTP; a permission rule with effect "prompt" fails the tool
|
|
22
|
+
* call at the registry seam (core/tools.ts:87-93). See APPROVALS_NOTE in /doc.
|
|
23
|
+
*
|
|
24
|
+
* Never-throw seam: route handlers are wrapped — an exception becomes a JSON
|
|
25
|
+
* 500; a mid-stream loop error becomes a final run_end error frame, never a
|
|
26
|
+
* socket reset or a crashed server. */
|
|
27
|
+
|
|
28
|
+
import { randomUUID } from "node:crypto";
|
|
29
|
+
import { join } from "node:path";
|
|
30
|
+
import { bootRuntime, type Runtime } from "../cli/runtime.ts";
|
|
31
|
+
import { noModelHint } from "../core/voice.ts";
|
|
32
|
+
import { SandboxConfigError } from "../core/sandbox-config.ts";
|
|
33
|
+
import { agentLoop, SteeringQueue } from "../core/loop.ts";
|
|
34
|
+
import { listSessions } from "../core/session.ts";
|
|
35
|
+
import type { ModelRef, RunEvent, StreamFn } from "../core/types.ts";
|
|
36
|
+
import { buildOpenApiDoc } from "./openapi.ts";
|
|
37
|
+
import { agentTree } from "../sdk/client.ts";
|
|
38
|
+
import { dashboardHtml } from "./dashboard.ts";
|
|
39
|
+
|
|
40
|
+
export const DEFAULT_PORT = 4100;
|
|
41
|
+
export const DEFAULT_HOSTNAME = "127.0.0.1";
|
|
42
|
+
/** request-body bound: prompt bodies over this fail with a clean JSON 413
|
|
43
|
+
* (checked against content-length before the body is read). Bun.serve's
|
|
44
|
+
* maxRequestBodySize is set to 2× this as a transport ceiling for clients
|
|
45
|
+
* that lie about / omit content-length (chunked): those are cut at the
|
|
46
|
+
* socket instead of ballooning RSS. */
|
|
47
|
+
export const MAX_BODY_BYTES = 1024 * 1024;
|
|
48
|
+
|
|
49
|
+
export interface ServerOptions {
|
|
50
|
+
/** TCP port; default 4100; 0 = ephemeral (tests) */
|
|
51
|
+
port?: number;
|
|
52
|
+
/** bind address; default 127.0.0.1 (loopback-only, opencode cli/network.ts:15) */
|
|
53
|
+
hostname?: string;
|
|
54
|
+
/** project root; sessions live in <cwd>/.rovecode/sessions */
|
|
55
|
+
cwd?: string;
|
|
56
|
+
/** injectable provider stream (tests); undefined = resolve from env; null = no stream */
|
|
57
|
+
stream?: StreamFn | null;
|
|
58
|
+
/** allow-all permission rules; default false = policy rules with prompt-effect
|
|
59
|
+
* gates, which FAIL over HTTP (no approver) — see APPROVALS_NOTE */
|
|
60
|
+
yolo?: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface RovecodeServer {
|
|
64
|
+
port: number;
|
|
65
|
+
hostname: string;
|
|
66
|
+
url: string;
|
|
67
|
+
/** closes the listener and in-flight SSE sockets, aborts every in-flight
|
|
68
|
+
* run's controller (port #21: the in-flight provider fetch and tool
|
|
69
|
+
* subprocesses die mid-turn), then closes every session runtime's MCP
|
|
70
|
+
* children. Returns without awaiting the aborted generators' final settle. */
|
|
71
|
+
stop(): Promise<void>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface SessionEntry {
|
|
75
|
+
runtime: Runtime;
|
|
76
|
+
running: boolean;
|
|
77
|
+
/** the in-flight run's controller (port #21) — DELETE /session/:id/prompt and
|
|
78
|
+
* stop() abort it; null when idle. Cleared by the pump's settle, like `running`. */
|
|
79
|
+
abort: AbortController | null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function json(body: unknown, status = 200): Response {
|
|
83
|
+
return new Response(JSON.stringify(body), {
|
|
84
|
+
status,
|
|
85
|
+
headers: { "content-type": "application/json" },
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** SSE framing (bar): `event: <type>\ndata: <json>\n\n`. */
|
|
90
|
+
function frame(ev: RunEvent): string {
|
|
91
|
+
return `event: ${ev.type}\ndata: ${JSON.stringify(ev)}\n\n`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Pump loop events into an SSE response; the stream closes on run_end.
|
|
95
|
+
* Client disconnects abort the run's controller (port #21): the in-flight
|
|
96
|
+
* provider fetch dies and tool subprocesses are killed mid-turn, then the
|
|
97
|
+
* generator settles at its next suspension point. `running` is cleared ONLY
|
|
98
|
+
* from the pump's finally — when the generator has actually released the
|
|
99
|
+
* session — so the 409 one-run-per-session invariant holds across
|
|
100
|
+
* disconnects (MED-F1). */
|
|
101
|
+
function sseResponse(run: AsyncGenerator<RunEvent>, onSettled: () => void, abort: () => void): Response {
|
|
102
|
+
const enc = new TextEncoder();
|
|
103
|
+
let settled = false;
|
|
104
|
+
const settle = () => { if (!settled) { settled = true; onSettled(); } };
|
|
105
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
106
|
+
async start(controller) {
|
|
107
|
+
try {
|
|
108
|
+
for await (const ev of run) {
|
|
109
|
+
controller.enqueue(enc.encode(frame(ev)));
|
|
110
|
+
if (ev.type === "run_end") break; // bar: stream closes on run_end
|
|
111
|
+
}
|
|
112
|
+
} catch (e) {
|
|
113
|
+
// never-throw seam: a loop/enqueue error becomes a terminal error frame
|
|
114
|
+
const end: RunEvent = { type: "run_end", status: "error", summary: e instanceof Error ? e.message : String(e) };
|
|
115
|
+
try { controller.enqueue(enc.encode(frame(end))); } catch { /* consumer gone */ }
|
|
116
|
+
} finally {
|
|
117
|
+
settle();
|
|
118
|
+
try { controller.close(); } catch { /* cancelled/closed already */ }
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
cancel() {
|
|
122
|
+
// client hung up mid-run: abort the run's controller (kills the in-flight
|
|
123
|
+
// fetch / tool subprocesses — port #21), then close the generator as the
|
|
124
|
+
// follow-through. Do NOT settle here: the pump's finally settles
|
|
125
|
+
// truthfully once the generator finishes; an eager settle let a second
|
|
126
|
+
// run share this session's store (MED-F1).
|
|
127
|
+
abort();
|
|
128
|
+
void Promise.resolve(run.return(undefined)).catch(() => {});
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
return new Response(stream, {
|
|
132
|
+
status: 200,
|
|
133
|
+
headers: {
|
|
134
|
+
// headers per opencode handlers/event.ts:78-83
|
|
135
|
+
"content-type": "text/event-stream",
|
|
136
|
+
"cache-control": "no-cache, no-transform",
|
|
137
|
+
"x-accel-buffering": "no",
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function bodyText(body: unknown): string | null {
|
|
143
|
+
if (body && typeof body === "object" && typeof (body as { text?: unknown }).text === "string") {
|
|
144
|
+
return (body as { text: string }).text;
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function bodyModel(body: unknown): ModelRef | null {
|
|
150
|
+
if (body && typeof body === "object" && (body as { model?: unknown }).model && typeof (body as { model?: unknown }).model === "object") {
|
|
151
|
+
const m = (body as { model: { provider?: unknown; model?: unknown } }).model;
|
|
152
|
+
if (typeof m.provider === "string" && typeof m.model === "string") return { provider: m.provider, model: m.model };
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function startServer(opts: ServerOptions = {}): RovecodeServer {
|
|
158
|
+
const hostname = opts.hostname ?? DEFAULT_HOSTNAME;
|
|
159
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
160
|
+
const sessionsRoot = join(cwd, ".rovecode", "sessions");
|
|
161
|
+
const yolo = opts.yolo ?? false;
|
|
162
|
+
const sessions = new Map<string, SessionEntry>();
|
|
163
|
+
|
|
164
|
+
// Global event bus (F2, sdk-blueprint.md §5c): every run event and every background-task
|
|
165
|
+
// status flip is broadcast here; GET /events streams it as SSE and the /ui dashboard,
|
|
166
|
+
// the SDK and scripts all read the SAME frames. Render-only: the bus holds no state.
|
|
167
|
+
const bus = new Set<(data: string) => void>();
|
|
168
|
+
const busSend = (payload: unknown): void => {
|
|
169
|
+
const data = JSON.stringify(payload);
|
|
170
|
+
for (const fn of bus) fn(data);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const createSession = async (): Promise<Response> => {
|
|
174
|
+
const id = randomUUID();
|
|
175
|
+
// Reuse the ONE runtime construction every surface uses (cli/runtime.ts):
|
|
176
|
+
// same stores, same tools, same provider resolution. Injectable stream for tests.
|
|
177
|
+
let runtime: Runtime;
|
|
178
|
+
try {
|
|
179
|
+
runtime = await bootRuntime({ cwd, sessionId: id, stream: opts.stream });
|
|
180
|
+
} catch (e) {
|
|
181
|
+
// port #27: sandbox misconfig / unavailable configured rung → 503 with the
|
|
182
|
+
// one-line message (the server cwd cannot provide the requested executor)
|
|
183
|
+
if (e instanceof SandboxConfigError) return json({ error: e.message }, 503);
|
|
184
|
+
throw e; // never-throw seam below turns anything else into a JSON 500
|
|
185
|
+
}
|
|
186
|
+
sessions.set(id, { runtime, running: false, abort: null });
|
|
187
|
+
runtime.tasks.subscribe(() => {
|
|
188
|
+
busSend({ type: "agent_tree_update", sessionId: id, tree: agentTree(runtime.tasks.list()) });
|
|
189
|
+
});
|
|
190
|
+
busSend({ type: "session_created", sessionId: id });
|
|
191
|
+
return json({ id }, 201);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const prompt = async (id: string, req: Request): Promise<Response> => {
|
|
195
|
+
const entry = sessions.get(id);
|
|
196
|
+
if (!entry) return json({ error: `unknown session ${id}` }, 404);
|
|
197
|
+
// LOW-MED-F4: bound the request body BEFORE reading it (declared size), and
|
|
198
|
+
// the parsed text after — an unbounded body was a local RSS balloon.
|
|
199
|
+
const declared = Number(req.headers.get("content-length") ?? "0");
|
|
200
|
+
if (declared > MAX_BODY_BYTES) return json({ error: `request body too large (max ${MAX_BODY_BYTES} bytes)` }, 413);
|
|
201
|
+
let body: unknown;
|
|
202
|
+
try { body = await req.json(); } catch { return json({ error: "body must be JSON" }, 400); }
|
|
203
|
+
const text = bodyText(body);
|
|
204
|
+
if (text === null) return json({ error: 'body must be {"text": string}' }, 400);
|
|
205
|
+
if (text.length > MAX_BODY_BYTES) return json({ error: `text too large (max ${MAX_BODY_BYTES} chars)` }, 413);
|
|
206
|
+
if (entry.running) return json({ error: "a run is already in progress for this session" }, 409);
|
|
207
|
+
const rt = entry.runtime;
|
|
208
|
+
const stream = rt.stream;
|
|
209
|
+
// live: a provider added after boot (rovecode provider add / auth set) serves the next request
|
|
210
|
+
const noProvider = rt.noProviderReason();
|
|
211
|
+
if (!stream || noProvider !== null) return json({ error: noProvider ?? noModelHint("cli") }, 503);
|
|
212
|
+
const model: ModelRef = bodyModel(body) ?? { provider: rt.provider?.id ?? "mock", model: rt.defaultModel || "default" };
|
|
213
|
+
const def = rt.buildDef(model);
|
|
214
|
+
// no interactive ApprovalFn — buildCfg installs the port-#9 exec-policy wrapper:
|
|
215
|
+
// allow-listed argv auto-runs, forbidden is denied, prompt-classified fails closed
|
|
216
|
+
// as tool_call_failed/permission_denied (core/tools.ts:87-93)
|
|
217
|
+
const cfg = rt.buildCfg(yolo, undefined);
|
|
218
|
+
const ac = new AbortController(); // port #21: one controller per run
|
|
219
|
+
rt.tasks.bindRun(ac.signal); // port #26: DELETE / disconnect / stop() also cancel the run's background tasks
|
|
220
|
+
const run = agentLoop(def, text, {}, cfg, {
|
|
221
|
+
stream, registry: rt.registry, store: rt.store,
|
|
222
|
+
tools: rt.registry.list().map((t) => t.schema), guard: rt.guard, planReminder: rt.planReminder,
|
|
223
|
+
hooks: rt.hooks, // port #29: .rovecode/hooks.{ts,js} of the server cwd
|
|
224
|
+
cwd: rt.cwd, // session cwd reaches ToolContext (same gap as ACP HIGH-G1)
|
|
225
|
+
signal: ac.signal, // port #21: DELETE / disconnect / stop() kill in-flight work
|
|
226
|
+
}, rt.steering); // port #26: the session's queue — background-task notes land on the next prompt
|
|
227
|
+
entry.running = true;
|
|
228
|
+
entry.abort = ac;
|
|
229
|
+
// tap: every frame the prompt's own SSE consumer gets also rides the global bus
|
|
230
|
+
// (GET /events, /ui). Generator delegation keeps the cancel path truthful:
|
|
231
|
+
// sseResponse's cancel() still reaches the real run through the wrapper.
|
|
232
|
+
const tapped = (async function* (): AsyncGenerator<RunEvent, void> {
|
|
233
|
+
for await (const ev of run) {
|
|
234
|
+
busSend({ type: "run_event", sessionId: id, event: ev });
|
|
235
|
+
yield ev;
|
|
236
|
+
}
|
|
237
|
+
})();
|
|
238
|
+
return sseResponse(tapped, () => { entry.running = false; entry.abort = null; }, () => ac.abort());
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
/** DELETE /session/:id/prompt — cancel the in-flight run (port #21). Aborting
|
|
242
|
+
* the controller kills the provider fetch / tool subprocesses; the run's own
|
|
243
|
+
* SSE stream then ends with run_end status "stopped". `running` still clears
|
|
244
|
+
* only from the pump's settle, so the 409 invariant is untouched: the session
|
|
245
|
+
* frees when the generator has actually released it. opencode exposes the
|
|
246
|
+
* same operation as POST /session/:sessionID/abort → SessionPrompt.cancel
|
|
247
|
+
* (routes/instance/httpapi/groups/session.ts:91,253; handlers/session.ts:232). */
|
|
248
|
+
const cancelPrompt = (id: string): Response => {
|
|
249
|
+
const entry = sessions.get(id);
|
|
250
|
+
if (!entry) return json({ error: `unknown session ${id}` }, 404);
|
|
251
|
+
const live = entry.abort !== null;
|
|
252
|
+
entry.abort?.abort();
|
|
253
|
+
return json({ cancelled: live });
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
/** GET /session/:id/tasks — background-task status (port #26): the session runtime's
|
|
257
|
+
* TaskManager snapshot (TaskInfo[], oldest first). opencode exposes the same registry
|
|
258
|
+
* through its job service (packages/core/src/background-job.ts list/get). */
|
|
259
|
+
const listTasks = (id: string): Response => {
|
|
260
|
+
const entry = sessions.get(id);
|
|
261
|
+
if (!entry) return json({ error: `unknown session ${id}` }, 404);
|
|
262
|
+
return json(entry.runtime.tasks.list());
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
/** GET /events — the global bus as SSE (never closes on run_end; one connection
|
|
266
|
+
* watches every session). A reconnecting client loses interim frames by design:
|
|
267
|
+
* the bus is a live tap, not a log — replay is the session JSONL's job. */
|
|
268
|
+
const eventsStream = (): Response => {
|
|
269
|
+
const enc = new TextEncoder();
|
|
270
|
+
let listener: ((data: string) => void) | null = null;
|
|
271
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
272
|
+
start(controller) {
|
|
273
|
+
listener = (data) => {
|
|
274
|
+
try { controller.enqueue(enc.encode(`data: ${data}\n\n`)); } catch { /* consumer gone */ }
|
|
275
|
+
};
|
|
276
|
+
bus.add(listener);
|
|
277
|
+
// snapshot so a fresh client is not blind until the next event
|
|
278
|
+
try {
|
|
279
|
+
controller.enqueue(enc.encode(`data: ${JSON.stringify({ type: "hello", sessions: [...sessions.keys()] })}\n\n`));
|
|
280
|
+
} catch { /* consumer gone */ }
|
|
281
|
+
},
|
|
282
|
+
cancel() { if (listener) bus.delete(listener); },
|
|
283
|
+
});
|
|
284
|
+
return new Response(stream, {
|
|
285
|
+
status: 200,
|
|
286
|
+
headers: { "content-type": "text/event-stream", "cache-control": "no-cache, no-transform", "x-accel-buffering": "no" },
|
|
287
|
+
});
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const route = async (req: Request): Promise<Response> => {
|
|
291
|
+
const path = new URL(req.url).pathname;
|
|
292
|
+
if (req.method === "POST" && path === "/session") return createSession();
|
|
293
|
+
const m = /^\/session\/([^/]+)\/prompt$/.exec(path);
|
|
294
|
+
if (req.method === "POST" && m) return prompt(m[1]!, req);
|
|
295
|
+
if (req.method === "DELETE" && m) return cancelPrompt(m[1]!);
|
|
296
|
+
const t = /^\/session\/([^/]+)\/tasks$/.exec(path);
|
|
297
|
+
if (req.method === "GET" && t) return listTasks(t[1]!);
|
|
298
|
+
if (req.method === "GET" && path === "/sessions") return json(listSessions(sessionsRoot));
|
|
299
|
+
if (req.method === "GET" && path === "/events") return eventsStream();
|
|
300
|
+
if (req.method === "GET" && (path === "/ui" || path === "/ui/")) {
|
|
301
|
+
return new Response(dashboardHtml(), { headers: { "content-type": "text/html; charset=utf-8" } });
|
|
302
|
+
}
|
|
303
|
+
if (req.method === "GET" && path === "/doc") return json(buildOpenApiDoc(api.url));
|
|
304
|
+
return json({ error: `no route for ${req.method} ${path}` }, 404);
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
const server = Bun.serve({
|
|
308
|
+
hostname,
|
|
309
|
+
port: opts.port ?? DEFAULT_PORT,
|
|
310
|
+
idleTimeout: 0, // SSE runs outlive Bun's default idle timeout
|
|
311
|
+
// transport ceiling (LOW-MED-F4): bodies without/with a lying content-length
|
|
312
|
+
// are cut here (socket close); declared oversizes get a clean 413 in prompt()
|
|
313
|
+
maxRequestBodySize: MAX_BODY_BYTES * 2,
|
|
314
|
+
async fetch(req) {
|
|
315
|
+
// never-throw seam: handler exceptions become JSON 500s, the server survives
|
|
316
|
+
try { return await route(req); }
|
|
317
|
+
catch (e) { return json({ error: e instanceof Error ? e.message : String(e) }, 500); }
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
const api: RovecodeServer = {
|
|
322
|
+
port: server.port ?? 0,
|
|
323
|
+
hostname,
|
|
324
|
+
url: `http://${hostname}:${server.port}`,
|
|
325
|
+
async stop() {
|
|
326
|
+
await server.stop(true);
|
|
327
|
+
// port #21: abort every in-flight run — the provider fetch and tool
|
|
328
|
+
// subprocesses die now instead of running on after the sockets closed.
|
|
329
|
+
for (const entry of sessions.values()) entry.abort?.abort();
|
|
330
|
+
for (const { runtime } of sessions.values()) runtime.tasks.cancelAll(); // port #26: background children die with the server, never after it
|
|
331
|
+
// MED-F3: reap MCP children — every POST /session spawns one set via
|
|
332
|
+
// createRuntime; without this an unauthenticated loopback port is an
|
|
333
|
+
// unbounded local spawn primitive that outlives the server.
|
|
334
|
+
const closing: Promise<unknown>[] = [];
|
|
335
|
+
for (const { runtime } of sessions.values()) {
|
|
336
|
+
closing.push(runtime.hooks.close()); // port #29: session_close per session runtime
|
|
337
|
+
if (runtime.mcp) closing.push(runtime.mcp.close().catch(() => {}));
|
|
338
|
+
}
|
|
339
|
+
await Promise.all(closing);
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
return api;
|
|
343
|
+
}
|