Foxglove Embedded Viewer

SkillAI & models

Use when embedding, operating, or debugging the Foxglove viewer in the NPA agent (the @foxglove/embed TypeScript SDK, MCAP recordings, the npa-foxglove-embed container, or /api/foxglove/* endpoints).

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Foxglove Embedded Viewer skill

What this skill tells your AI

The instructions your AI receives, as published by nebius/nebius-physical-ai in skills/tools/foxglove/SKILL.md and read by ahel’s review.

NPA embeds Foxglove with the official @foxglove/embed TypeScript SDK: the SDK creates an iframe pointing at a Foxglove deployment and drives it over postMessage (setDataSource, selectLayout, seekPlayback, ready/error).

What NPA ships vs what Foxglove hosts — NPA serves the MIT-licensed SDK (fetched unmodified from npm, sha512-verified) plus your recordings. The viewer application is Foxglove's: either https://embed.foxglove.dev/ (users sign in to a Foxglove organization on a plan that allows embedding) or your self-hosted deployment. Without a configured embed source the viewer pane says so — it never renders an empty frame and calls it a viewer.

Which viewer when

NPA ships two MCAP viewers; they are complements, not alternatives:

skills/tools/lichtblick/SKILL.md (OSS)this skill (official Foxglove)
What runsLichtblick web build (MPL-2.0) served by the agent, in-pageFoxglove's own app in a cross-origin iframe, driven by @foxglove/embed
Accountnonea Foxglove org on a plan that allows embedding
Renders MCAPyes, out of the boxyes, after sign-in
Recording pathsame-origin /lichtblick/recordings/ (no CORS)/foxglove/data/ (CORS + byte ranges)
Use it fordefault operator playback, CI, air-gappedcustomers standardized on Foxglove (layouts, extensions, org sharing)

The agent's Foxglove pane picks between them at runtime (/api/foxglove/configviewer_backend): the official app when NPA_FOXGLOVE_EMBED_SRC is configured and the SDK assets are installed, otherwise the self-hosted OSS viewer, otherwise an explained unavailable state. NPA_FOXGLOVE_VIEWER_BACKEND=foxglove-sdk|self-hosted forces one.

When To Use

  • Adding/changing the agent's Foxglove viewer pane or /api/foxglove/* endpoints
  • Publishing .mcap / .bag recordings for playback in the agent
  • Building/running the npa-foxglove-embed container
  • Converting run artifacts to MCAP (npa workbench foxglove convert-run)
  • Debugging "viewer unavailable", CORS, or byte-range playback problems

Architecture

PieceWhere
Pinned SDK version + integrity + asset probenpa/src/npa/workbench/foxglove/__init__.py
One install recipe (fetch + verify + extract)npa/docker/workbench/foxglove-embed/install-sdk.sh
Shared browser glue (mountFoxgloveViewer)npa/src/npa/cli/assets/foxglove/npa-foxglove-host.js
Standalone host pagenpa/src/npa/cli/assets/foxglove/index.html
Container (caddy, :8099, non-root)npa/docker/workbench/foxglove-embed/
Agent backend helpers (shipped module)npa/src/npa/agent_backend/foxglove.py (shim: cli/agent_foxglove.py)
Agent routes (shipped module)npa/src/npa/agent_backend/foxglove_routes.py
nginx serving policynpa/src/npa/cli/agent_site.py
Agent UI pane + lazy SDK importnpa/src/npa/cli/agent_ui.html (ensureFoxgloveViewer)
MCAP writer / readernpa/src/npa/workbench/foxglove/{mcap_writer,inspect}.py
CLI / SDKnpa/src/npa/cli/workbench/foxglove.py, npa/src/npa/sdk/workbench/foxglove.py

On the agent VM, bootstrap installs assets to /opt/npa-agent/foxglove/: sdk/ (SDK), app/ (glue), data/ (published recordings). nginx serves /foxglove/ behind basic auth and /foxglove/data/ without auth but with CORS, Accept-Ranges, gzip off, and an OPTIONS preflight — the cross-origin viewer iframe cannot send credentials and streams recordings with Range requests. Published names are random (<token>-<stem>.mcap) and pruned to the newest few.

Agent endpoints

RoutePurpose
GET /api/foxglove/configEverything the UI needs to mount a viewer: viewer_backend, self_hosted_url, SDK/embed settings, data source; available:false + reason when neither backend can render
GET /api/foxglove/statusReadiness + active recording (also grounds the foxglove_viewer chat intent)
POST /api/foxglove/load-artifactLoad a discovered .mcap/.bag/.db3/.ulg/.ulog artifact (run_id + s3_uri, or run_id + key)
POST /api/foxglove/convert-runConvert the active run's local artifacts to MCAP and load it
POST /api/foxglove/exportAuthorize and prepare/reuse the exact selected MCAP; return the viewer config plus phase timings, download it, open its official remote-file link, or explicitly upload/index it
POST /api/foxglove/livePoint the viewer at a public ws:///wss:// Foxglove or ROS-bridge URL

Configuration (no secrets): NPA_FOXGLOVE_EMBED_SRC, NPA_FOXGLOVE_ORG_SLUG, NPA_FOXGLOVE_LIVE_URL, NPA_FOXGLOVE_COLOR_SCHEME, NPA_FOXGLOVE_LAYOUT_STORAGE_KEY, NPA_FOXGLOVE_ENABLED, and the positive, finite NPA_FOXGLOVE_CLOUD_IMPORT_TIMEOUT_SECONDS (default 300, maximum 3600); or npa agent bootstrap --foxglove-embed-src <url> --foxglove-org-slug <slug>.

CLI

npa workbench foxglove config --output json
npa workbench foxglove install-sdk --dest /opt/npa-agent/foxglove/sdk
npa workbench foxglove convert-run --input-path <run-dir> --output-path run.mcap --fps 10
npa workbench foxglove export-run --input-path <run-dir> --output-path run.mcap
npa workbench foxglove open --recording-id <indexed-recording-id>
npa workbench foxglove inspect --input-path run.mcap

open uses Foxglove's official foxglove-stream recording deep-link contract for an explicitly indexed Cloud recording. Agent export persists exactly one canonical run artifact at <run-prefix>/<run-id>/reports/sim2real.mcap, with sim2real.mcap.provenance.json beside it. A valid native MCAP is reused; otherwise real S3 run artifacts are converted and the run-list cache is invalidated. Lichtblick, the download transport, and Cloud import use identical canonical bytes and report the same SHA-256. The artifact card's ordinary View in Foxglove action opens the embedded SDK pane and binds the exact selected MCAP as a remote-file source. Before any no-download reuse, the backend authorizes the immutable run reference and exact key. Exact artifact cards use a narrow fail-closed authorization check: verify the selected project is tenant-visible, verify only its selected bucket, and probe that bucket's current read scope. Do not rebuild the tenant-wide access report or probe unrelated buckets on the exact artifact-inventory or playback path. Once discovery has issued this exact source tuple, browser verification must reuse it instead of repeating tenant-wide run search. A successfully rendered exact artifact card refreshes a 30-second, full-source-keyed access proof and exact inventory for its immediate playback click; explicit access refresh clears access proofs. Rendering the cards and binding their actions precedes slower run-detail enrichment, so playback never waits behind that optional UI work. After the 30-second inventory window expires, exact playback must re-probe only the authorized prefix/run-id/ scope and list that exact run. It must never fall back to rebuilding the bucket-wide run index for a source-qualified card. The backend then reads a fresh strong object-store identity (ETag or version id) and verifies the published bytes against the persisted SHA-256 and provenance. An unchanged selection skips download, conversion, and publication; a changed object identity or mismatched local byte invalidates the cache. A canonical cache miss prepares and applies the local result once rather than downloading it again. The export response includes the matching viewer config, cache_reused, and phase timings so the UI does not need a redundant config request.

The SDK iframe mounts before backend preparation finishes and remains mounted across exact-card selections. The UI sends setDataSource and selectLayout only when their identities change. Status, artifact actions, and visualization summary participate in normal layout above the viewer canvas; they must never be absolutely or fixed-positioned over playback controls. Success text stays compact while its full accessible value remains available through the status element.

The separate Open in Foxglove action uses Foxglove's documented ds=remote-file&ds.url=<public HTTPS MCAP> link. The recording URL is encoded exactly once, contains no credentials, and is the same CORS + byte-range transport used by the official embed SDK. The button synchronously reserves a popup during the user gesture and reports blocked or failed navigation honestly.

For recordings that advertise npa.foxglove.robot-motion.v3, the server idempotently creates the versioned NPA Physical AI robot motion v3 organization layout and adds its non-secret layoutId to the hosted link. The layout seeds a large 3D robot/trajectory panel, discovered source-camera tabs, metrics, phase/state, and logs. Existing versioned layouts and SDK storageKey arrangements are reused without forcing or overwriting user changes. If the layout API is unavailable, the link still opens the rich topics and the UI says that a saved layout must be selected after sign-in.

An explicit backend cloud_import mode can additionally upload the MCAP once under a stable content key, reuse unchanged or in-progress imports, and wait for indexed complete state. A server-side API token is required for shared-layout creation and the optional Cloud import at tokens.FOXGLOVE_API_TOKEN in ~/.npa/credentials.yaml (mode 0600). It is never part of browser config, deep links, subprocess arguments, shared workbench env, or the agent's foxglove.env. If it is already exported in the operator shell, persist it with npa configure --no-interactive --save-env-credentials; never pass its value as an argument.

convert-run packs real artifacts into Foxglove well-known schemas: foxglove.CompressedImage on /camera/<name> (PNG/JPEG passed through, PPM/BMP/TIFF transcoded), foxglove.Log on /log, and npa.RunMetrics.<name> on /metrics/<name>. When a real npa.sim2real.action_rollout.v1 artifact is present, it also emits the npa.foxglove.robot-motion.v3 contract: an explicitly labelled action-derived diagnostic robot (foxglove.SceneUpdate), end-effector pose and cumulative trajectory (PoseInFrame / PosesInFrame), foxglove.JointStates, actuator commands, run phase/progress, camera transforms, metrics, and logs on one coherent clock. This schematic must remain labelled as uncalibrated diagnostic kinematics; camera frames and copied simulator-ground-truth fields retain their source fidelity. An explicitly declared npa.foxglove.pointcloud-series.v1 artifact becomes foxglove.PointCloud on /trajectory plus its real frame relationship as foxglove.FrameTransform on /tf; its provenance must describe the coordinate semantics and must not imply world geometry when the points represent state space. Run artifacts carry no capture time, so frame timestamps come from --fps and are recorded as timestamps=synthetic-fps in the MCAP metadata — never present them as sensor time. Needs the optional extra: pip install "npa[foxglove]".

Container

docker build -t npa-foxglove-embed:0.58.0 -f npa/docker/workbench/foxglove-embed/Dockerfile npa
docker run --rm -p 8099:8099 -v $PWD/recordings:/srv/data:ro npa-foxglove-embed:0.58.0
# http://localhost:8099/?src=https://embed.foxglove.dev/&org=<slug>&mcap=/data/run.mcap
docker exec <container> sh /usr/local/bin/npa-foxglove-smoke.sh   # golden eval

Serves /sdk, /app, /data (CORS + ranges + listing), / (host page), /healthz. It has no authentication of its own: keep it cluster-internal or behind an auth proxy.

Gotchas

  • Absolute URLs only. new FoxgloveViewer({src}) throws on a relative URL, and data-source URLs are fetched by the cross-origin iframe — always absolutize.
  • Secure context. Foxglove requires HTTPS (or localhost).
  • Ranges break under compression. Never gzip /foxglove/data/ or /data/.
  • Range triggers a CORS preflight. OPTIONS must answer with Access-Control-Allow-Headers: Range.
  • No pixel capture. The embed is cross-origin, so "Describe this" sends viewer state (source type, recording URL, run/artifact ids) and says so. Do not add a screenshot path for this pane.
  • Lazy load. The SDK is imported only when the Foxglove tab is opened; keep it that way so the Rerun-first boot path stays fast.
  • ds.url must be absolute. The self-hosted viewer's remote-file source silently ignores a relative URL (no range request, "No data source"), so always pin it onto the browsed origin.
  • Hosted remote-file URLs require public HTTPS. Refuse relative, HTTP, credential-bearing, loopback, private, link-local, reserved, or metadata targets. Foxglove Web fetches this exact URL, so its certificate, CORS preflight, and byte-range behavior must work from a clean browser.
  • An API token is not browser sign-in. The token remains server-side for the layout/recording APIs. The cross-origin hosted or embedded app can still require an interactive Foxglove sign-in and an eligible plan; report that surface rather than calling an iframe or handshake useful rendering proof.
  • No implicit hosted app. An unset NPA_FOXGLOVE_EMBED_SRC means "no official app", not embed.foxglove.dev — otherwise a stock deploy shows a sign-in wall instead of rendering.
  • Bump the SDK by editing FOXGLOVE_EMBED_SDK_VERSION + FOXGLOVE_EMBED_SDK_INTEGRITY (npm dist.integrity) and the Dockerfile ARGs together — npa/tests/docker/test_foxglove_image.py fails if they drift.

Verify

npa/.venv/bin/python -m pytest \
  npa/tests/cli/test_agent_foxglove.py npa/tests/cli/test_foxglove_cli.py \
  npa/tests/workbench/test_foxglove_mcap.py npa/tests/docker/test_foxglove_image.py \
  npa/tests/cli/test_agent_backend_render.py npa/tests/smoke/test_agent_smoke.py -q
bash npa/scripts/run_agent_cypress.sh --mock     # includes agent_foxglove.cy.js
# Explicit live opt-in; URL/user/password are runtime environment variables.
NPA_AGENT_CYPRESS_LIVE=1 bash npa/scripts/run_agent_cypress.sh --live

The browser spec drives the real @foxglove/embed build against a protocol-accurate stand-in for the Foxglove application (the licensed viewer cannot run in CI): it asserts the handshake, the data source actually sent, error surfacing, the unconfigured path, lazy loading, and the text-only Describe-this contract.

Signals

GitHub stars
28
Forks
15
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
foxglove
Source
github.com/nebius/nebius-physical-ai