Repository navigation
feat(*): upgrade from the page with progress, open it for bare raven - #806
Conversation
`raven serve` has refused to start on a half-written environment since the upgrade marker existed, but `raven gateway` never did, and the gateway is what `raven web` supervises. A restart that landed inside an upgrade window came up on the half-written tree, and because the page route is chosen once, served the placeholder page until restarted by hand. The guard now runs first in the gateway command, before the instance lock, since the wait inside it can last the whole install. The page also stops pasting the server's English refusal sentence into an otherwise localized card. Refusals it can name come from the catalogue; refusals whose detail carries facts from this run keep them. Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
uv draws one progress bar per package and never an overall total, so an upgrade whose engines are tens of megabytes reads exactly like one that has stopped. On a slow link the wait is long enough that people kill it. The helper now prints what it is about to fetch, with each asset's size and the total, read from the release assets with HEAD. The headline is printed before the probes so a probe that hangs adds no new silence, the probes are bounded at five seconds, and a release that answers none of them degrades to the headline alone rather than failing the upgrade. Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
The installer ends in `raven web`, which opens the page, but the command a reader types the next day landed them in the terminal UI instead. One product, two entry points, two different surfaces. Bare `raven` now does what `raven web` does. Where no browser can be opened the page would be a URL nobody can reach, so it runs the native TUI instead, through the same `tui` callback as before so the startup gate is identical either way. The check is made before anything is started, so a headless box never ends up with a resident gateway it cannot show. `raven web` and `raven tui` name a surface outright and are unaffected. Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
The placeholder read "Describe your task, or have Raven build an agent for it". The second half is dropped so the input asks for one thing. Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
From the page, the upgrade helper's output goes to web.log, a file, which Python buffers by the block, while uv writes to the same file at once. An end-to-end run showed the manifest landing after uv's own lines, the one place it exists to precede. The headline and the total are now flushed. One asset now reads "1 package" rather than "1 packages". Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
`raven web` runs `raven gateway`, so nearly every reader's page is gateway-hosted, and `system.upgrade` refused all of them: its relaunch brought back a bare `raven serve`, which builds no ChannelManager, so the IM channels would have vanished. The upgrade button could never work. The gateway now hands the page its own graceful stop, its busy check and its supervisor pid on every page mount. On upgrade the gateway exits cleanly, the `raven web` supervisor reads the zero exit as a request to stand down, and the helper waits for the supervisor, not the gateway, before installing and bringing back `raven web --supervise` on the same port with the same credentials. Waiting on the supervisor matters: its cleanup removes web.json after the gateway exits, and a new supervisor started before that would lose its state file to the old one. The upgrade is refused, with its own reason, when nothing would bring the gateway back (started by hand, or `raven web --foreground`), when work the page cannot see is running (an IM turn, a sub-agent, a pending question), and when there is no executable to relaunch from. The swap and the upgrade now share one busy check instead of two copies. Verified end to end in a sandboxed uv tool install upgrading 0.2.90 to 0.2.91 from a local stand-in release: the page came back by itself in 17 seconds, still signed in, served by `raven gateway` on the same port. Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
Both lived inside the gateway's run() closure, which serves forever and so never executes under test: the busy check was covered by nothing and the "only our own parent is our supervisor" rule only by a source-text pin. They are now module-level functions with behavioural tests, and the closure keeps one-line calls into them. Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
aiohttp's cleanup waits for live handlers instead of closing websockets, so a browser tab left open held the process up for most of a minute after it was told to stop. Measured in a page-started upgrade: 46 seconds between the control plane stopping and the tab's socket closing, during which the upgrade could not start because it waits for this process to exit. The WS gateway now closes every tracked socket with 1001 from the app's on_shutdown hook; the same run dropped to 4 milliseconds. This also speeds up `raven web --stop` whenever a page is open. Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
The transport decides between "the gateway is absent, keep waiting" and "the gateway is back and refused this session" by probing /health, and it counted any answer but 401 or 403 as back. During a page-started upgrade the helper holds the port and answers 503, so the transport gave up and painted "the service is not running, start it with raven web": the one command that, mid-upgrade, starts a second supervisor beside the one coming back. A 5xx now means not back yet. Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
The page's upgrade card said "Upgrading Raven..." and nothing else until the new Raven answered, which on a slow link is tens of minutes with nothing moving: the same complaint as the terminal, moved to the page. The page had nothing to ask, because the process that served it is gone for the whole install. The helper now downloads the release itself, where the bytes can be counted, and hands uv the local files so uv only resolves dependencies. It is all or nothing: if any asset cannot be fetched, uv is given the original URLs and downloads them as before. The files live under ~/.raven/cache/upgrade/<version>, and earlier versions' are cleared. Once the old Raven has exited, the helper answers on the page's port, recorded in the upgrade marker: /upgrade/status with the phase, bytes done and total, and rate, and 503 to everything else, so the page's "is it back?" probe keeps waiting and a reload shows a self-refreshing wait page. It answers only the page's own Host, and lets the port go before the relaunch binds it. A failure is held until the page has read it (at most five seconds), so a failed upgrade no longer reloads silently onto the old version. The page polls that status, measures the bar while bytes are counted and slides it otherwise, and says when the new Raven is starting. Its twenty-minute ceiling now counts silence rather than the whole upgrade, so a slow download that is visibly moving is no longer given up on. The quick-start's update section now covers updating from the page. Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
While a turn runs, the composer's placeholder said a message typed now waits for the turn to end. That stopped being true when mid-turn sends started merging into the running turn at its next gap; the queue is only the fallback for an old gateway or a dropped socket. It now says Raven receives the new instruction directly. Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
… one The first redraw of the live download line said "0 KB/s", before a second sample existed to measure a rate from, which reads as a stall. The page already leaves the rate out until it is known; the terminal now does the same. Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
The shutdown test only started reading after the server had finished stopping, so under load it never answered the server's close frame and saw the transport drop (1006) instead of the close (1001). A page is always reading. Reading concurrently made it pass 25 of 25 runs under CPU load, and it still fails without the on_shutdown hook. Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
Every entry-point test stubs `_can_open_a_browser`, so its own logic never ran; it now has a test of both answers. `close_all` gets one for a socket that raises on close: the others still close, and the broken one is forgotten. The `_shutdown` closure's pragma moves to its def line, where it excludes the function rather than only its body. Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
The helper is started as `python -I -c <bootstrap>`, with the whole source base64-encoded into the command line, and Windows caps a command line at 32,767 characters. main was already at about 25,100; the download and status code in this branch took it to 39,730, and the Windows self-upgrade job failed with WinError 206 before the helper ran. The source is now zlib-compressed before encoding (12,953 characters). A test holds the bootstrap under 20,000 on every OS, since otherwise only a Windows machine notices. Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
gloryfromca
left a comment
There was a problem hiding this comment.
Blocking: a stalled helper can leave the page trapped in the upgrade shade indefinitely.
I reviewed the PR-specific diff against GitHub's actual base (2f5027cdf), plus the requested local github/main...HEAD diff, the upgrade/supervisor callers and history, backward compatibility, test changes, and the repository rules in AGENTS.md, CONTEXT-MAP.md, CONTEXT.md, and ui-web/CONTEXT.md. I found one blocking defect inline; I refuted the other candidates around supervisor teardown, port ownership, socket shutdown, busy-work detection, and the bare-command fallback.
Verification: uv run pytest tests/test_cli_gateway_commands.py tests/test_cli_tui_commands.py tests/test_rpc_serve_control.py tests/test_rpc_system.py tests/test_rpc_transport.py tests/test_updates_upgrade.py -x passed 380 tests after uv sync --extra dev; focused web tests passed 49 tests; the full web suite passed 3,096 tests; TypeScript type-check and RPC generation check passed; ESLint completed with four pre-existing warnings and no errors; source-language and large-file gates passed. The initial Python attempt failed in fixture setup because the default environment lacked raven_everos; no test assertion ran in that attempt.
gloryfromca
left a comment
There was a problem hiding this comment.
Blocking: the unchanged progress watcher still treats status-server liveness as progress and can wait forever.
The new revision only compresses the upgrade-helper bootstrap so it fits the Windows command-line limit. I reviewed that delta, its caller and tests, backward compatibility, and the applicable repository rules; the zlib/base64 bootstrap preserves the helper source and leaves substantial room under the platform limit. I found no new issue in this commit. The existing inline blocker remains open because ui-web/src/app/updates.ts is unchanged.
Verification for this revision: uv run pytest tests/test_updates_upgrade.py -x passed 156 tests. The generated bootstrap is 12,953 characters, and a representative fully quoted Windows launch command is 13,323 characters.
Every status answer refreshed the page's twenty-minute ceiling, even when the phase and byte count had not changed. The helper's status server is its own thread and keeps answering while a download or uv is stuck, so a stalled helper extended the deadline forever, and the running card has no close action while a reload resumes it from local storage: the reader was trapped. Only a new phase or more bytes now move the deadline, so a slow download that keeps moving is still waited out and a stuck one gives up with the manual command after twenty minutes without progress. Reported in review by gloryfromca. Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
I reviewed the fix delta and re-checked the affected watcher, helper behavior, history, backward compatibility, test changes, architecture boundary, and applicable AGENTS.md/CONTEXT rules. The prior blocker is fixed and its thread is resolved; I found no new plain error.
Verification: npm test -- src/app/updates.test.ts passed 19 tests, and npm run type-check passed.
## Summary Bump the package version from 0.2.2 to 0.2.3 for the next release. Only pyproject.toml and uv.lock change. Since v0.2.2, main merged 13 PRs: three features (#802 an experimental Curator that cultivates harness-of-harnesses workers, #805 one header per desk pane, #806 upgrade from the page with progress), four fixes (#792 and #799 name grok and copilot connection failures, #807 records the questions an unattended turn could not ask, #808 keeps the report shape on interrupted research turns) and six docs PRs. None carries a BREAKING CHANGE footer, so this is a patch bump, the way v0.2.1 and v0.2.2 were for their features. The tag and release notes follow once this lands. ## Type - [ ] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [x] Other ## Verification - `uv lock` on a clean worktree cut from origin/main: Resolved 251 packages, Updated raven v0.2.2 -> v0.2.3 - `git diff --stat origin/main..HEAD`: pyproject.toml and uv.lock, 2 insertions, 2 deletions - `PYTHONPATH=. python scripts/check_commit_messages.py origin/main..HEAD`: exit 0 - `npx commitlint --from origin/main --to HEAD --config commitlint.config.cjs`: exit 0 - `uv version` in the worktree: raven 0.2.3 - No test run: the change is version metadata only. - [ ] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk No code change. Rollback is reverting this commit before any v0.2.3 tag is pushed. - [ ] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com>
…806) ## Summary The upgrade button on the page could never work for a normal install, and when an upgrade did run, neither the page nor (on a slow link) the terminal showed anything that moved. This PR makes the page upgrade work, shows its progress, and fixes what an end-to-end run of it turned up, plus two entry-point changes from the same pass. **The page upgrade works on `raven web`.** `raven web` runs `raven gateway`, so nearly every page is gateway-hosted, and `system.upgrade` refused all of them (`gateway_hosted`): its relaunch brought back a bare `raven serve`, which builds no ChannelManager, so the IM channels would have vanished. Now: - On every page mount the gateway hands the page its own graceful stop (the path `gateway.shutdown` takes), its busy check, and its supervisor pid. Every mount, because a config swap tears the page down and disarms it. - On upgrade the gateway exits 0. The `raven web` supervisor already reads a zero exit as "stand down" (its docstring names `system.upgrade`), so it exits too. - The helper waits for the **supervisor**, not the gateway, then installs and relaunches `raven web --supervise` on the same port with the same token and cookie. Waiting on the supervisor matters: its cleanup removes `web.json` after the gateway exits, and a supervisor started before that would lose its state file, inviting the next `raven web` to start a second one. - Refused, each with its own reason, when nothing would bring the gateway back (started by hand, or `raven web --foreground`), when work the page cannot see is running (an IM turn, a sub-agent, a pending question), or when there is no executable to relaunch from. The config swap and the upgrade now share one busy check. This takes effect for upgrades that start from a release containing it; a gateway on an older release still refuses with the old message. **The page shows the upgrade's progress.** The card said "Upgrading Raven..." and nothing else until the new Raven answered, because the process that served the page is gone for the whole install. - The helper downloads the release itself, where the bytes can be counted, and hands uv the local files so uv only resolves dependencies. All or nothing: if any asset cannot be fetched, uv gets the original URLs and downloads them as before. Files live under `~/.raven/cache/upgrade/<version>`; earlier versions' are cleared. - Once the old Raven has exited, the helper answers on the page's port (recorded in the upgrade marker): `/upgrade/status` with phase, bytes done and total, and rate; 503 to everything else, so the page's "is it back?" probe keeps waiting and a reload gets a self-refreshing wait page. It answers only the page's own Host, and lets the port go before the relaunch binds it. - A failure is held until the page has read it (at most five seconds). Before, a failed install relaunched the old version and the page reloaded onto it silently. - The page polls the status, measures the bar while bytes are counted and slides it otherwise, and says when the new Raven is starting. Its twenty-minute ceiling now counts silence, not the whole upgrade, so a slow download that is visibly moving is not given up on. **Two bugs the end-to-end run exposed**, both fixed here: - *A red "the service is not running, start it with raven web" bar during the upgrade.* The transport's health probe counted any answer but 401/403 as "the gateway is back and refused this session", so the helper's 503 made it give up. A 5xx now means "not back yet". Following that bar's advice mid-upgrade is exactly what would start a second supervisor. - *The old gateway took 46 seconds to exit, blocked on the page's own open WebSocket.* aiohttp's cleanup waits for live handlers instead of closing websockets. The WS gateway now closes its sockets from `on_shutdown`; the same run dropped to 4 ms. This also speeds up `raven web --stop` with a page open. **`raven gateway` refuses a half-written install.** `raven serve` has guarded this since the upgrade marker existed, but the gateway never did, and the gateway is what `raven web` supervises. A restart inside an upgrade window came up on the half-written tree and, because the page route is chosen once, served the placeholder page until restarted by hand. The guard runs before the instance lock. It is also a precondition for the restart above. **Refusals are localized.** The page pasted the server's English `detail` into an otherwise Chinese card. Refusals it can name come from the catalogue; refusals whose detail carries facts from this run keep them. A server-side `busy` gets the same wait notice as the page's own busy turn, not the "run this in a terminal" card. **The terminal upgrade names its download size.** uv draws one bar per package and no total, so a large release reads like a stalled install (a real report: a 72 MiB upgrade at about 50 KB/s looked frozen). The helper prints each asset's size and the total before downloading, from HEAD requests bounded at 5 seconds, headline first so a hanging probe adds no silence; the terminal then shows the helper's own live progress line. Output is flushed so it keeps its place in `web.log`. **Bare `raven` opens the page.** The installer ends in `raven web`, but the command typed the next day landed in the TUI. Bare `raven` now does what `raven web` does; where no browser can be opened it runs the TUI instead, decided before anything starts. `raven web` and `raven tui` are unaffected. **Composer placeholders.** Empty: "Describe your task, or have Raven build an agent for it" becomes "Describe your task". While a turn runs: "it is sent once this turn ends" was stale (mid-turn sends merge into the running turn; the queue is only a fallback), so it now says Raven receives the new instruction directly. ## Type - [ ] Fix - [x] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification End to end, three times, in a sandboxed uv tool install (own `UV_TOOL_DIR` and `RAVEN_HOME`, no plugins, so nothing reached a real memory server), upgrading 0.2.90 to 0.2.91 from a local stand-in for the GitHub release API, clicking the real rail-foot upgrade row in a browser: - First run (fast local download): page back after 17 seconds, reloaded by itself, still signed in; served by `python -P -m raven gateway` (not `serve`) on the same port; `web.json` names the new supervisor, which is the new gateway's parent; old supervisor and gateway gone. - Second run, with the release trickled at ~200 KB/s: the progress showed, and the run found the two bugs above (the red bar, and the old gateway taking 46 seconds to exit). - Third run, same trickle, after the fixes: the card read "Downloading the new version: 0.6 / 6.1 MiB, 191 KB/s" five seconds after the click, then 2.4 / 6.1 MiB with the bar measuring; no red bar; the old gateway's page socket closed 4 ms after the control plane stopped; uv prepared the package from the local file in 157 ms; the page reloaded itself onto 0.2.91. - `raven upgrade` in a real pseudo-terminal against the same trickle: the live line redrew in place 98 times from 0.1 to 6.1 of 6.1 MiB, then uv installed from the local file. Commands, all on this branch rebased onto current main: - `make coverage` - 26,955 passed. 21 failed, and they are the same 21 that fail on unmodified main on this machine (research-tool and simulation tests, and one that assumes LibreOffice is absent); a 22nd was a load-sensitive test added here, fixed in this PR and then 25 of 25 under CPU load - `scripts/coverage_gate.py diff --base-ref github/main --threshold 90` - 100% (77 of 77 changed lines) - `npm test --prefix ui-web` - 3096 tests pass - `make lint-python` - pass; `uv run lint-imports` - 10 contracts kept, 0 broken - `make lint-ui` - 0 errors (4 pre-existing `exhaustive-deps` warnings in files this PR does not touch); `npm run lint:i18n --prefix ui-tui` - up to date - `node ui-web/scripts/check-page.mjs`, `check-css.mjs`, `check-class-namespace.mjs` after `npm run build` and `build.py` - OK - `mkdocs build --strict` - pass - `npx commitlint --from github/main --to HEAD`, `scripts/check_commit_messages.py`, `make check-source-language` and `make check-large-files` with `COMMIT_RANGE=github/main..HEAD` - pass New tests were mutation-checked; each edit below turns the test written for it red. Server: relaunching `serve` instead of the supervisor, waiting on the gateway instead of the supervisor, dropping the unsupervised refusal, the busy refusal, the no-relaunch guard, or the page-mount hand-over; not closing sockets on shutdown. Helper: never downloading, not closing the status server before the relaunch, dropping the Host check, not holding a failure, dropping the manifest or its flush. Page: counting the ceiling from the start instead of from silence, ignoring a held failure, dropping the "starting" line, a bar that ignores or overshoots the fraction, the old 5xx probe rule, server sentence instead of catalogue, `busy` routed to the failure card. Entry point: never opening the page, opening it on the wrong port. Note for reviewers running the gates locally: `make check-commits` hardcodes `--from origin/main`, and `check-source-language` / `check-large-files` default `COMMIT_RANGE` to `origin/main..HEAD`. Where `origin` is not GitHub those ranges fail on unrelated commits; pass the GitHub base explicitly. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed The quick-start's update section (English and Chinese) now covers updating from the page. ## Risk User-visible changes: - Bare `raven` opens the browser page instead of the TUI, leaving the resident gateway `raven web` always leaves. Headless machines keep the TUI; `raven tui` still names it outright. - The page upgrade restarts the gateway, briefly disconnecting IM channels. It is refused while any turn, sub-agent or pending question is running. - During a page-started upgrade the page's port is answered by the upgrade helper (status and 503s) instead of nothing. - The helper keeps the current version's wheels under `~/.raven/cache/upgrade/<version>` (about 75 MB for a release with its engines), because the tool's receipt names them as the install source. - A `raven gateway` that starts inside an upgrade window waits it out and exits instead of serving; the supervisor restarts it once the install is whole. Known edge: running `raven web` by hand in the seconds between the old supervisor exiting and the new one starting can start a second supervisor beside the relaunched one. The install itself stays protected by the upgrade marker, and the page no longer tells the reader to do this. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes Security: the status endpoint is unauthenticated but loopback-only, answers only the page's own Host (so a rebound host name gets 421), and exposes version numbers and byte counts; the relaunch carries the same token and cookie the `raven serve` path already did. Rollback: revert the squash commit. Upgrades started from a release without this change keep the old refusal, so there is no mixed-version state to unwind. ## Related Issues N/A --------- Co-authored-by: arelchan <204152633+arelchan@users.noreply.github.com> Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
## Summary Bump the package version from 0.2.2 to 0.2.3 for the next release. Only pyproject.toml and uv.lock change. Since v0.2.2, main merged 13 PRs: three features (#802 an experimental Curator that cultivates harness-of-harnesses workers, #805 one header per desk pane, #806 upgrade from the page with progress), four fixes (#792 and #799 name grok and copilot connection failures, #807 records the questions an unattended turn could not ask, #808 keeps the report shape on interrupted research turns) and six docs PRs. None carries a BREAKING CHANGE footer, so this is a patch bump, the way v0.2.1 and v0.2.2 were for their features. The tag and release notes follow once this lands. ## Type - [ ] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [x] Other ## Verification - `uv lock` on a clean worktree cut from origin/main: Resolved 251 packages, Updated raven v0.2.2 -> v0.2.3 - `git diff --stat origin/main..HEAD`: pyproject.toml and uv.lock, 2 insertions, 2 deletions - `PYTHONPATH=. python scripts/check_commit_messages.py origin/main..HEAD`: exit 0 - `npx commitlint --from origin/main --to HEAD --config commitlint.config.cjs`: exit 0 - `uv version` in the worktree: raven 0.2.3 - No test run: the change is version metadata only. - [ ] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk No code change. Rollback is reverting this commit before any v0.2.3 tag is pushed. - [ ] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com>
…s recovery (#1003) ## Summary - The A remainder of #716 after #806 landed most of A and B: a source checkout is no longer offered a page upgrade it can never take, and every refusal that has a recovery of its own names it instead of the card's fixed `raven upgrade`. - `update_notice._upgrade_command_works()` now excludes editable installs, as its docstring always claimed: an editable uv-tool install (a checkout under ./install.sh) passes the receipt check alone, and both `raven upgrade` and the page's fallback refuse it. - `system.upgrade` refusals carry an optional `data.command` the card copies: `unsupervised` names `raven upgrade` then `raven web`, and a new `editable` refusal fires before the release lookup and names `git pull` plus the platform's install script (`.\install.ps1` on Windows, `./install.sh` elsewhere). - The refusals classify before they manage: not_serving, then editable, then the gateway's unsupervised and busy answers. An editable checkout under a hand-started gateway therefore gets the checkout's own recovery, not the unsupervised sequence whose first command (`raven upgrade`) always refuses a checkout. - The failure card renders and copies the refusal's own command, line per line, keeping the default when none was given; the `editable` frame is translated (`gui.upg.why.editable`, en and zh, TUI catalogue regenerated). - Tests that simulate a release install now pin `_is_editable_install` to False: a dev venv (and CI) install raven editable from the tree, so without the pin the new pre-check would fire everywhere. ## Type - [x] Fix ## Verification - [x] Relevant tests pass locally: `uv run pytest tests/test_rpc_system.py tests/test_updates_update_notice.py` -> 84 passed (the overlap test arms the hosted-unsupervised state with the editable probe true and asserts the checkout answer wins); `npx vitest run src/state/upgradeShade.test.ts src/app/updates.test.ts` -> 25 passed (both file orders); `npm run type-check` and eslint on the four touched web files clean - [x] The new coverage fails on the base: stashing only the source changes -> the editable-refusal test, the card-command test and the overlap test fail; restoring -> pass - [x] Relevant lint / type checks pass locally: `ruff check` + `ruff format --check` on the touched Python files; `ty check` identical to a stashed baseline (zero new) - [ ] User-facing docs or screenshots are updated when needed: the refusal copy and its zh translation are the user-facing surface and are in the catalogue ## Risk - [x] Security impact considered: the command strings are constants chosen by reason, never assembled from user input - [x] Backward compatibility considered: refusals without a `command` keep today's card verbatim; the button's absence on checkouts is the docstring's promised behaviour - [x] Rollback path is clear: plain revert ## Related Issues Part of #716 (the remainder scoped in its thread; #806 already landed most of A and B) --------- Co-authored-by: Dizhan Xue <xuedizhan17@mails.ucas.ac.cn>
Summary
The upgrade button on the page could never work for a normal install, and when an upgrade did run, neither the page nor (on a slow link) the terminal showed anything that moved. This PR makes the page upgrade work, shows its progress, and fixes what an end-to-end run of it turned up, plus two entry-point changes from the same pass.
The page upgrade works on
raven web.raven webrunsraven gateway, so nearly every page is gateway-hosted, andsystem.upgraderefused all of them (gateway_hosted): its relaunch brought back a bareraven serve, which builds no ChannelManager, so the IM channels would have vanished. Now:gateway.shutdowntakes), its busy check, and its supervisor pid. Every mount, because a config swap tears the page down and disarms it.raven websupervisor already reads a zero exit as "stand down" (its docstring namessystem.upgrade), so it exits too.raven web --superviseon the same port with the same token and cookie. Waiting on the supervisor matters: its cleanup removesweb.jsonafter the gateway exits, and a supervisor started before that would lose its state file, inviting the nextraven webto start a second one.raven web --foreground), when work the page cannot see is running (an IM turn, a sub-agent, a pending question), or when there is no executable to relaunch from. The config swap and the upgrade now share one busy check.This takes effect for upgrades that start from a release containing it; a gateway on an older release still refuses with the old message.
The page shows the upgrade's progress. The card said "Upgrading Raven..." and nothing else until the new Raven answered, because the process that served the page is gone for the whole install.
~/.raven/cache/upgrade/<version>; earlier versions' are cleared./upgrade/statuswith phase, bytes done and total, and rate; 503 to everything else, so the page's "is it back?" probe keeps waiting and a reload gets a self-refreshing wait page. It answers only the page's own Host, and lets the port go before the relaunch binds it.Two bugs the end-to-end run exposed, both fixed here:
on_shutdown; the same run dropped to 4 ms. This also speeds upraven web --stopwith a page open.raven gatewayrefuses a half-written install.raven servehas guarded this since the upgrade marker existed, but the gateway never did, and the gateway is whatraven websupervises. A restart inside an upgrade window came up on the half-written tree and, because the page route is chosen once, served the placeholder page until restarted by hand. The guard runs before the instance lock. It is also a precondition for the restart above.Refusals are localized. The page pasted the server's English
detailinto an otherwise Chinese card. Refusals it can name come from the catalogue; refusals whose detail carries facts from this run keep them. A server-sidebusygets the same wait notice as the page's own busy turn, not the "run this in a terminal" card.The terminal upgrade names its download size. uv draws one bar per package and no total, so a large release reads like a stalled install (a real report: a 72 MiB upgrade at about 50 KB/s looked frozen). The helper prints each asset's size and the total before downloading, from HEAD requests bounded at 5 seconds, headline first so a hanging probe adds no silence; the terminal then shows the helper's own live progress line. Output is flushed so it keeps its place in
web.log.Bare
ravenopens the page. The installer ends inraven web, but the command typed the next day landed in the TUI. Bareravennow does whatraven webdoes; where no browser can be opened it runs the TUI instead, decided before anything starts.raven webandraven tuiare unaffected.Composer placeholders. Empty: "Describe your task, or have Raven build an agent for it" becomes "Describe your task". While a turn runs: "it is sent once this turn ends" was stale (mid-turn sends merge into the running turn; the queue is only a fallback), so it now says Raven receives the new instruction directly.
Type
Verification
End to end, three times, in a sandboxed uv tool install (own
UV_TOOL_DIRandRAVEN_HOME, no plugins, so nothing reached a real memory server), upgrading 0.2.90 to 0.2.91 from a local stand-in for the GitHub release API, clicking the real rail-foot upgrade row in a browser:python -P -m raven gateway(notserve) on the same port;web.jsonnames the new supervisor, which is the new gateway's parent; old supervisor and gateway gone.raven upgradein a real pseudo-terminal against the same trickle: the live line redrew in place 98 times from 0.1 to 6.1 of 6.1 MiB, then uv installed from the local file.Commands, all on this branch rebased onto current main:
make coverage- 26,955 passed. 21 failed, and they are the same 21 that fail on unmodified main on this machine (research-tool and simulation tests, and one that assumes LibreOffice is absent); a 22nd was a load-sensitive test added here, fixed in this PR and then 25 of 25 under CPU loadscripts/coverage_gate.py diff --base-ref github/main --threshold 90- 100% (77 of 77 changed lines)npm test --prefix ui-web- 3096 tests passmake lint-python- pass;uv run lint-imports- 10 contracts kept, 0 brokenmake lint-ui- 0 errors (4 pre-existingexhaustive-depswarnings in files this PR does not touch);npm run lint:i18n --prefix ui-tui- up to datenode ui-web/scripts/check-page.mjs,check-css.mjs,check-class-namespace.mjsafternpm run buildandbuild.py- OKmkdocs build --strict- passnpx commitlint --from github/main --to HEAD,scripts/check_commit_messages.py,make check-source-languageandmake check-large-fileswithCOMMIT_RANGE=github/main..HEAD- passNew tests were mutation-checked; each edit below turns the test written for it red. Server: relaunching
serveinstead of the supervisor, waiting on the gateway instead of the supervisor, dropping the unsupervised refusal, the busy refusal, the no-relaunch guard, or the page-mount hand-over; not closing sockets on shutdown. Helper: never downloading, not closing the status server before the relaunch, dropping the Host check, not holding a failure, dropping the manifest or its flush. Page: counting the ceiling from the start instead of from silence, ignoring a held failure, dropping the "starting" line, a bar that ignores or overshoots the fraction, the old 5xx probe rule, server sentence instead of catalogue,busyrouted to the failure card. Entry point: never opening the page, opening it on the wrong port.Note for reviewers running the gates locally:
make check-commitshardcodes--from origin/main, andcheck-source-language/check-large-filesdefaultCOMMIT_RANGEtoorigin/main..HEAD. Whereoriginis not GitHub those ranges fail on unrelated commits; pass the GitHub base explicitly.The quick-start's update section (English and Chinese) now covers updating from the page.
Risk
User-visible changes:
ravenopens the browser page instead of the TUI, leaving the resident gatewayraven webalways leaves. Headless machines keep the TUI;raven tuistill names it outright.~/.raven/cache/upgrade/<version>(about 75 MB for a release with its engines), because the tool's receipt names them as the install source.raven gatewaythat starts inside an upgrade window waits it out and exits instead of serving; the supervisor restarts it once the install is whole.Known edge: running
raven webby hand in the seconds between the old supervisor exiting and the new one starting can start a second supervisor beside the relaunched one. The install itself stays protected by the upgrade marker, and the page no longer tells the reader to do this.Security: the status endpoint is unauthenticated but loopback-only, answers only the page's own Host (so a rebound host name gets 421), and exposes version numbers and byte counts; the relaunch carries the same token and cookie the
raven servepath already did. Rollback: revert the squash commit. Upgrades started from a release without this change keep the old refusal, so there is no mixed-version state to unwind.Related Issues
N/A