mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 00:21:23 +00:00
chore: migrate to oxlint and oxfmt
Co-authored-by: Christoph Nakazawa <christoph.pojer@gmail.com>
This commit is contained in:
@@ -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 }>();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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:");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user