import { stopBrowserBridgeServer } from "../../browser/bridge-server.js"; import { loadConfig } from "../../config/config.js"; import { BROWSER_BRIDGES } from "./browser-bridges.js"; import { resolveSandboxConfigForAgent } from "./config.js"; import { dockerContainerState, execDocker } from "./docker.js"; import { readBrowserRegistry, readRegistry, removeBrowserRegistryEntry, removeRegistryEntry, type SandboxBrowserRegistryEntry, type SandboxRegistryEntry, } from "./registry.js"; import { resolveSandboxAgentId } from "./shared.js"; export type SandboxContainerInfo = SandboxRegistryEntry & { running: boolean; imageMatch: boolean; }; export type SandboxBrowserInfo = SandboxBrowserRegistryEntry & { running: boolean; imageMatch: boolean; }; export async function listSandboxContainers(): Promise { const config = loadConfig(); const registry = await readRegistry(); const results: SandboxContainerInfo[] = []; for (const entry of registry.entries) { const state = await dockerContainerState(entry.containerName); // Get actual image from container let actualImage = entry.image; if (state.exists) { try { const result = await execDocker( ["inspect", "-f", "{{.Config.Image}}", entry.containerName], { allowFailure: true }, ); if (result.code === 0) { actualImage = result.stdout.trim(); } } catch { // ignore } } const agentId = resolveSandboxAgentId(entry.sessionKey); const configuredImage = resolveSandboxConfigForAgent(config, agentId).docker.image; results.push({ ...entry, image: actualImage, running: state.running, imageMatch: actualImage === configuredImage, }); } return results; } export async function listSandboxBrowsers(): Promise { const config = loadConfig(); const registry = await readBrowserRegistry(); const results: SandboxBrowserInfo[] = []; for (const entry of registry.entries) { const state = await dockerContainerState(entry.containerName); let actualImage = entry.image; if (state.exists) { try { const result = await execDocker( ["inspect", "-f", "{{.Config.Image}}", entry.containerName], { allowFailure: true }, ); if (result.code === 0) { actualImage = result.stdout.trim(); } } catch { // ignore } } const agentId = resolveSandboxAgentId(entry.sessionKey); const configuredImage = resolveSandboxConfigForAgent(config, agentId).browser.image; results.push({ ...entry, image: actualImage, running: state.running, imageMatch: actualImage === configuredImage, }); } return results; } export async function removeSandboxContainer(containerName: string): Promise { try { await execDocker(["rm", "-f", containerName], { allowFailure: true }); } catch { // ignore removal failures } await removeRegistryEntry(containerName); } export async function removeSandboxBrowserContainer(containerName: string): Promise { try { await execDocker(["rm", "-f", containerName], { allowFailure: true }); } catch { // ignore removal failures } await removeBrowserRegistryEntry(containerName); // Stop browser bridge if active for (const [sessionKey, bridge] of BROWSER_BRIDGES.entries()) { if (bridge.containerName === containerName) { await stopBrowserBridgeServer(bridge.bridge.server).catch(() => undefined); BROWSER_BRIDGES.delete(sessionKey); } } }