Skip to main content

Sandbox Cold-start HUD

When the agent needs a sandbox to continue, the backend emits sandbox.launch / sandbox.ready on the stream. Core folds them into sandboxPhase, and the SDK floats a self-contained "launching" HUD into the chat view's bottom-right, playing a short ready beat before it fades out.

This is automatic — consumers wire nothing.

Sandbox Cold-start HUD

When the agent needs a sandbox to continue, the backend emits sandbox.launch / sandbox.ready; core folds them into sandboxPhase and the SDK floats a self-contained "launching" HUD into the chat view's bottom-right. The 1s threshold is the point: a warm sandbox is often ready in 100–300ms, and flashing an animation then is just noise — anything finishing inside the threshold stays silent.

Drive the phase by hand
  • Cold start: press launching, wait past 1s, then ready → stage walks launching → ready → leaving → hidden.
  • Warm start: press launching then ready immediately → stage stays hidden the whole time; nothing flashes.
import { useSandboxLaunch } from '@asgard-js/react';
import type { SandboxPhase } from '@asgard-js/core';

// phase comes from channel.sandboxPhase$ (or getSandboxPhase())
const { stage, visible } = useSandboxLaunch(phase, {
  thresholdMs: 1000, // launches finishing sooner never show
  readyBeatMs: 900,  // how long "ready" lingers before the exit
  exitMs: 600,       // fade-out duration
});

// stage: 'hidden' | 'launching' | 'ready' | 'leaving'

The built-in HUD appears on its own — no wiring needed; the hook is for consumers drawing their own launch cue. It is independent of the RunningIndicator at the footer seam: this one tracks only "is the sandbox stuck cold-starting", that one binds the whole run's connection.

useSandboxLaunch state

The 1s Threshold

A warm sandbox is often ready in 100–300ms. Flashing an animation then is just noise, so the phase must stay launching past the threshold (1000ms by default) before the HUD shows; anything finishing inside it stays completely silent.

Conversely, the HUD does not vanish the instant ready arrives: it lingers for a ready beat (900ms by default) so the user sees "done", then fades out. If ready beat the threshold (the HUD never showed), the whole thing stays quiet.

phase: idle ──► launching ─────────────────► ready ──► idle
│◄── 1000ms ──►│
stage: hidden hidden launching ready ──► leaving ──► hidden
(animation) (beat) (fade)

sandboxPhase

type SandboxPhase = "idle" | "launching" | "ready";

Available from Channel as either a subscription or a snapshot:

channel.sandboxPhase$.subscribe((phase) => { /* … */ });
const phase = channel.getSandboxPhase();

Drawing Your Own Launch Cue

To replace the built-in HUD, useSandboxLaunch maps the phase onto a display stage:

import { useSandboxLaunch } from "@asgard-js/react";

const { stage, visible } = useSandboxLaunch(phase, {
thresholdMs: 1000, // launches finishing sooner never show
readyBeatMs: 900, // how long "ready" lingers before the exit begins
exitMs: 600, // fade-out duration; must match your CSS
});

// stage: 'hidden' | 'launching' | 'ready' | 'leaving'
// visible: convenience flag for stage !== 'hidden'

Every timer is cleaned up on unmount or phase change.

Independent From the Run Indicator

The connection line at the footer seam (RunningIndicator) binds the whole run's connection; the HUD tracks only "is the sandbox stuck cold-starting". They can appear together and never interfere.

The Launched-Sandbox List

launchedSandboxes$ carries each sandbox's identity and capabilities:

interface LaunchedSandbox {
sandboxName: string; // authoritative key; every fs / browser API locates by it
sandboxBlueprintName: string; // the blueprint that created it — usually more readable
workingDirectory: string; // the File Explorer tree root
editorServerEnabled: boolean;
browserEnabled: boolean; // gates the browser-handoff card
}
It does not share a source with sandboxPhase

sandboxPhase is folded straight from the SSE events; launchedSandboxes$ is not. This list's sole authority is GET /channel/metadata — SSE events never write into it directly. asgard.sandbox.launch is only a hint: it records the name as pending and triggers one metadata refetch, and whatever comes back decides who is live.

So never infer "which sandboxes exist right now" from the sandbox.launch events yourself — that diverges from the backend's authoritative state.

The File Explorer's sandbox dropdown is fed by this list.

See Also