chore: migrate to oxlint and oxfmt

Co-authored-by: Christoph Nakazawa <christoph.pojer@gmail.com>
This commit is contained in:
Peter Steinberger
2026-01-14 14:31:43 +00:00
parent 912ebffc63
commit c379191f80
1480 changed files with 28608 additions and 43547 deletions

View File

@@ -1,6 +1,3 @@
import type { BrowserBridge } from "../../browser/bridge-server.js";
export const BROWSER_BRIDGES = new Map<
string,
{ bridge: BrowserBridge; containerName: string }
>();
export const BROWSER_BRIDGES = new Map<string, { bridge: BrowserBridge; containerName: string }>();

View File

@@ -1,17 +1,8 @@
import {
startBrowserBridgeServer,
stopBrowserBridgeServer,
} from "../../browser/bridge-server.js";
import {
type ResolvedBrowserConfig,
resolveProfile,
} from "../../browser/config.js";
import { startBrowserBridgeServer, stopBrowserBridgeServer } from "../../browser/bridge-server.js";
import { type ResolvedBrowserConfig, resolveProfile } from "../../browser/config.js";
import { DEFAULT_CLAWD_BROWSER_COLOR } from "../../browser/constants.js";
import { BROWSER_BRIDGES } from "./browser-bridges.js";
import {
DEFAULT_SANDBOX_BROWSER_IMAGE,
SANDBOX_AGENT_WORKSPACE_MOUNT,
} from "./constants.js";
import { DEFAULT_SANDBOX_BROWSER_IMAGE, SANDBOX_AGENT_WORKSPACE_MOUNT } from "./constants.js";
import {
buildSandboxCreateArgs,
dockerContainerState,
@@ -23,10 +14,7 @@ import { slugifySessionKey } from "./shared.js";
import { isToolAllowed } from "./tool-policy.js";
import type { SandboxBrowserContext, SandboxConfig } from "./types.js";
async function waitForSandboxCdp(params: {
cdpPort: number;
timeoutMs: number;
}): Promise<boolean> {
async function waitForSandboxCdp(params: { cdpPort: number; timeoutMs: number }): Promise<boolean> {
const deadline = Date.now() + Math.max(0, params.timeoutMs);
const url = `http://127.0.0.1:${params.cdpPort}/json/version`;
while (Date.now() < deadline) {
@@ -94,17 +82,12 @@ export async function ensureSandboxBrowser(params: {
if (!params.cfg.browser.enabled) return null;
if (!isToolAllowed(params.cfg.tools, "browser")) return null;
const slug =
params.cfg.scope === "shared"
? "shared"
: slugifySessionKey(params.scopeKey);
const slug = params.cfg.scope === "shared" ? "shared" : slugifySessionKey(params.scopeKey);
const name = `${params.cfg.browser.containerPrefix}${slug}`;
const containerName = name.slice(0, 63);
const state = await dockerContainerState(containerName);
if (!state.exists) {
await ensureSandboxBrowserImage(
params.cfg.browser.image ?? DEFAULT_SANDBOX_BROWSER_IMAGE,
);
await ensureSandboxBrowserImage(params.cfg.browser.image ?? DEFAULT_SANDBOX_BROWSER_IMAGE);
const args = buildSandboxCreateArgs({
name: containerName,
cfg: params.cfg.docker,
@@ -112,18 +95,11 @@ export async function ensureSandboxBrowser(params: {
labels: { "clawdbot.sandboxBrowser": "1" },
});
const mainMountSuffix =
params.cfg.workspaceAccess === "ro" &&
params.workspaceDir === params.agentWorkspaceDir
params.cfg.workspaceAccess === "ro" && params.workspaceDir === params.agentWorkspaceDir
? ":ro"
: "";
args.push(
"-v",
`${params.workspaceDir}:${params.cfg.docker.workdir}${mainMountSuffix}`,
);
if (
params.cfg.workspaceAccess !== "none" &&
params.workspaceDir !== params.agentWorkspaceDir
) {
args.push("-v", `${params.workspaceDir}:${params.cfg.docker.workdir}${mainMountSuffix}`);
if (params.cfg.workspaceAccess !== "none" && params.workspaceDir !== params.agentWorkspaceDir) {
const agentMountSuffix = params.cfg.workspaceAccess === "ro" ? ":ro" : "";
args.push(
"-v",
@@ -134,22 +110,11 @@ export async function ensureSandboxBrowser(params: {
if (params.cfg.browser.enableNoVnc && !params.cfg.browser.headless) {
args.push("-p", `127.0.0.1::${params.cfg.browser.noVncPort}`);
}
args.push(
"-e",
`CLAWDBOT_BROWSER_HEADLESS=${params.cfg.browser.headless ? "1" : "0"}`,
);
args.push(
"-e",
`CLAWDBOT_BROWSER_ENABLE_NOVNC=${
params.cfg.browser.enableNoVnc ? "1" : "0"
}`,
);
args.push("-e", `CLAWDBOT_BROWSER_HEADLESS=${params.cfg.browser.headless ? "1" : "0"}`);
args.push("-e", `CLAWDBOT_BROWSER_ENABLE_NOVNC=${params.cfg.browser.enableNoVnc ? "1" : "0"}`);
args.push("-e", `CLAWDBOT_BROWSER_CDP_PORT=${params.cfg.browser.cdpPort}`);
args.push("-e", `CLAWDBOT_BROWSER_VNC_PORT=${params.cfg.browser.vncPort}`);
args.push(
"-e",
`CLAWDBOT_BROWSER_NOVNC_PORT=${params.cfg.browser.noVncPort}`,
);
args.push("-e", `CLAWDBOT_BROWSER_NOVNC_PORT=${params.cfg.browser.noVncPort}`);
args.push(params.cfg.browser.image);
await execDocker(args);
await execDocker(["start", containerName]);
@@ -157,10 +122,7 @@ export async function ensureSandboxBrowser(params: {
await execDocker(["start", containerName]);
}
const mappedCdp = await readDockerPort(
containerName,
params.cfg.browser.cdpPort,
);
const mappedCdp = await readDockerPort(containerName, params.cfg.browser.cdpPort);
if (!mappedCdp) {
throw new Error(`Failed to resolve CDP port mapping for ${containerName}.`);
}
@@ -171,17 +133,11 @@ export async function ensureSandboxBrowser(params: {
: null;
const existing = BROWSER_BRIDGES.get(params.scopeKey);
const existingProfile = existing
? resolveProfile(existing.bridge.state.resolved, "clawd")
: null;
const existingProfile = existing ? resolveProfile(existing.bridge.state.resolved, "clawd") : null;
const shouldReuse =
existing &&
existing.containerName === containerName &&
existingProfile?.cdpPort === mappedCdp;
existing && existing.containerName === containerName && existingProfile?.cdpPort === mappedCdp;
if (existing && !shouldReuse) {
await stopBrowserBridgeServer(existing.bridge.server).catch(
() => undefined,
);
await stopBrowserBridgeServer(existing.bridge.server).catch(() => undefined);
BROWSER_BRIDGES.delete(params.scopeKey);
}
@@ -241,9 +197,7 @@ export async function ensureSandboxBrowser(params: {
});
const noVncUrl =
mappedNoVnc &&
params.cfg.browser.enableNoVnc &&
!params.cfg.browser.headless
mappedNoVnc && params.cfg.browser.enableNoVnc && !params.cfg.browser.headless
? `http://127.0.0.1:${mappedNoVnc}/vnc.html?autoconnect=1&resize=remote`
: undefined;

View File

@@ -39,8 +39,7 @@ export function resolveSandboxDockerConfig(params: {
globalDocker?: Partial<SandboxDockerConfig>;
agentDocker?: Partial<SandboxDockerConfig>;
}): SandboxDockerConfig {
const agentDocker =
params.scope === "shared" ? undefined : params.agentDocker;
const agentDocker = params.scope === "shared" ? undefined : params.agentDocker;
const globalDocker = params.globalDocker;
const env = agentDocker?.env
@@ -59,12 +58,9 @@ export function resolveSandboxDockerConfig(params: {
agentDocker?.containerPrefix ??
globalDocker?.containerPrefix ??
DEFAULT_SANDBOX_CONTAINER_PREFIX,
workdir:
agentDocker?.workdir ?? globalDocker?.workdir ?? DEFAULT_SANDBOX_WORKDIR,
readOnlyRoot:
agentDocker?.readOnlyRoot ?? globalDocker?.readOnlyRoot ?? true,
tmpfs: agentDocker?.tmpfs ??
globalDocker?.tmpfs ?? ["/tmp", "/var/tmp", "/run"],
workdir: agentDocker?.workdir ?? globalDocker?.workdir ?? DEFAULT_SANDBOX_WORKDIR,
readOnlyRoot: agentDocker?.readOnlyRoot ?? globalDocker?.readOnlyRoot ?? true,
tmpfs: agentDocker?.tmpfs ?? globalDocker?.tmpfs ?? ["/tmp", "/var/tmp", "/run"],
network: agentDocker?.network ?? globalDocker?.network ?? "none",
user: agentDocker?.user ?? globalDocker?.user,
capDrop: agentDocker?.capDrop ?? globalDocker?.capDrop ?? ["ALL"],
@@ -76,8 +72,7 @@ export function resolveSandboxDockerConfig(params: {
cpus: agentDocker?.cpus ?? globalDocker?.cpus,
ulimits,
seccompProfile: agentDocker?.seccompProfile ?? globalDocker?.seccompProfile,
apparmorProfile:
agentDocker?.apparmorProfile ?? globalDocker?.apparmorProfile,
apparmorProfile: agentDocker?.apparmorProfile ?? globalDocker?.apparmorProfile,
dns: agentDocker?.dns ?? globalDocker?.dns,
extraHosts: agentDocker?.extraHosts ?? globalDocker?.extraHosts,
binds: binds.length ? binds : undefined,
@@ -89,44 +84,27 @@ export function resolveSandboxBrowserConfig(params: {
globalBrowser?: Partial<SandboxBrowserConfig>;
agentBrowser?: Partial<SandboxBrowserConfig>;
}): SandboxBrowserConfig {
const agentBrowser =
params.scope === "shared" ? undefined : params.agentBrowser;
const agentBrowser = params.scope === "shared" ? undefined : params.agentBrowser;
const globalBrowser = params.globalBrowser;
const allowedControlUrls =
agentBrowser?.allowedControlUrls ?? globalBrowser?.allowedControlUrls;
const allowedControlUrls = agentBrowser?.allowedControlUrls ?? globalBrowser?.allowedControlUrls;
const allowedControlHosts =
agentBrowser?.allowedControlHosts ?? globalBrowser?.allowedControlHosts;
const allowedControlPorts =
agentBrowser?.allowedControlPorts ?? globalBrowser?.allowedControlPorts;
return {
enabled: agentBrowser?.enabled ?? globalBrowser?.enabled ?? false,
image:
agentBrowser?.image ??
globalBrowser?.image ??
DEFAULT_SANDBOX_BROWSER_IMAGE,
image: agentBrowser?.image ?? globalBrowser?.image ?? DEFAULT_SANDBOX_BROWSER_IMAGE,
containerPrefix:
agentBrowser?.containerPrefix ??
globalBrowser?.containerPrefix ??
DEFAULT_SANDBOX_BROWSER_PREFIX,
cdpPort:
agentBrowser?.cdpPort ??
globalBrowser?.cdpPort ??
DEFAULT_SANDBOX_BROWSER_CDP_PORT,
vncPort:
agentBrowser?.vncPort ??
globalBrowser?.vncPort ??
DEFAULT_SANDBOX_BROWSER_VNC_PORT,
cdpPort: agentBrowser?.cdpPort ?? globalBrowser?.cdpPort ?? DEFAULT_SANDBOX_BROWSER_CDP_PORT,
vncPort: agentBrowser?.vncPort ?? globalBrowser?.vncPort ?? DEFAULT_SANDBOX_BROWSER_VNC_PORT,
noVncPort:
agentBrowser?.noVncPort ??
globalBrowser?.noVncPort ??
DEFAULT_SANDBOX_BROWSER_NOVNC_PORT,
agentBrowser?.noVncPort ?? globalBrowser?.noVncPort ?? DEFAULT_SANDBOX_BROWSER_NOVNC_PORT,
headless: agentBrowser?.headless ?? globalBrowser?.headless ?? false,
enableNoVnc:
agentBrowser?.enableNoVnc ?? globalBrowser?.enableNoVnc ?? true,
allowHostControl:
agentBrowser?.allowHostControl ??
globalBrowser?.allowHostControl ??
false,
enableNoVnc: agentBrowser?.enableNoVnc ?? globalBrowser?.enableNoVnc ?? true,
allowHostControl: agentBrowser?.allowHostControl ?? globalBrowser?.allowHostControl ?? false,
allowedControlUrls:
Array.isArray(allowedControlUrls) && allowedControlUrls.length > 0
? allowedControlUrls
@@ -155,14 +133,8 @@ export function resolveSandboxPruneConfig(params: {
const agentPrune = params.scope === "shared" ? undefined : params.agentPrune;
const globalPrune = params.globalPrune;
return {
idleHours:
agentPrune?.idleHours ??
globalPrune?.idleHours ??
DEFAULT_SANDBOX_IDLE_HOURS,
maxAgeDays:
agentPrune?.maxAgeDays ??
globalPrune?.maxAgeDays ??
DEFAULT_SANDBOX_MAX_AGE_DAYS,
idleHours: agentPrune?.idleHours ?? globalPrune?.idleHours ?? DEFAULT_SANDBOX_IDLE_HOURS,
maxAgeDays: agentPrune?.maxAgeDays ?? globalPrune?.maxAgeDays ?? DEFAULT_SANDBOX_MAX_AGE_DAYS,
};
}
@@ -174,8 +146,7 @@ export function resolveSandboxConfigForAgent(
// Agent-specific sandbox config overrides global
let agentSandbox: typeof agent | undefined;
const agentConfig =
cfg && agentId ? resolveAgentConfig(cfg, agentId) : undefined;
const agentConfig = cfg && agentId ? resolveAgentConfig(cfg, agentId) : undefined;
if (agentConfig?.sandbox) {
agentSandbox = agentConfig.sandbox;
}
@@ -190,12 +161,9 @@ export function resolveSandboxConfigForAgent(
return {
mode: agentSandbox?.mode ?? agent?.mode ?? "off",
scope,
workspaceAccess:
agentSandbox?.workspaceAccess ?? agent?.workspaceAccess ?? "none",
workspaceAccess: agentSandbox?.workspaceAccess ?? agent?.workspaceAccess ?? "none",
workspaceRoot:
agentSandbox?.workspaceRoot ??
agent?.workspaceRoot ??
DEFAULT_SANDBOX_WORKSPACE_ROOT,
agentSandbox?.workspaceRoot ?? agent?.workspaceRoot ?? DEFAULT_SANDBOX_WORKSPACE_ROOT,
docker: resolveSandboxDockerConfig({
scope,
globalDocker: agent?.docker,

View File

@@ -4,11 +4,7 @@ import path from "node:path";
import { CHANNEL_IDS } from "../../channels/registry.js";
import { STATE_DIR_CLAWDBOT } from "../../config/config.js";
export const DEFAULT_SANDBOX_WORKSPACE_ROOT = path.join(
os.homedir(),
".clawdbot",
"sandboxes",
);
export const DEFAULT_SANDBOX_WORKSPACE_ROOT = path.join(os.homedir(), ".clawdbot", "sandboxes");
export const DEFAULT_SANDBOX_IMAGE = "clawdbot-sandbox:bookworm-slim";
export const DEFAULT_SANDBOX_CONTAINER_PREFIX = "clawdbot-sbx-";
@@ -41,10 +37,8 @@ export const DEFAULT_TOOL_DENY = [
...CHANNEL_IDS,
] as const;
export const DEFAULT_SANDBOX_BROWSER_IMAGE =
"clawdbot-sandbox-browser:bookworm-slim";
export const DEFAULT_SANDBOX_COMMON_IMAGE =
"clawdbot-sandbox-common:bookworm-slim";
export const DEFAULT_SANDBOX_BROWSER_IMAGE = "clawdbot-sandbox-browser:bookworm-slim";
export const DEFAULT_SANDBOX_COMMON_IMAGE = "clawdbot-sandbox-common:bookworm-slim";
export const DEFAULT_SANDBOX_BROWSER_PREFIX = "clawdbot-sbx-browser-";
export const DEFAULT_SANDBOX_BROWSER_CDP_PORT = 9222;
@@ -55,11 +49,5 @@ export const DEFAULT_SANDBOX_BROWSER_AUTOSTART_TIMEOUT_MS = 12_000;
export const SANDBOX_AGENT_WORKSPACE_MOUNT = "/agent";
export const SANDBOX_STATE_DIR = path.join(STATE_DIR_CLAWDBOT, "sandbox");
export const SANDBOX_REGISTRY_PATH = path.join(
SANDBOX_STATE_DIR,
"containers.json",
);
export const SANDBOX_BROWSER_REGISTRY_PATH = path.join(
SANDBOX_STATE_DIR,
"browsers.json",
);
export const SANDBOX_REGISTRY_PATH = path.join(SANDBOX_STATE_DIR, "containers.json");
export const SANDBOX_BROWSER_REGISTRY_PATH = path.join(SANDBOX_STATE_DIR, "browsers.json");

View File

@@ -10,10 +10,7 @@ import { resolveSandboxConfigForAgent } from "./config.js";
import { ensureSandboxContainer } from "./docker.js";
import { maybePruneSandboxes } from "./prune.js";
import { resolveSandboxRuntimeStatus } from "./runtime-status.js";
import {
resolveSandboxScopeKey,
resolveSandboxWorkspaceDir,
} from "./shared.js";
import { resolveSandboxScopeKey, resolveSandboxWorkspaceDir } from "./shared.js";
import type { SandboxContext, SandboxWorkspaceInfo } from "./types.js";
import { ensureSandboxWorkspace } from "./workspace.js";
@@ -41,11 +38,8 @@ export async function resolveSandboxContext(params: {
const workspaceRoot = resolveUserPath(cfg.workspaceRoot);
const scopeKey = resolveSandboxScopeKey(cfg.scope, rawSessionKey);
const sandboxWorkspaceDir =
cfg.scope === "shared"
? workspaceRoot
: resolveSandboxWorkspaceDir(workspaceRoot, scopeKey);
const workspaceDir =
cfg.workspaceAccess === "rw" ? agentWorkspaceDir : sandboxWorkspaceDir;
cfg.scope === "shared" ? workspaceRoot : resolveSandboxWorkspaceDir(workspaceRoot, scopeKey);
const workspaceDir = cfg.workspaceAccess === "rw" ? agentWorkspaceDir : sandboxWorkspaceDir;
if (workspaceDir === sandboxWorkspaceDir) {
await ensureSandboxWorkspace(
sandboxWorkspaceDir,
@@ -60,8 +54,7 @@ export async function resolveSandboxContext(params: {
config: params.config,
});
} catch (error) {
const message =
error instanceof Error ? error.message : JSON.stringify(error);
const message = error instanceof Error ? error.message : JSON.stringify(error);
defaultRuntime.error?.(`Sandbox skill sync failed: ${message}`);
}
}
@@ -123,11 +116,8 @@ export async function ensureSandboxWorkspaceForSession(params: {
const workspaceRoot = resolveUserPath(cfg.workspaceRoot);
const scopeKey = resolveSandboxScopeKey(cfg.scope, rawSessionKey);
const sandboxWorkspaceDir =
cfg.scope === "shared"
? workspaceRoot
: resolveSandboxWorkspaceDir(workspaceRoot, scopeKey);
const workspaceDir =
cfg.workspaceAccess === "rw" ? agentWorkspaceDir : sandboxWorkspaceDir;
cfg.scope === "shared" ? workspaceRoot : resolveSandboxWorkspaceDir(workspaceRoot, scopeKey);
const workspaceDir = cfg.workspaceAccess === "rw" ? agentWorkspaceDir : sandboxWorkspaceDir;
if (workspaceDir === sandboxWorkspaceDir) {
await ensureSandboxWorkspace(
sandboxWorkspaceDir,
@@ -142,8 +132,7 @@ export async function ensureSandboxWorkspaceForSession(params: {
config: params.config,
});
} catch (error) {
const message =
error instanceof Error ? error.message : JSON.stringify(error);
const message = error instanceof Error ? error.message : JSON.stringify(error);
defaultRuntime.error?.(`Sandbox skill sync failed: ${message}`);
}
}

View File

@@ -1,41 +1,32 @@
import { spawn } from "node:child_process";
import {
DEFAULT_SANDBOX_IMAGE,
SANDBOX_AGENT_WORKSPACE_MOUNT,
} from "./constants.js";
import { DEFAULT_SANDBOX_IMAGE, SANDBOX_AGENT_WORKSPACE_MOUNT } from "./constants.js";
import { updateRegistry } from "./registry.js";
import { resolveSandboxScopeKey, slugifySessionKey } from "./shared.js";
import type {
SandboxConfig,
SandboxDockerConfig,
SandboxWorkspaceAccess,
} from "./types.js";
import type { SandboxConfig, SandboxDockerConfig, SandboxWorkspaceAccess } from "./types.js";
export function execDocker(args: string[], opts?: { allowFailure?: boolean }) {
return new Promise<{ stdout: string; stderr: string; code: number }>(
(resolve, reject) => {
const child = spawn("docker", args, {
stdio: ["ignore", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
child.stdout?.on("data", (chunk) => {
stdout += chunk.toString();
});
child.stderr?.on("data", (chunk) => {
stderr += chunk.toString();
});
child.on("close", (code) => {
const exitCode = code ?? 0;
if (exitCode !== 0 && !opts?.allowFailure) {
reject(new Error(stderr.trim() || `docker ${args.join(" ")} failed`));
return;
}
resolve({ stdout, stderr, code: exitCode });
});
},
);
return new Promise<{ stdout: string; stderr: string; code: number }>((resolve, reject) => {
const child = spawn("docker", args, {
stdio: ["ignore", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
child.stdout?.on("data", (chunk) => {
stdout += chunk.toString();
});
child.stderr?.on("data", (chunk) => {
stderr += chunk.toString();
});
child.on("close", (code) => {
const exitCode = code ?? 0;
if (exitCode !== 0 && !opts?.allowFailure) {
reject(new Error(stderr.trim() || `docker ${args.join(" ")} failed`));
return;
}
resolve({ stdout, stderr, code: exitCode });
});
});
}
export async function readDockerPort(containerName: string, port: number) {
@@ -69,10 +60,9 @@ export async function ensureDockerImage(image: string) {
}
export async function dockerContainerState(name: string) {
const result = await execDocker(
["inspect", "-f", "{{.State.Running}}", name],
{ allowFailure: true },
);
const result = await execDocker(["inspect", "-f", "{{.State.Running}}", name], {
allowFailure: true,
});
if (result.code !== 0) return { exists: false, running: false };
return { exists: true, running: result.stdout.trim() === "true" };
}
@@ -95,10 +85,8 @@ function formatUlimitValue(
const raw = String(value).trim();
return raw ? `${name}=${raw}` : null;
}
const soft =
typeof value.soft === "number" ? Math.max(0, value.soft) : undefined;
const hard =
typeof value.hard === "number" ? Math.max(0, value.hard) : undefined;
const soft = typeof value.soft === "number" ? Math.max(0, value.soft) : undefined;
const hard = typeof value.hard === "number" ? Math.max(0, value.hard) : undefined;
if (soft === undefined && hard === undefined) return null;
if (soft === undefined) return `${name}=${hard}`;
if (hard === undefined) return `${name}=${soft}`;
@@ -184,14 +172,9 @@ async function createSandboxContainer(params: {
});
args.push("--workdir", cfg.workdir);
const mainMountSuffix =
params.workspaceAccess === "ro" && workspaceDir === params.agentWorkspaceDir
? ":ro"
: "";
params.workspaceAccess === "ro" && workspaceDir === params.agentWorkspaceDir ? ":ro" : "";
args.push("-v", `${workspaceDir}:${cfg.workdir}${mainMountSuffix}`);
if (
params.workspaceAccess !== "none" &&
workspaceDir !== params.agentWorkspaceDir
) {
if (params.workspaceAccess !== "none" && workspaceDir !== params.agentWorkspaceDir) {
const agentMountSuffix = params.workspaceAccess === "ro" ? ":ro" : "";
args.push(
"-v",
@@ -215,8 +198,7 @@ export async function ensureSandboxContainer(params: {
cfg: SandboxConfig;
}) {
const scopeKey = resolveSandboxScopeKey(params.cfg.scope, params.sessionKey);
const slug =
params.cfg.scope === "shared" ? "shared" : slugifySessionKey(scopeKey);
const slug = params.cfg.scope === "shared" ? "shared" : slugifySessionKey(scopeKey);
const name = `${params.cfg.docker.containerPrefix}${slug}`;
const containerName = name.slice(0, 63);
const state = await dockerContainerState(containerName);

View File

@@ -46,8 +46,7 @@ export async function listSandboxContainers(): Promise<SandboxContainerInfo[]> {
}
}
const agentId = resolveSandboxAgentId(entry.sessionKey);
const configuredImage = resolveSandboxConfigForAgent(config, agentId).docker
.image;
const configuredImage = resolveSandboxConfigForAgent(config, agentId).docker.image;
results.push({
...entry,
image: actualImage,
@@ -81,8 +80,7 @@ export async function listSandboxBrowsers(): Promise<SandboxBrowserInfo[]> {
}
}
const agentId = resolveSandboxAgentId(entry.sessionKey);
const configuredImage = resolveSandboxConfigForAgent(config, agentId)
.browser.image;
const configuredImage = resolveSandboxConfigForAgent(config, agentId).browser.image;
results.push({
...entry,
image: actualImage,
@@ -94,9 +92,7 @@ export async function listSandboxBrowsers(): Promise<SandboxBrowserInfo[]> {
return results;
}
export async function removeSandboxContainer(
containerName: string,
): Promise<void> {
export async function removeSandboxContainer(containerName: string): Promise<void> {
try {
await execDocker(["rm", "-f", containerName], { allowFailure: true });
} catch {
@@ -105,9 +101,7 @@ export async function removeSandboxContainer(
await removeRegistryEntry(containerName);
}
export async function removeSandboxBrowserContainer(
containerName: string,
): Promise<void> {
export async function removeSandboxBrowserContainer(containerName: string): Promise<void> {
try {
await execDocker(["rm", "-f", containerName], { allowFailure: true });
} catch {
@@ -118,9 +112,7 @@ export async function removeSandboxBrowserContainer(
// Stop browser bridge if active
for (const [sessionKey, bridge] of BROWSER_BRIDGES.entries()) {
if (bridge.containerName === containerName) {
await stopBrowserBridgeServer(bridge.bridge.server).catch(
() => undefined,
);
await stopBrowserBridgeServer(bridge.bridge.server).catch(() => undefined);
BROWSER_BRIDGES.delete(sessionKey);
}
}

View File

@@ -61,9 +61,7 @@ async function pruneSandboxBrowsers(cfg: SandboxConfig) {
await removeBrowserRegistryEntry(entry.containerName);
const bridge = BROWSER_BRIDGES.get(entry.sessionKey);
if (bridge?.containerName === entry.containerName) {
await stopBrowserBridgeServer(bridge.bridge.server).catch(
() => undefined,
);
await stopBrowserBridgeServer(bridge.bridge.server).catch(() => undefined);
BROWSER_BRIDGES.delete(entry.sessionKey);
}
}
@@ -85,9 +83,7 @@ export async function maybePruneSandboxes(cfg: SandboxConfig) {
: typeof error === "string"
? error
: JSON.stringify(error);
defaultRuntime.error?.(
`Sandbox prune failed: ${message ?? "unknown error"}`,
);
defaultRuntime.error?.(`Sandbox prune failed: ${message ?? "unknown error"}`);
}
}

View File

@@ -45,21 +45,13 @@ export async function readRegistry(): Promise<SandboxRegistry> {
async function writeRegistry(registry: SandboxRegistry) {
await fs.mkdir(SANDBOX_STATE_DIR, { recursive: true });
await fs.writeFile(
SANDBOX_REGISTRY_PATH,
`${JSON.stringify(registry, null, 2)}\n`,
"utf-8",
);
await fs.writeFile(SANDBOX_REGISTRY_PATH, `${JSON.stringify(registry, null, 2)}\n`, "utf-8");
}
export async function updateRegistry(entry: SandboxRegistryEntry) {
const registry = await readRegistry();
const existing = registry.entries.find(
(item) => item.containerName === entry.containerName,
);
const next = registry.entries.filter(
(item) => item.containerName !== entry.containerName,
);
const existing = registry.entries.find((item) => item.containerName === entry.containerName);
const next = registry.entries.filter((item) => item.containerName !== entry.containerName);
next.push({
...entry,
createdAtMs: existing?.createdAtMs ?? entry.createdAtMs,
@@ -70,9 +62,7 @@ export async function updateRegistry(entry: SandboxRegistryEntry) {
export async function removeRegistryEntry(containerName: string) {
const registry = await readRegistry();
const next = registry.entries.filter(
(item) => item.containerName !== containerName,
);
const next = registry.entries.filter((item) => item.containerName !== containerName);
if (next.length === registry.entries.length) return;
await writeRegistry({ entries: next });
}
@@ -97,16 +87,10 @@ async function writeBrowserRegistry(registry: SandboxBrowserRegistry) {
);
}
export async function updateBrowserRegistry(
entry: SandboxBrowserRegistryEntry,
) {
export async function updateBrowserRegistry(entry: SandboxBrowserRegistryEntry) {
const registry = await readBrowserRegistry();
const existing = registry.entries.find(
(item) => item.containerName === entry.containerName,
);
const next = registry.entries.filter(
(item) => item.containerName !== entry.containerName,
);
const existing = registry.entries.find((item) => item.containerName === entry.containerName);
const next = registry.entries.filter((item) => item.containerName !== entry.containerName);
next.push({
...entry,
createdAtMs: existing?.createdAtMs ?? entry.createdAtMs,
@@ -117,9 +101,7 @@ export async function updateBrowserRegistry(
export async function removeBrowserRegistryEntry(containerName: string) {
const registry = await readBrowserRegistry();
const next = registry.entries.filter(
(item) => item.containerName !== containerName,
);
const next = registry.entries.filter((item) => item.containerName !== containerName);
if (next.length === registry.entries.length) return;
await writeBrowserRegistry({ entries: next });
}

View File

@@ -1,19 +1,12 @@
import type { ClawdbotConfig } from "../../config/config.js";
import {
canonicalizeMainSessionAlias,
resolveAgentMainSessionKey,
} from "../../config/sessions.js";
import { canonicalizeMainSessionAlias, resolveAgentMainSessionKey } from "../../config/sessions.js";
import { resolveSessionAgentId } from "../agent-scope.js";
import { expandToolGroups } from "../tool-policy.js";
import { resolveSandboxConfigForAgent } from "./config.js";
import { resolveSandboxToolPolicyForAgent } from "./tool-policy.js";
import type { SandboxConfig, SandboxToolPolicyResolved } from "./types.js";
function shouldSandboxSession(
cfg: SandboxConfig,
sessionKey: string,
mainSessionKey: string,
) {
function shouldSandboxSession(cfg: SandboxConfig, sessionKey: string, mainSessionKey: string) {
if (cfg.mode === "off") return false;
if (cfg.mode === "all") return true;
return sessionKey.trim() !== mainSessionKey.trim();
@@ -113,9 +106,7 @@ export function formatSandboxToolPolicyBlockedMessage(params: {
}
const lines: string[] = [];
lines.push(
`Tool "${tool}" blocked by sandbox tool policy (mode=${runtime.mode}).`,
);
lines.push(`Tool "${tool}" blocked by sandbox tool policy (mode=${runtime.mode}).`);
lines.push(`Session: ${runtime.sessionKey || "(unknown)"}`);
lines.push(`Reason: ${reasons.join(" + ")}`);
lines.push("Fix:");

View File

@@ -7,11 +7,7 @@ import { resolveAgentIdFromSessionKey } from "../agent-scope.js";
export function slugifySessionKey(value: string) {
const trimmed = value.trim() || "session";
const hash = crypto
.createHash("sha1")
.update(trimmed)
.digest("hex")
.slice(0, 8);
const hash = crypto.createHash("sha1").update(trimmed).digest("hex").slice(0, 8);
const safe = trimmed
.toLowerCase()
.replace(/[^a-z0-9._-]+/g, "-")
@@ -26,10 +22,7 @@ export function resolveSandboxWorkspaceDir(root: string, sessionKey: string) {
return path.join(resolvedRoot, slug);
}
export function resolveSandboxScopeKey(
scope: "session" | "agent" | "shared",
sessionKey: string,
) {
export function resolveSandboxScopeKey(scope: "session" | "agent" | "shared", sessionKey: string) {
const trimmed = sessionKey.trim() || "main";
if (scope === "shared") return "shared";
if (scope === "session") return trimmed;

View File

@@ -20,8 +20,7 @@ export function resolveSandboxToolPolicyForAgent(
cfg?: ClawdbotConfig,
agentId?: string,
): SandboxToolPolicyResolved {
const agentConfig =
cfg && agentId ? resolveAgentConfig(cfg, agentId) : undefined;
const agentConfig = cfg && agentId ? resolveAgentConfig(cfg, agentId) : undefined;
const agentAllow = agentConfig?.tools?.sandbox?.tools?.allow;
const agentDeny = agentConfig?.tools?.sandbox?.tools?.deny;
const globalAllow = cfg?.tools?.sandbox?.tools?.allow;