-
-
{error &&
{error}
}
{screenshotError &&
{screenshotError}
}
-
+ {/* ========== Runtime Section ========== */}
Desktop Runtime
-
+
{status?.state ?? "unknown"}
-
Display
@@ -244,65 +315,35 @@ const DesktopTab = ({
{formatStartedAt(status?.startedAt)}
-
-
-
void handleStart()}
- disabled={acting === "start"}
- >
- {acting === "start" ? (
-
- ) : (
-
- )}
- Start Desktop
-
-
void handleStop()}
- disabled={acting === "stop"}
- >
- {acting === "stop" ? (
-
- ) : (
-
- )}
- Stop Desktop
-
+ {isActive ? (
+
void handleStop()} disabled={acting === "stop"}>
+ {acting === "stop" ? : }
+ Stop Desktop
+
+ ) : (
+
void handleStart()} disabled={acting === "start"}>
+ {acting === "start" ? : }
+ Start Desktop
+
+ )}
-
+ {/* ========== Missing Dependencies ========== */}
{status?.missingDependencies && status.missingDependencies.length > 0 && (
@@ -310,18 +351,188 @@ const DesktopTab = ({
{status.missingDependencies.map((dependency) => (
- {dependency}
+
+ {dependency}
+
))}
{status.installCommand && (
<>
-
Install command
+
+ Install command
+
{status.installCommand}
>
)}
)}
-
+ {/* ========== Live View Section ========== */}
+
+
+
+
+ Live View
+
+ {isActive && (
+
{
+ e.stopPropagation();
+ if (liveViewActive) {
+ // Stop: close viewer then stop the stream process
+ setLiveViewActive(false);
+ void getClient()
+ .stopDesktopStream()
+ .catch(() => undefined);
+ } else {
+ setLiveViewActive(true);
+ }
+ }}
+ style={{ padding: "4px 10px", fontSize: 11 }}
+ >
+ {liveViewActive ? (
+ <>
+
+ Stop Stream
+ >
+ ) : (
+ <>
+
+ Start Stream
+ >
+ )}
+
+ )}
+
+ {liveViewError && (
+
+ {liveViewError}
+
+ )}
+ {!isActive &&
Start the desktop runtime to enable live view.
}
+ {isActive && liveViewActive &&
}
+ {isActive && !liveViewActive && (
+ <>
+ {screenshotUrl ? (
+
+

+
+ ) : (
+
Click "Start Stream" for live desktop view, or use the Screenshot button above.
+ )}
+ >
+ )}
+
+ {/* ========== Recording Section ========== */}
+
+
+
+
+ Recording
+
+ {activeRecording && Recording}
+
+ {recordingError && (
+
+ {recordingError}
+
+ )}
+ {!isActive &&
Start the desktop runtime to enable recording.
}
+ {isActive && (
+ <>
+
+
+
+ setRecordingFps(e.target.value)}
+ inputMode="numeric"
+ style={{ maxWidth: 80 }}
+ disabled={!!activeRecording}
+ />
+
+
+
+ {!activeRecording ? (
+ void handleStartRecording()} disabled={recordingActing === "start"}>
+ {recordingActing === "start" ? (
+
+ ) : (
+
+ )}
+ Start Recording
+
+ ) : (
+ void handleStopRecording()} disabled={recordingActing === "stop"}>
+ {recordingActing === "stop" ? : }
+ Stop Recording
+
+ )}
+ void loadRecordings()} disabled={recordingLoading}>
+
+ Refresh
+
+
+ {recordings.length > 0 && (
+
+ {recordings.map((rec) => (
+
+
+
+
+ {rec.fileName}
+
+
+ {rec.status}
+
+
+ {rec.status === "completed" && (
+
+ void handleDownloadRecording(rec.id, rec.fileName)}
+ disabled={downloadingRecordingId === rec.id}
+ style={{ padding: "4px 6px" }}
+ >
+ {downloadingRecordingId === rec.id ? : }
+
+ void handleDeleteRecording(rec.id)}
+ disabled={deletingRecordingId === rec.id}
+ style={{ padding: "4px 6px", color: "var(--danger)" }}
+ >
+ {deletingRecordingId === rec.id ? : }
+
+
+ )}
+
+
+ {formatBytes(rec.bytes)}
+ {" \u00b7 "}
+ {formatDuration(rec.startedAt, rec.endedAt)}
+ {" \u00b7 "}
+ {formatStartedAt(rec.startedAt)}
+
+
+ ))}
+
+ )}
+ {recordings.length === 0 && !recordingLoading && (
+
+ No recordings yet. Click "Start Recording" to begin.
+
+ )}
+ >
+ )}
+
+ {/* ========== Diagnostics Section ========== */}
{(status?.lastError || status?.runtimeLogPath || (status?.processes?.length ?? 0) > 0) && (
@@ -352,9 +563,7 @@ const DesktopTab = ({
{process.running ? "running" : "stopped"}
-
- {process.pid ? `pid ${process.pid}` : "no pid"}
-
+
{process.pid ? `pid ${process.pid}` : "no pid"}
{process.logPath &&
{process.logPath}
}
))}
@@ -363,31 +572,7 @@ const DesktopTab = ({
)}
)}
-
-
-
- Latest Screenshot
- {status?.state === "active" ? (
- Manual refresh only
- ) : null}
-
-
- {loading ?
Loading...
: null}
- {!loading && !screenshotUrl && (
-
- {status?.state === "active"
- ? "No screenshot loaded yet."
- : "Start the desktop runtime to capture a screenshot."}
-
- )}
- {screenshotUrl && (
-
-

-
- )}
-
);
};
-
export default DesktopTab;
diff --git a/frontend/packages/inspector/vite.config.ts b/frontend/packages/inspector/vite.config.ts
index 6496813..d398b20 100644
--- a/frontend/packages/inspector/vite.config.ts
+++ b/frontend/packages/inspector/vite.config.ts
@@ -8,7 +8,7 @@ export default defineConfig(({ command }) => ({
port: 5173,
proxy: {
"/v1": {
- target: "http://localhost:2468",
+ target: process.env.SANDBOX_AGENT_URL || "http://localhost:2468",
changeOrigin: true,
ws: true,
},
diff --git a/justfile b/justfile
index 84b761f..c23c751 100644
--- a/justfile
+++ b/justfile
@@ -76,6 +76,26 @@ run-gigacode *ARGS:
dev-docs:
cd docs && pnpm dlx mintlify dev --host 0.0.0.0
+# Start the desktop dev stack (sandbox-agent backend in Docker + inspector frontend)
+[group('server')]
+server-dev:
+ docker compose -f server/compose.dev.yaml up --build --force-recreate -d
+
+# Stop the desktop dev stack
+[group('server')]
+server-dev-down:
+ docker compose -f server/compose.dev.yaml down
+
+# Tail desktop dev stack logs
+[group('server')]
+server-dev-logs *ARGS:
+ docker compose -f server/compose.dev.yaml logs -f --tail=200 {{ ARGS }}
+
+# Rebuild and restart only the backend container
+[group('server')]
+server-dev-restart-backend:
+ docker compose -f server/compose.dev.yaml up --build --force-recreate -d backend
+
install:
pnpm install
pnpm build --filter @sandbox-agent/inspector...
diff --git a/sdks/react/src/DesktopViewer.tsx b/sdks/react/src/DesktopViewer.tsx
index b807e42..4c0c440 100644
--- a/sdks/react/src/DesktopViewer.tsx
+++ b/sdks/react/src/DesktopViewer.tsx
@@ -2,26 +2,19 @@
import type { CSSProperties, MouseEvent, WheelEvent } from "react";
import { useEffect, useRef, useState } from "react";
-import type {
- DesktopMouseButton,
- DesktopStreamErrorStatus,
- DesktopStreamReadyStatus,
- SandboxAgent,
-} from "sandbox-agent";
+import type { DesktopMouseButton, DesktopStreamErrorStatus, DesktopStreamReadyStatus, DesktopStreamSession, SandboxAgent } from "sandbox-agent";
type ConnectionState = "connecting" | "ready" | "closed" | "error";
-export type DesktopViewerClient = Pick<
- SandboxAgent,
- "startDesktopStream" | "stopDesktopStream" | "connectDesktopStream"
->;
+export type DesktopViewerClient = Pick