feat(frontend): Show startup states before an agent streams - #6073
Conversation
A cold agent session spends ~15s booting its sandbox while the chat showed a wordless three-dot bubble, so the run read as stalled (#6047). The empty turn now advances through a startup story — "Getting things ready" to "Almost ready" — and clears the moment content, an error, or a Stop arrives. A warm session keeps the existing dots, gated on the liveness poll already being fetched. The phases are TIMED GUESSES, not observed state: sandbox boot and ordinary model latency share the one window between the stream's `start` chunk and its first content chunk, so nothing client-side can tell them apart and no backend signal is involved. Copy deliberately avoids naming our infrastructure ("sandbox", "harness") since that means nothing to the reader; a test enforces it. The label is derived per render off a per-turn clock atom, with timeouts scheduled to each phase boundary rather than a poll, so it cannot go stale and costs no timers once the last phase lands.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository YAML (base), Organization UI (inherited) Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review. 📝 WalkthroughSummary by CodeRabbit
WalkthroughThe change adds timed startup labels for cold agent turns. Session clocks follow turn lifecycle states. Pending turns render accessible startup activity with shimmer styling. Warm turns retain the existing loading behavior. ChangesCold Startup Activity
Estimated code review effort: 3 (Moderate) | ~25 minutes Merge Risk: 🔵 Low · up to The PR improves startup feedback, but warm sessions may briefly show misleading startup labels and users who prefer reduced motion may still see animated dots. The change is mergeable with explicit owner awareness or follow-up on these bounded issues. Sequence Diagram(s)sequenceDiagram
actor User
participant useAgentChatSession
participant startTurnClockAtom
participant useStartupPhase
participant AgentMessage
participant StartupActivity
User->>useAgentChatSession: submit agent turn
useAgentChatSession->>startTurnClockAtom: record cold-turn start time
AgentMessage->>useStartupPhase: read session startup phase
useStartupPhase->>useStartupPhase: schedule next phase boundary
useStartupPhase-->>AgentMessage: return current phase label
AgentMessage->>StartupActivity: render startup label
StartupActivity-->>User: announce and display phase
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 0fb16222-9fdd-43c7-b1d4-c216ea9b4896
📒 Files selected for processing (10)
web/oss/src/components/AgentChatSlice/assets/startupPhases.test.tsweb/oss/src/components/AgentChatSlice/assets/startupPhases.tsweb/oss/src/components/AgentChatSlice/components/AgentMessage.tsxweb/oss/src/components/AgentChatSlice/components/TurnActivity.test.tsxweb/oss/src/components/AgentChatSlice/components/TurnActivity.tsxweb/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.tsweb/oss/src/components/AgentChatSlice/hooks/useStartupPhase.tsweb/oss/src/components/AgentChatSlice/state/turnClock.test.tsweb/oss/src/components/AgentChatSlice/state/turnClock.tsweb/oss/tailwind.config.ts
Included review availability: Your plan includes up to 8 reviews per rolling hour; 7 remain after this review.
Railway Preview Environment
|
A review pass measured this code at 1.82 and 1.09 comment-to-code against a folder norm of 0.28-0.42, and web/CLAUDE.md sets a hard rule against multi-line prose comments. Kept the two genuinely surprising constraints (the labels are timed guesses with no backend signal, and why "Working" at 0ms is safe) and cut the rest to one line each. Also dropped three tests that could not fail independently: two loops brute- forcing monotonicity over a five-line scan, and a check that a Record keyed by session id is keyed by session id. Removed two assertions matching literal Tailwind class names, which would have failed on a cosmetic rename while still passing if the tailwind config entry were deleted.
Incidental eslint --fix output picked up while working elsewhere in this file. No behaviour change.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
web/oss/src/components/AgentChatSlice/components/TurnActivity.test.tsx (1)
2-6: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winShorten the test rationale comment.
This five-line block exceeds the repository rule for in-code comments. Keep only the non-obvious
renderToStaticMarkupconstraint in one short line; the test intent is clear from the assertions.As per coding guidelines: “Keep in-code comments to at most one short line; use longer comments only for genuinely surprising constraints such as bugs, races, or ordering requirements.”
Source: Coding guidelines
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 57c3f9b6-b6c0-4eee-b7aa-de864fff0d4d
📒 Files selected for processing (7)
web/oss/src/components/AgentChatSlice/assets/startupPhases.test.tsweb/oss/src/components/AgentChatSlice/assets/startupPhases.tsweb/oss/src/components/AgentChatSlice/components/TurnActivity.test.tsxweb/oss/src/components/AgentChatSlice/components/TurnActivity.tsxweb/oss/src/components/AgentChatSlice/hooks/useStartupPhase.tsweb/oss/src/components/AgentChatSlice/state/turnClock.test.tsweb/oss/src/components/AgentChatSlice/state/turnClock.ts
💤 Files with no reviewable changes (2)
- web/oss/src/components/AgentChatSlice/assets/startupPhases.test.ts
- web/oss/src/components/AgentChatSlice/state/turnClock.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- web/oss/src/components/AgentChatSlice/components/TurnActivity.tsx
- web/oss/src/components/AgentChatSlice/hooks/useStartupPhase.ts
- web/oss/src/components/AgentChatSlice/state/turnClock.ts
- web/oss/src/components/AgentChatSlice/assets/startupPhases.ts
Included review availability: Your plan includes up to 8 reviews per rolling hour; 6 remain after this review.
…g comments CodeRabbit review on #6073. The three pulsing dots animated unconditionally while the label beside them was already motion-safe, so a reduced-motion reader still got movement. SessionRunSpinner sets the convention here; the dots now follow it. Also cut the narrative comments the earlier trim missed: the PendingTurn docblock, the test-suite rationale, and the two tailwind ones. Kept the client-side timed-guess limitation and the one-line note on why "Working" sits at 0ms, which a test pins.
Runner startup-event investigationThe runner can report useful startup state natively. I recommend prioritizing a focused follow-up, but keeping the first version coarse. What the architecture already knowsThe shared sandbox-agent path has authoritative boundaries for:
These boundaries live in The stream protocol also already supports transient custom data events. The Python Vercel adapter converts a runner Gap and effortThe current One safeguard is required: A useful partial implementation looks low to medium effort:
A richer breakdown such as mounts, instructions, tools, and provider readiness is medium effort. Some phases overlap or differ by local versus Daytona execution, so we should add only boundaries that the runner can prove. RecommendationPrioritize the coarse follow-up after this PR. The architecture supports it cleanly, and partial information is already valuable. Do not reproduce the current four timed labels as server events. Start with two or three observed states, measure their durations, then add finer phases only where the runner owns a reliable boundary. |
Product review: when the startup messages appearThe detailed labels do not appear on every message by design. At submission,
After a successful first turn, There are two accuracy limits:
A revision or configuration change does not reliably trigger these labels. If the runner can reconcile the existing live environment, the client sees Product recommendationI would not reject this PR for showing the labels on every message, because it does not intentionally do that. I would treat it as an interim improvement with inaccurate phase copy. Before merge, either:
The eventual product behavior should be event-driven: show startup messages only when the runner confirms a startup or restore boundary, and use the plain activity indicator for ordinary model latency. |
bekossy
left a comment
There was a problem hiding this comment.
Thanks for the work here. The per-turn clock and cleanup behavior look good, but I think we should address one functional issue before merging: unresolved liveness is still treated as isAlive: false, so a warm session can show the cold-start ladder if the user sends before the liveness query resolves.
Could we keep the existing dots while liveness is loading, and only show the startup ladder once we know the session is not alive?
Separately, the timed labels are fine as an interim UX if we explicitly accept them as approximations, but they are not confirmed backend startup states yet
feat(agent): show observed startup states
Closes #6047
Context
Starting a new agent session takes around 15 seconds while its sandbox boots, worst with Claude Code. For that entire wait the chat rendered
@ant-design/x'sBubble loading: three animated dots and no words. The session looked stalled, and the only way to know it wasn't was to keep waiting.A note on the issue text, since a reviewer checking against it will notice. The issue says the chat "shows elapsed time". It does not. There is no timer anywhere in
AgentChatSlice; the current state is the wordless dots. Requirement 4 ("do not show the elapsed-time treatment") is honored here in the sense that no number is ever rendered.Changes
The empty assistant turn now says what is happening, and advances as the wait goes on.
The line clears the instant real content arrives, and equally on an error or a Stop. A warm session skips the ladder entirely and keeps today's dots, gated on
nest.isAlivefrom the session-liveness poll the tab dots already run.These labels are timed guesses, not observed state, and that is deliberate. Nothing client-side can distinguish a cold sandbox boot from ordinary model latency: both occupy the same window between the stream's
startchunk and its first content chunk. Reporting real per-phase progress would need the runner to emit startup events, which is a cross-stack change this PR does not make. The copy is written to fit that constraint rather than to overstate what we know.Two properties are load-bearing and worth understanding before editing the ladder:
"Working" sits at 0ms with no grace window because it is the one line true of every turn, warm or cold. That is what makes an immediate label safe. A turn this code misjudges as cold and which answers in 300ms still only ever said something correct. A first label claiming a startup step would not survive that case, so a test pins both the 0ms mark and the word.
The copy avoids our own vocabulary. "Sandbox" and "harness" name our infrastructure, not anything a reader has a model of, and "workspace" is already taken in this product for the org-level container. A test fails if any label reintroduces those terms.
The label is derived on every render off a per-turn clock atom, never stored, so it cannot lag the clock. The effect exists only to force a render at each boundary, and it sleeps exactly that long instead of polling. Once the last phase lands it stops scheduling, so a turn that then runs for minutes costs no timers.
Tests
startupPhases.test.ts,turnClock.test.ts, andTurnActivity.test.tsx. All pass.tsc --noEmitonweb/oss: 0 errors. eslint clean on every touched file.@testing-library/react, so component tests userenderToStaticMarkup. That constraint is whyStartupActivitytakes its label as a prop and owns no timer.submittededge and always replaces.What to QA
hi, and watch the line advance. It should reach "Starting the agent" at about 2s and vanish the moment the answer begins.prefers-reduced-motion. With reduced motion the shimmer stops and the label renders as plain text; it must not come out invisible.Notes for the reviewer
The timings are calibrated to a design prototype's clock, not to measurement, so they want a human eye on a real cold start. The 2s beat is the one most likely to feel early: the sandbox is genuinely still booting then, so "Starting the agent" arrives slightly ahead of the truth.
The issue words the first two states as "Sandbox starting" and "Sandbox started". We moved off that wording on purpose, because "sandbox" means nothing to a non-technical user.