feat(onboarding): warm agent runtimes eagerly and make waiting legible - #362
feat(onboarding): warm agent runtimes eagerly and make waiting legible#362wibus-wee wants to merge 3 commits into
Conversation
A client running its own deadline over `machine/acp-capabilities-refresh` disagreed with the machine about who owns the truth. The Electron local transport used a 120s socket timeout while the machine could still be inside a 300s cold `npx` init followed by a 120s `session/new`, so the client reported its own timeout for a request the machine was still working on and would have answered — with its real failure reason — minutes later. The machine owns the deadline. `packages/shared/src/acp-startup-budget.ts` is now the one binding for its worst case, and both client paths (Electron local control and the Machine RPC plane) derive a backstop that stays strictly above it, so reaching that backstop means the daemon never replied at all. The CLI's own `initialize` / `session/new` defaults and the cold-`npx` ceiling read from the same module, so the two ends cannot drift into different worst cases. Runtime download is deliberately outside the budget: it streams progress frames, which continuously reset an inactivity-based transport timeout, so a slow download cannot expire the request. Model: claude-opus-5
Three things made first-run setup feel like a black box behind a Test button. Prefetch ran one runtime at a time, so the wait a user actually feels — wall clock — was the sum of three downloads instead of the longest one. They are independent artifacts keyed by agent type and installs are already deduped per machine and agent, so they now all start together; selecting a provider only moves it to the front of the launch order and never restarts work in flight. This stays off the renderer's critical path because progress fans out only to live listeners and otherwise just records a snapshot, so a concurrent download during the intro ceremony cannot become a render storm. A runtime progress status that already failed (`error`, `unsupported-platform`, `incompatible-host`) mapped back to `checking-runtime`, presenting a known failure as work still in progress. It resolves to a distinct `runtime-failed` phase wearing the failure tone; the final response still owns the durable reason. The waiting itself had no vocabulary. `AgentReadinessMark` gives it one, using assets that already ship: saturation carries readiness, so the logo wall reads as an inventory filling in as each warmed runtime lands rather than as a queue of pending work, and one ring carries the wait — filling when there is a real denominator (a download), orbiting when there is not (the ACP handshake), so a user learns the shape once and recognises both. A stage with no denominator reports elapsed seconds instead of inventing a percentage, because a wait you can measure is bounded and an open-ended one has no floor. Animation is CSS-only on `transform`, with a reduced-motion opt-out. Model: claude-opus-5
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
4f5c332 to
65511d8
Compare
Replace raw provider setup failure enums with localized, actionable recovery copy on the onboarding summary. Give a bypassed startup step a reduced-motion-aware spring probe that reaches toward the next stage and rebounds while the CLI remains delayed. Add Storybook and regression coverage for both states. Model: gpt-5
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 310fa2d776
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| * slow download cannot expire the request. | ||
| */ | ||
| export const ACP_CAPABILITIES_REFRESH_MACHINE_BUDGET_MS = | ||
| ACP_COLD_NPX_INIT_TIMEOUT_MS + ACP_NEW_SESSION_TIMEOUT_MS; |
There was a problem hiding this comment.
Include npx recovery retries in the startup budget
When a cold npx initialize times out, runNpxStartupWithRecovery may purge the cache and make up to three attempts, each receiving the 300-second cold timeout; a successful final attempt may then spend another 120 seconds in session/new. This formula budgets only one attempt, so the new 450-second client backstop can expire during the second attempt while the machine is still executing its intended recovery, again returning a client timeout instead of the machine's final failure reason. Account for the full retry policy (and cleanup overhead) when deriving the backstop.
AGENTS.md reference: AGENTS.md:L42-L46
Useful? React with 👍 / 👎.
Why
First-run setup asked the user to press Test and then showed a single
Checkingbadge over five very different stages. Anything that went wrong looked the same as work in progress, and the wait had no shape.Three separate things were wrong underneath.
What changed
1. Runtimes warm concurrently (
feat)Onboarding's built-in runtime prefetch ran one agent at a time, so the wait a user actually feels — wall clock — was the sum of three downloads instead of the longest one.
They are independent artifacts keyed by agent type and installs are already deduped per machine and agent, so they now all start together. Selecting a provider only moves it to the front of the launch order; it never restarts or waits behind work already in flight.
This stays off the renderer's critical path:
handleMachineAcpBinaryProgressfans out only to live listeners and otherwise just records a snapshot, so a concurrent download during the intro ceremony cannot become a render storm.2. A failed runtime stops reporting itself as still checking (
fix)providerTestActivityFromProgressmappederror/unsupported-platform/incompatible-hostback tochecking-runtime, which presented a known failure as work still in progress. It now resolves to a distinctruntime-failedphase wearing the failure tone. The final refresh response still owns the durable reason.3. One owner for the ACP startup deadline (
fix)The client and the machine ran two different clocks over one
machine/acp-capabilities-refresh. The Electron local transport used a 120s socket timeout while the machine could still be inside a 300s coldnpxinit followed by a 120ssession/new— so the client reported its own timeout for a request the machine was still working on and would have answered, with its real failure reason, minutes later.packages/shared/src/acp-startup-budget.tsis now the single binding for the machine's worst case. Both client paths derive a backstop that stays strictly above it, so reaching that backstop means the daemon never replied at all. The CLI's owninitialize/session/newdefaults and the cold-npxceiling read from the same module, so the two ends cannot drift.Runtime download is deliberately outside the budget: it streams progress frames, which continuously reset an inactivity-based transport timeout, so a slow download cannot expire the request.
4. A vocabulary for waiting (
feat)AgentReadinessMarkgives the waiting a shape, using assets that already ship:Animation is CSS-only on
transform, with aprefers-reduced-motionopt-out. Storybook covers every state.Verification
pnpm typecheck,pnpm check:quick(lint, i18n, code-collab, platform, public-boundary), andpnpm format:checkclean.@lody/components@lody/sharedapps/cli@lody/electronRe-verified after rebasing onto current
main; per-file diff is byte-identical to the tested tree.Deliberately not in this PR
inFlightAcpRefresh) — what is missing is a cancellation path from client to daemon. Adding a button before that exists would claim something untrue.machine/acp-capabilities-refreshresolves an existing config by id, and during the ceremony none exists yet. That needs a config-less warmup message and its own protocol capability, which belongs in its own change.