From 5b36bbf83e72cc68f73c33e409ad71a9f844f74e Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 4 Jun 2026 00:57:14 -0400 Subject: [PATCH] docs: document sandbox lifecycle registry --- src/agents/sandbox/manage.ts | 7 +++++++ src/agents/sandbox/registry.ts | 17 +++++++++++++++++ src/agents/sandbox/shared.ts | 7 +++++++ 3 files changed, 31 insertions(+) diff --git a/src/agents/sandbox/manage.ts b/src/agents/sandbox/manage.ts index 7fbc85a77397..091dc51d484e 100644 --- a/src/agents/sandbox/manage.ts +++ b/src/agents/sandbox/manage.ts @@ -13,6 +13,9 @@ import { } from "./registry.js"; import { resolveSandboxAgentId } from "./shared.js"; +/** + * CLI-facing sandbox management helpers for listing and removing runtime/browser containers. + */ export type SandboxContainerInfo = SandboxRegistryEntry & { running: boolean; imageMatch: boolean; @@ -32,6 +35,7 @@ function toBrowserDockerRuntimeEntry(entry: SandboxBrowserRegistryEntry): Sandbo }; } +/** Lists registered sandbox containers with live backend status and config-label match state. */ export async function listSandboxContainers(): Promise { const config = getRuntimeConfig(); const registry = await readRegistry(); @@ -65,6 +69,7 @@ export async function listSandboxContainers(): Promise { return results; } +/** Lists registered browser sandbox containers with live Docker status. */ export async function listSandboxBrowsers(): Promise { const config = getRuntimeConfig(); const registry = await readBrowserRegistry(); @@ -88,6 +93,7 @@ export async function listSandboxBrowsers(): Promise { return results; } +/** Removes one sandbox container from its backend and registry. */ export async function removeSandboxContainer(containerName: string): Promise { const config = getRuntimeConfig(); const registry = await readRegistry(); @@ -103,6 +109,7 @@ export async function removeSandboxContainer(containerName: string): Promise { const config = getRuntimeConfig(); const registry = await readBrowserRegistry(); diff --git a/src/agents/sandbox/registry.ts b/src/agents/sandbox/registry.ts index bc056917f812..e7efd8abfb8b 100644 --- a/src/agents/sandbox/registry.ts +++ b/src/agents/sandbox/registry.ts @@ -12,6 +12,12 @@ import { } from "./constants.js"; import { hashTextSha256 } from "./hash.js"; +/** + * Persistent sandbox registry storage for runtime and browser containers. + * + * Entries are sharded by container-name hash to avoid a single hot JSON file while preserving + * migration support for older monolithic registry files. + */ export type SandboxRegistryEntry = { containerName: string; backendId?: string; @@ -122,6 +128,7 @@ async function readLegacyRegistryFile(registryPath: string): Promise { const entries = await readShardedEntries(SANDBOX_CONTAINERS_DIR); return { @@ -139,6 +146,8 @@ async function withEntryLock( fn: () => Promise, ): Promise { const entryPath = shardedEntryFilePath(dir, containerName); + // Entry-level locks keep independent container updates concurrent while serializing writes for + // the same container hash path. const lock = await acquireSessionWriteLock({ sessionFile: entryPath, allowReentrant: false, @@ -280,6 +289,7 @@ function legacyRegistryTargets(): LegacyRegistryTarget[] { ]; } +/** Inspects old monolithic registry files without mutating them. */ export async function inspectLegacySandboxRegistryFiles(): Promise< LegacySandboxRegistryInspection[] > { @@ -307,6 +317,7 @@ export async function inspectLegacySandboxRegistryFiles(): Promise< return inspections; } +/** Migrates old monolithic registry files into sharded entry files when present. */ export async function migrateLegacySandboxRegistryFiles(): Promise< LegacySandboxRegistryMigrationResult[] > { @@ -317,6 +328,7 @@ export async function migrateLegacySandboxRegistryFiles(): Promise< return results; } +/** Reads one registered sandbox runtime container by container name. */ export async function readRegistryEntry( containerName: string, ): Promise { @@ -324,6 +336,7 @@ export async function readRegistryEntry( return entry ? normalizeSandboxRegistryEntry(entry) : null; } +/** Creates or updates one sandbox runtime registry entry, preserving immutable creation fields. */ export async function updateRegistry(entry: SandboxRegistryEntry) { await withEntryLock(SANDBOX_CONTAINERS_DIR, entry.containerName, async () => { const existing = await readShardedEntry( @@ -342,16 +355,19 @@ export async function updateRegistry(entry: SandboxRegistryEntry) { }); } +/** Removes one sandbox runtime registry entry by container name. */ export async function removeRegistryEntry(containerName: string) { await withEntryLock(SANDBOX_CONTAINERS_DIR, containerName, async () => { await removeShardedEntry(SANDBOX_CONTAINERS_DIR, containerName); }); } +/** Reads all registered browser sandbox containers from the sharded registry. */ export async function readBrowserRegistry(): Promise { return { entries: await readShardedEntries(SANDBOX_BROWSERS_DIR) }; } +/** Creates or updates one browser sandbox registry entry, preserving immutable creation fields. */ export async function updateBrowserRegistry(entry: SandboxBrowserRegistryEntry) { await withEntryLock(SANDBOX_BROWSERS_DIR, entry.containerName, async () => { const existing = await readShardedEntry( @@ -367,6 +383,7 @@ export async function updateBrowserRegistry(entry: SandboxBrowserRegistryEntry) }); } +/** Removes one browser sandbox registry entry by container name. */ export async function removeBrowserRegistryEntry(containerName: string) { await withEntryLock(SANDBOX_BROWSERS_DIR, containerName, async () => { await removeShardedEntry(SANDBOX_BROWSERS_DIR, containerName); diff --git a/src/agents/sandbox/shared.ts b/src/agents/sandbox/shared.ts index 295e22f40f60..0e5d86817e36 100644 --- a/src/agents/sandbox/shared.ts +++ b/src/agents/sandbox/shared.ts @@ -5,6 +5,10 @@ import { resolveUserPath } from "../../utils.js"; import { resolveAgentIdFromSessionKey } from "../agent-scope.js"; import { hashTextSha256 } from "./hash.js"; +/** + * Shared sandbox naming and scope helpers used by runtime, registry, and CLI management code. + */ +/** Converts an arbitrary session key into a bounded filesystem/container-safe slug. */ export function slugifySessionKey(value: string) { const trimmed = value.trim() || "session"; const hash = hashTextSha256(trimmed).slice(0, 8); @@ -15,12 +19,14 @@ export function slugifySessionKey(value: string) { return `${base}-${hash}`; } +/** Resolves the per-session sandbox workspace directory under the configured sandbox root. */ export function resolveSandboxWorkspaceDir(root: string, sessionKey: string) { const resolvedRoot = resolveUserPath(root); const slug = slugifySessionKey(sessionKey); return path.join(resolvedRoot, slug); } +/** Resolves the registry scope key for session-, agent-, or shared-scope sandbox lifetimes. */ export function resolveSandboxScopeKey(scope: "session" | "agent" | "shared", sessionKey: string) { const trimmed = sessionKey.trim() || "main"; if (scope === "shared") { @@ -33,6 +39,7 @@ export function resolveSandboxScopeKey(scope: "session" | "agent" | "shared", se return `agent:${agentId}`; } +/** Extracts the agent id represented by a sandbox scope key, when one exists. */ export function resolveSandboxAgentId(scopeKey: string): string | undefined { const trimmed = scopeKey.trim(); if (!trimmed || trimmed === "shared") {