feat(cloud): a box reports its agent status so the sidebar can show it
What
Cloud workspace rows get an agent-status dot without the sidebar holding a socket to the box.
- Box → API: host-service in sandbox mode (
runtime/sandbox-agent-status) derives the workspace's status — the most urgent across its terminals, from the same lifecycle events a host's dot uses — andPOSTs it to/api/cloud-workspaces/:id/agent-statuswith its host secret. Only on change, settled 2 s, capped at one report per 5 s per box. - API:
reportSandboxAgentStatusverifies the secret, writescloud_workspaces.agent_status/agent_status_at(migration0126), and nudges the realtime channel with the value. - Realtime Worker: nudges can carry patches (
updates, last write per workspace, coalesced with the existing 500 ms window). A patch never turns into a refetch. - Desktop:
RealtimeNudgespatchescloudWorkspace.listin the cache; the sidebar status provider uses the reported status for sandbox rows, which are now in its target list. Open boxes keep their live subscription as before. - One vocabulary:
@superset/shared/agent-statusowns the status union, urgency order and event→status mapping.tabs-types.ts(PaneStatus,STATUS_PRIORITY) andderiveTerminalAgentStatusnow read from it instead of restating it. docs/cloud-sandbox-mismatches.mdgets the entry.
Why
Agent notifications in the sidebar didn't work for cloud. Polling the boxes was ruled out (tickets + sockets per box, wakes stopped boxes, and it's the pattern that has bitten us); a DO-per-sandbox was judged premature. This keeps the API out of any hot loop: the write volume is bounded by the box-side cap, and clients never refetch on a status change. If the route's invocation count ever says otherwise, the Worker+DO version is a contained swap behind the same client contract.
Verified
- Reporter, at the wire (scratch harness against an in-memory
TerminalAgentStoreand a local listener): a burst of Start/Stop/Start → oneworkingreport at 2 s; aPermissionRequestinside the window → deferred to the 5 s cap; an unchanged event → no report; both agents stopping →review; host secret on every call. - API → Worker → desktop, in the dev app (seeded ready row; POST as the box with a minted host secret; CDP on the renderer):
working→ spinner on the closed row,permission→ indicator,null→ cleared, 0cloudWorkspace.listfetches throughout; wrong secret → 401. Direct Worker subscription shows the frame:{"type":"nudge","kinds":[],"updates":[{…"agentStatus":"working"…}]}. - Root typecheck 42/42,
bun run lintclean, desktop 448 / shared 1108 / host-service 96 tests pass. - Not yet shown with a real box: the reporter reaches a box only through a host-service release.
https://claude.ai/code/session_01RL946q6aRucLVJs659sW24
Summary by cubic
Cloud workspace rows in the sidebar now show an agent-status dot. A box reports its own status instead of the sidebar polling it or holding a socket to it, which would keep the VM awake.
New Features
- host-service in sandbox mode derives the most urgent status across its terminals and POSTs it to
/api/cloud-workspaces/:id/agent-statuswith its host secret, coalesced to at most one report per 5 seconds per box. - The API stores the last value (
agent_status/agent_status_at, migration0126) and the realtime nudge carries it as a patch; open clients patch their cache and never refetch on a status change. - Cloud rows join the sidebar status target list;
reviewandfailedmark the row unread, and seen times come from the host's clock. @superset/shared/agent-statusnow owns the status union, urgency order, and event mapping that the desktop and the reporter share.- Reaches a box only through a host-service release.
Summary by CodeRabbit
- New Features
- Cloud workspaces now display agent activity in the dashboard sidebar, including working, review, failed, and permission statuses.
- Status changes update in real time while the dashboard is open.
- Review and failed statuses contribute to unread indicators; marking a workspace as seen clears review alerts.
- Cloud workspace activity may take a few seconds to appear when the sandbox is closed.