openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
526 lines (525 loc) ⢠26.8 kB
JavaScript
import { c as normalizeOptionalLowercaseString, f as normalizeStringifiedEntries, l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js";
import { a as writeRuntimeJson, r as defaultRuntime } from "./runtime-CF2WjnNZ.js";
import { n as getRuntimeConfig } from "./io.runtime-B9iJRs3w.js";
import { n as normalizeAgentId } from "./agent-id-CeT3w4ap.js";
import { t as formatCliCommand } from "./command-format-C7YfyMTd.js";
import { _ as resolveConfiguredAgentId, c as resolveAgentConfig, m as resolveAgentWorkspaceDir } from "./agent-scope-config-DcbEhP0R.js";
import { O as parseAgentSessionKey, a as buildAgentMainSessionKey, f as resolveAgentIdFromSessionKey, u as normalizeMainKey } from "./session-key-BnWWjqNc.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { o as resolveSessionStorePathCore } from "./paths-CXdaYWF_.js";
import { _ as resolveSessionAgentId } from "./agent-scope-DbtJyKUL.js";
import "./config-Cs0XXL3x.js";
import { t as formatDocsLink } from "./links-ClIwBcy4.js";
import { n as isRich, r as theme, t as colorize } from "./theme-vjDs9tao.js";
import { t as normalizeAnyChannelId } from "./registry-normalize-CY8V_nbx.js";
import "./registry-COqNvmCg.js";
import { t as INTERNAL_MESSAGE_CHANNEL } from "./message-channel-constants-2zSoJXQC.js";
import "./message-channel-BQrhwUEA.js";
import { d as sessionDeliveryChannel } from "./delivery-context.shared-CXmRgetN.js";
import "./session-accessor-YsytfDtG.js";
import { u as loadSessionEntryReadOnly } from "./session-accessor.sqlite-entry-CWk3jL7s.js";
import { r as resolveAgentMainSessionKey } from "./main-session-Br0F9dzh.js";
import { t as formatDurationCompact } from "./format-duration-CeDWULoS.js";
import "./sessions-9nxpeTwt.js";
import { r as resolveSandboxToolPolicyForAgent } from "./tool-policy-WMCUEiT6.js";
import { i as resolveSandboxConfigForAgent } from "./config-RoLkL_H5.js";
import { n as resolveSandboxRuntimeStatus } from "./runtime-status-6c8Jb1Kg.js";
import { r as resolveSandboxWorkspaceLayoutPaths } from "./shared-DdMEwpA0.js";
import { s as getSandboxBackendWorkdirResolver } from "./browser-bridges-Cf8_qfWT.js";
import { t as buildSandboxFsMounts } from "./fs-paths-a-ImteWc.js";
import { i as removeSandboxContainer, n as listSandboxContainers, r as removeSandboxBrowserContainer, t as listSandboxBrowsers } from "./sandbox-A2KOznlu.js";
import { r as resolveIngressWorkspaceOverrideForSessionRun } from "./spawned-context-Dt3YQx79.js";
import { n as runCommandWithRuntime } from "./cli-utils-D1DAWB8d.js";
import { t as formatHelpExamples } from "./help-format-CAcwboTs.js";
import { confirm } from "@clack/prompts";
//#region src/commands/sandbox-explain.ts
/**
* Sandbox explanation command.
*
* It resolves the effective sandbox/tool/elevated policy for an agent session
* and prints either JSON or a human-readable fix-it report.
*/
const SANDBOX_DOCS_URL = "https://docs.openclaw.ai/sandbox";
function normalizeExplainSessionKey(params) {
const raw = (params.session ?? "").trim();
if (!raw) return resolveAgentMainSessionKey({
cfg: params.cfg,
agentId: params.agentId
});
if (raw.includes(":")) return raw;
if (raw === "global") return "global";
return buildAgentMainSessionKey({
agentId: params.agentId,
mainKey: normalizeMainKey(raw)
});
}
function inferProviderFromSessionKey(params) {
const parsed = parseAgentSessionKey(params.sessionKey);
if (!parsed) return;
const rest = parsed.rest.trim();
if (!rest) return;
const parts = rest.split(":").filter(Boolean);
if (parts.length === 0) return;
const configuredMainKey = normalizeMainKey(params.cfg.session?.mainKey);
if (parts[0] === configuredMainKey) return;
const candidate = normalizeOptionalLowercaseString(parts[0]);
if (!candidate) return;
if (candidate === "webchat") return INTERNAL_MESSAGE_CHANNEL;
return normalizeAnyChannelId(candidate) ?? void 0;
}
function resolveActiveChannel(params) {
const candidate = (sessionDeliveryChannel(params.entry) ?? "").trim();
const normalizedCandidate = normalizeOptionalLowercaseString(candidate);
if (!normalizedCandidate) return inferProviderFromSessionKey({
cfg: params.cfg,
sessionKey: params.sessionKey
});
if (normalizedCandidate === "webchat") return INTERNAL_MESSAGE_CHANNEL;
const normalized = normalizeAnyChannelId(normalizedCandidate);
if (normalized) return normalized;
return inferProviderFromSessionKey({
cfg: params.cfg,
sessionKey: params.sessionKey
});
}
/** Prints the effective sandbox policy for a session or agent. */
async function sandboxExplainCommand(opts, runtime) {
const cfg = getRuntimeConfig();
const requestedSession = opts.session?.trim();
const requestedAgent = opts.agent?.trim();
if (opts.agent !== void 0 && !requestedAgent) throw new Error("--agent must not be blank");
const requestedAgentId = requestedAgent ? normalizeAgentId(requestedAgent) : void 0;
const sessionAgentId = requestedSession && requestedSession !== "global" && requestedSession.includes(":") ? normalizeAgentId(resolveAgentIdFromSessionKey(requestedSession)) : void 0;
if (requestedAgentId && sessionAgentId && requestedAgentId !== sessionAgentId) throw new Error(`Sandbox explain agent "${requestedAgentId}" does not match session agent "${sessionAgentId}".`);
if (requestedAgentId) resolveConfiguredAgentId(cfg, requestedAgentId);
const resolvedAgentId = resolveSessionAgentId({
sessionKey: requestedSession,
config: cfg,
agentId: requestedAgentId
});
const sessionKey = normalizeExplainSessionKey({
cfg,
agentId: resolvedAgentId,
session: opts.session
});
const toolPolicy = resolveSandboxToolPolicyForAgent(cfg, resolvedAgentId);
const sandboxRuntime = resolveSandboxRuntimeStatus({
cfg,
sessionKey,
agentId: resolvedAgentId,
classificationAgentId: resolvedAgentId
});
const configuredSandbox = resolveSandboxConfigForAgent(cfg, resolvedAgentId);
const sandboxCfg = sandboxRuntime.sandboxRequired ? {
...configuredSandbox,
scope: "agent",
workspaceAccess: sandboxRuntime.workspaceAccess
} : configuredSandbox;
const mainSessionKey = sandboxRuntime.mainSessionKey;
const sessionIsSandboxed = sandboxRuntime.sandboxed;
const storePath = resolveSessionStorePathCore(cfg.session?.store, { agentId: resolvedAgentId });
const sessionEntry = loadSessionEntryReadOnly({
agentId: resolvedAgentId,
sessionKey,
storePath
});
const agentConfig = resolveAgentConfig(cfg, resolvedAgentId);
const configuredWorkspaceDir = resolveAgentWorkspaceDir(cfg, resolvedAgentId);
const effectiveAgentWorkspaceDir = resolveIngressWorkspaceOverrideForSessionRun({
spawnedBy: sessionEntry?.spawnedBy,
workspaceDir: sessionEntry?.spawnedWorkspaceDir,
cwd: sessionEntry?.spawnedCwd
}) ?? configuredWorkspaceDir;
const directRuntimeCwd = normalizeOptionalString(sessionEntry?.spawnedCwd) ?? effectiveAgentWorkspaceDir;
const workspaceLayout = resolveSandboxWorkspaceLayoutPaths({
cfg: sandboxCfg,
agentId: resolvedAgentId,
isolationSubject: sandboxRuntime.isolationSubject,
rawSessionKey: sessionKey === "global" ? buildAgentMainSessionKey({
agentId: resolvedAgentId,
mainKey: normalizeMainKey(cfg.session?.mainKey)
}) : sessionKey,
workspaceDir: effectiveAgentWorkspaceDir
});
const sandboxWorkdir = getSandboxBackendWorkdirResolver(sandboxCfg.backend)?.({
sessionKey,
scopeKey: workspaceLayout.scopeKey,
workspaceDir: workspaceLayout.workspaceDir,
agentWorkspaceDir: workspaceLayout.agentWorkspaceDir,
skillsWorkspaceDir: workspaceLayout.skillsWorkspaceDir,
cfg: sandboxCfg
});
const effectiveHostWorkspaceRoot = sessionIsSandboxed ? workspaceLayout.workspaceDir : workspaceLayout.agentWorkspaceDir;
const runtimeWorkdir = sessionIsSandboxed ? sandboxWorkdir : directRuntimeCwd;
const workspaceSource = sessionIsSandboxed ? workspaceLayout.workspaceSource : "direct";
const usesLocalContainerMounts = sandboxCfg.backend.toLowerCase() === "docker" || sandboxCfg.backend.toLowerCase() === "podman";
const workspaceMounts = sessionIsSandboxed && usesLocalContainerMounts && sandboxWorkdir ? buildSandboxFsMounts({
workspaceDir: workspaceLayout.workspaceDir,
agentWorkspaceDir: workspaceLayout.agentWorkspaceDir,
skillsWorkspaceDir: workspaceLayout.skillsWorkspaceDir,
workspaceAccess: sandboxCfg.workspaceAccess,
containerName: "",
containerWorkdir: sandboxWorkdir,
docker: sandboxCfg.docker
}) : [];
const channel = resolveActiveChannel({
cfg,
entry: sessionEntry,
sessionKey
});
const elevatedGlobal = cfg.tools?.elevated;
const elevatedAgent = agentConfig?.tools?.elevated;
const elevatedGlobalEnabled = elevatedGlobal?.enabled !== false;
const elevatedAgentEnabled = elevatedAgent?.enabled !== false;
const elevatedEnabled = elevatedGlobalEnabled && elevatedAgentEnabled;
const globalAllow = channel ? elevatedGlobal?.allowFrom?.[channel] : void 0;
const agentAllow = channel ? elevatedAgent?.allowFrom?.[channel] : void 0;
const allowTokens = (values) => normalizeStringifiedEntries(values);
const globalAllowTokens = allowTokens(globalAllow);
const agentAllowTokens = allowTokens(agentAllow);
const elevatedAllowedByConfig = elevatedEnabled && Boolean(channel) && globalAllowTokens.length > 0 && (elevatedAgent?.allowFrom ? agentAllowTokens.length > 0 : true);
const elevatedAlwaysAllowedByConfig = elevatedAllowedByConfig && globalAllowTokens.includes("*") && (elevatedAgent?.allowFrom ? agentAllowTokens.includes("*") : true);
const elevatedFailures = [];
if (!elevatedGlobalEnabled) elevatedFailures.push({
gate: "enabled",
key: "tools.elevated.enabled"
});
if (!elevatedAgentEnabled) elevatedFailures.push({
gate: "enabled",
key: "agents.entries.*.tools.elevated.enabled"
});
if (channel && globalAllowTokens.length === 0) elevatedFailures.push({
gate: "allowFrom",
key: `tools.elevated.allowFrom.${channel}`
});
if (channel && elevatedAgent?.allowFrom && agentAllowTokens.length === 0) elevatedFailures.push({
gate: "allowFrom",
key: `agents.entries.*.tools.elevated.allowFrom.${channel}`
});
const fixIt = [];
if (sandboxCfg.mode !== "off") {
fixIt.push("agents.defaults.sandbox.mode=off");
fixIt.push("agents.entries.*.sandbox.mode=off");
}
fixIt.push("tools.sandbox.tools.allow");
fixIt.push("tools.sandbox.tools.alsoAllow");
fixIt.push("tools.sandbox.tools.deny");
fixIt.push("agents.entries.*.tools.sandbox.tools.allow");
fixIt.push("agents.entries.*.tools.sandbox.tools.alsoAllow");
fixIt.push("agents.entries.*.tools.sandbox.tools.deny");
fixIt.push("tools.elevated.enabled");
if (channel) fixIt.push(`tools.elevated.allowFrom.${channel}`);
const payload = {
docsUrl: SANDBOX_DOCS_URL,
agentId: resolvedAgentId,
sessionKey,
mainSessionKey,
sandbox: {
mode: sandboxCfg.mode,
scope: sandboxCfg.scope,
backend: sandboxCfg.backend,
workspaceAccess: sandboxCfg.workspaceAccess,
workspaceRoot: sandboxCfg.workspaceRoot,
effectiveHostWorkspaceRoot,
runtimeWorkdir,
workspaceMounts,
workspaceSource,
sessionIsSandboxed,
tools: {
allow: toolPolicy.allow,
deny: toolPolicy.deny,
sources: toolPolicy.sources
}
},
elevated: {
enabled: elevatedEnabled,
channel,
allowedByConfig: elevatedAllowedByConfig,
alwaysAllowedByConfig: elevatedAlwaysAllowedByConfig,
allowFrom: {
global: channel ? globalAllowTokens : void 0,
agent: elevatedAgent?.allowFrom && channel ? agentAllowTokens : void 0
},
failures: elevatedFailures
},
fixIt
};
if (opts.json) {
writeRuntimeJson(runtime, payload);
return;
}
const rich = isRich();
const heading = (value) => colorize(rich, theme.heading, value);
const key = (value) => colorize(rich, theme.muted, value);
const value = (val) => colorize(rich, theme.info, val);
const ok = (val) => colorize(rich, theme.success, val);
const warn = (val) => colorize(rich, theme.warn, val);
const err = (val) => colorize(rich, theme.error, val);
const bool = (flag) => flag ? ok("true") : err("false");
const lines = [];
lines.push(heading("Effective sandbox:"));
lines.push(` ${key("agentId:")} ${value(payload.agentId)}`);
lines.push(` ${key("sessionKey:")} ${value(payload.sessionKey)}`);
lines.push(` ${key("mainSessionKey:")} ${value(payload.mainSessionKey)}`);
lines.push(` ${key("runtime:")} ${payload.sandbox.sessionIsSandboxed ? warn("sandboxed") : ok("direct")}`);
lines.push(` ${key("mode:")} ${value(payload.sandbox.mode)} ${key("scope:")} ${value(payload.sandbox.scope)}`);
lines.push(` ${key("workspaceAccess:")} ${value(payload.sandbox.workspaceAccess)} ${key("workspaceRoot:")} ${value(payload.sandbox.workspaceRoot)}`);
lines.push(` ${key("effectiveHostWorkspaceRoot:")} ${value(payload.sandbox.effectiveHostWorkspaceRoot)}`);
lines.push(` ${key("backend:")} ${value(payload.sandbox.backend)} ${key("runtimeWorkdir:")} ${value(payload.sandbox.runtimeWorkdir ?? "(direct host)")} ${key("workspaceSource:")} ${value(payload.sandbox.workspaceSource)}`);
if (payload.sandbox.workspaceMounts.length > 0) {
lines.push(` ${key("workspaceMounts:")}`);
for (const mount of payload.sandbox.workspaceMounts) lines.push(` - ${value(mount.hostRoot)} -> ${value(mount.containerRoot)} ${key(mount.writable ? "rw" : "ro")} ${key(`(${mount.source})`)}`);
}
lines.push("");
lines.push(heading("Sandbox tool policy:"));
lines.push(` ${key(`allow (${payload.sandbox.tools.sources.allow.source}):`)} ${value(payload.sandbox.tools.allow.join(", ") || "(empty)")}`);
lines.push(` ${key(`deny (${payload.sandbox.tools.sources.deny.source}):`)} ${value(payload.sandbox.tools.deny.join(", ") || "(empty)")}`);
lines.push("");
lines.push(heading("Elevated:"));
lines.push(` ${key("enabled:")} ${bool(payload.elevated.enabled)}`);
lines.push(` ${key("channel:")} ${value(payload.elevated.channel ?? "(unknown)")}`);
lines.push(` ${key("allowedByConfig:")} ${bool(payload.elevated.allowedByConfig)}`);
if (payload.elevated.failures.length > 0) lines.push(` ${key("failing gates:")} ${warn(payload.elevated.failures.map((f) => `${f.gate} (${f.key})`).join(", "))}`);
if (payload.sandbox.mode === "non-main" && payload.sandbox.sessionIsSandboxed) {
lines.push("");
lines.push(`${warn("Hint:")} sandbox mode is non-main; use main session key to run direct: ${value(payload.mainSessionKey)}`);
}
lines.push("");
lines.push(heading("Fix-it:"));
for (const keyLocal of payload.fixIt) lines.push(` - ${keyLocal}`);
lines.push("");
lines.push(`${key("Docs:")} ${formatDocsLink("/sandbox", "docs.openclaw.ai/sandbox")}`);
runtime.log(`${lines.join("\n")}\n`);
}
//#endregion
//#region src/commands/sandbox-display.ts
function displayContainers(containers, runtime) {
if (containers.length === 0) {
runtime.log("No sandbox runtimes found.");
return;
}
runtime.log("\nš¦ Sandbox Runtimes:\n");
for (const container of containers) {
runtime.log(` ${container.runtimeLabel ?? container.containerName}`);
runtime.log(` Status: ${container.running ? "š¢ running" : "ā« stopped"}`);
runtime.log(` ${container.configLabelKind ?? "Image"}: ${container.image} ${container.imageMatch ? "ā" : "ā ļø mismatch"}`);
runtime.log(` Backend: ${container.backendId ?? "docker"}`);
runtime.log(` Age: ${formatDurationCompact(Date.now() - container.createdAtMs, { spaced: true }) ?? "0s"}`);
runtime.log(` Idle: ${formatDurationCompact(Date.now() - container.lastUsedAtMs, { spaced: true }) ?? "0s"}`);
runtime.log(` Session: ${container.sessionKey}`);
runtime.log("");
}
}
function displayBrowsers(browsers, runtime) {
if (browsers.length === 0) {
runtime.log("No sandbox browser containers found.");
return;
}
runtime.log("\nš Sandbox Browser Containers:\n");
for (const browser of browsers) {
runtime.log(` ${browser.containerName}`);
runtime.log(` Status: ${browser.running ? "š¢ running" : "ā« stopped"}`);
runtime.log(` Image: ${browser.image} ${browser.imageMatch ? "ā" : "ā ļø mismatch"}`);
runtime.log(` CDP: ${browser.cdpPort}`);
if (browser.noVncPort) runtime.log(` noVNC: ${browser.noVncPort}`);
runtime.log(` Age: ${formatDurationCompact(Date.now() - browser.createdAtMs, { spaced: true }) ?? "0s"}`);
runtime.log(` Idle: ${formatDurationCompact(Date.now() - browser.lastUsedAtMs, { spaced: true }) ?? "0s"}`);
runtime.log(` Session: ${browser.sessionKey}`);
runtime.log("");
}
}
function displaySummary(containers, browsers, runtime) {
const totalCount = containers.length + browsers.length;
const runningCount = containers.filter((c) => c.running).length + browsers.filter((b) => b.running).length;
const mismatchCount = containers.filter((c) => !c.imageMatch).length + browsers.filter((b) => !b.imageMatch).length;
runtime.log(`Total: ${totalCount} (${runningCount} running)`);
if (mismatchCount > 0) {
runtime.log(`\nā ļø ${mismatchCount} runtime(s) with config mismatch detected.`);
runtime.log(` Run '${formatCliCommand("openclaw sandbox recreate --all")}' to update all runtimes.`);
}
}
function displayRecreatePreview(containers, browsers, runtime) {
runtime.log("\nSandbox runtimes to be recreated:\n");
if (containers.length > 0) {
runtime.log("š¦ Sandbox Runtimes:");
for (const container of containers) runtime.log(` - ${container.runtimeLabel ?? container.containerName} [${container.backendId ?? "docker"}] (${container.running ? "running" : "stopped"})`);
}
if (browsers.length > 0) {
runtime.log("\nš Browser Containers:");
for (const browser of browsers) runtime.log(` - ${browser.containerName} (${browser.running ? "running" : "stopped"})`);
}
const total = containers.length + browsers.length;
runtime.log(`\nTotal: ${total} runtime(s)`);
}
function displayRecreateResult(result, runtime) {
runtime.log(`\nDone: ${result.successCount} removed, ${result.failCount} failed`);
if (result.successCount > 0) runtime.log("\nRuntimes will be automatically recreated when the agent is next used.");
}
//#endregion
//#region src/commands/sandbox.ts
/**
* Sandbox runtime management commands.
*
* Supports listing active sandbox containers/browsers and recreating them by
* session, agent, or all scopes.
*/
/** Lists active sandbox containers or browser containers. */
async function sandboxListCommand(opts, runtime) {
const containers = opts.browser ? [] : await listSandboxContainers();
const browsers = opts.browser ? await listSandboxBrowsers() : [];
if (opts.json) {
writeRuntimeJson(runtime, {
containers,
browsers
});
return;
}
if (opts.browser) displayBrowsers(browsers, runtime);
else displayContainers(containers, runtime);
displaySummary(containers, browsers, runtime);
}
/** Stops and removes sandbox runtimes matching the requested scope. */
async function sandboxRecreateCommand(opts, runtime) {
if (!validateRecreateOptions(opts, runtime)) return;
const filtered = await fetchAndFilterContainers(opts);
if (filtered.containers.length + filtered.browsers.length === 0) {
runtime.log(`No sandbox runtimes found matching the criteria. Run ${formatCliCommand("openclaw sandbox list")} to inspect active runtimes.`);
return;
}
displayRecreatePreview(filtered.containers, filtered.browsers, runtime);
if (!opts.force && !await confirmRecreate()) {
runtime.log("Cancelled.");
return;
}
const result = await removeContainers(filtered, runtime);
displayRecreateResult(result, runtime);
if (result.failCount > 0) runtime.exit(1);
}
function validateRecreateOptions(opts, runtime) {
if (!opts.all && !opts.session && !opts.agent) {
runtime.error(`Choose the sandbox scope: --all, --session <key>, or --agent <id>. Run ${formatCliCommand("openclaw sandbox list")} to inspect active runtimes first.`);
runtime.exit(1);
return false;
}
if ([
opts.all,
opts.session,
opts.agent
].filter(Boolean).length > 1) {
runtime.error("Choose only one sandbox scope: --all, --session, or --agent.");
runtime.exit(1);
return false;
}
return true;
}
async function fetchAndFilterContainers(opts) {
const allContainers = await listSandboxContainers();
const allBrowsers = await listSandboxBrowsers();
let containers = opts.browser ? [] : allContainers;
let browsers = opts.browser ? allBrowsers : [];
if (opts.session) {
containers = containers.filter((c) => c.sessionKey === opts.session);
browsers = browsers.filter((b) => b.sessionKey === opts.session);
} else if (opts.agent) {
const matchesAgent = createAgentMatcher(opts.agent);
containers = containers.filter(matchesAgent);
browsers = browsers.filter(matchesAgent);
}
return {
containers,
browsers
};
}
function createAgentMatcher(agentId) {
const agentPrefix = `agent:${agentId}`;
return (item) => item.sessionKey === agentPrefix || item.sessionKey.startsWith(`${agentPrefix}:`);
}
async function confirmRecreate() {
return await confirm({
message: "This will stop and remove these containers. Continue?",
initialValue: false
}) === true;
}
async function removeContainers(filtered, runtime) {
runtime.log("\nRemoving sandbox runtimes...\n");
let successCount = 0;
let failCount = 0;
for (const container of filtered.containers) if ((await removeContainer(container.containerName, removeSandboxContainer, runtime)).success) successCount++;
else failCount++;
for (const browser of filtered.browsers) if ((await removeContainer(browser.containerName, removeSandboxBrowserContainer, runtime)).success) successCount++;
else failCount++;
return {
successCount,
failCount
};
}
async function removeContainer(containerName, removeFn, runtime) {
try {
await removeFn(containerName);
runtime.log(`ā Removed ${containerName}`);
return { success: true };
} catch (err) {
runtime.error(`Failed to remove ${containerName}: ${formatErrorMessage(err)}. Run ${formatCliCommand("openclaw sandbox list")} to inspect what remains.`);
return { success: false };
}
}
//#endregion
//#region src/cli/sandbox-cli.ts
const SANDBOX_EXAMPLES = {
main: [
["openclaw sandbox list", "List all sandbox containers."],
["openclaw sandbox list --browser", "List only browser containers."],
["openclaw sandbox recreate --all", "Recreate all containers."],
["openclaw sandbox recreate --session main", "Recreate a specific session."],
["openclaw sandbox recreate --agent mybot", "Recreate agent containers."],
["openclaw sandbox explain", "Explain effective sandbox config."]
],
list: [
["openclaw sandbox list", "List all sandbox containers."],
["openclaw sandbox list --browser", "List only browser containers."],
["openclaw sandbox list --json", "JSON output."]
],
recreate: [
["openclaw sandbox recreate --all", "Recreate all containers."],
["openclaw sandbox recreate --session main", "Recreate a specific session."],
["openclaw sandbox recreate --agent mybot", "Recreate a specific agent (includes sub-agents)."],
["openclaw sandbox recreate --browser --all", "Recreate only browser containers."],
["openclaw sandbox recreate --all --force", "Skip confirmation."]
],
explain: [
["openclaw sandbox explain", "Show effective sandbox config."],
["openclaw sandbox explain --session agent:main:main", "Explain a specific session."],
["openclaw sandbox explain --agent work", "Explain an agent sandbox."],
["openclaw sandbox explain --json", "JSON output."]
]
};
function createRunner(commandFn) {
return async (opts) => {
await runCommandWithRuntime(defaultRuntime, async () => {
await commandFn(opts, defaultRuntime);
});
};
}
function registerSandboxCli(program) {
const sandbox = program.command("sandbox").description("Manage sandbox containers (Docker-based agent isolation)").addHelpText("after", () => `\n${theme.heading("Examples:")}\n${formatHelpExamples(SANDBOX_EXAMPLES.main)}\n`).addHelpText("after", () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/sandbox", "docs.openclaw.ai/cli/sandbox")}\n`).action(() => {
sandbox.help({ error: true });
});
sandbox.command("list").description("List sandbox containers and their status").option("--json", "Output result as JSON", false).option("--browser", "List browser containers only", false).addHelpText("after", () => `\n${theme.heading("Examples:")}\n${formatHelpExamples(SANDBOX_EXAMPLES.list)}\n\n${theme.heading("Output includes:")}\n${theme.muted("- Container name and status (running/stopped)")}\n${theme.muted("- Docker image and whether it matches current config")}\n${theme.muted("- Age (time since creation)")}\n${theme.muted("- Idle time (time since last use)")}\n${theme.muted("- Associated session/agent ID")}`).action(createRunner((opts) => sandboxListCommand({
browser: Boolean(opts.browser),
json: Boolean(opts.json)
}, defaultRuntime)));
sandbox.command("recreate").description("Remove containers to force recreation with updated config").option("--all", "Recreate all sandbox containers", false).option("--session <key>", "Recreate container for specific session").option("--agent <id>", "Recreate containers for specific agent").option("--browser", "Only recreate browser containers", false).option("--force", "Skip confirmation prompt", false).addHelpText("after", () => `\n${theme.heading("Examples:")}\n${formatHelpExamples(SANDBOX_EXAMPLES.recreate)}\n\n${theme.heading("Why use this?")}\n${theme.muted("After updating Docker images or sandbox configuration, existing containers continue running with old settings.")}\n${theme.muted("This command removes them so they'll be recreated automatically with current config when next needed.")}\n\n${theme.heading("Filter options:")}\n${theme.muted(" --all Remove all sandbox containers")}\n${theme.muted(" --session Remove container for specific session key")}\n${theme.muted(" --agent Remove containers for agent (includes agent:id:* variants)")}\n\n${theme.heading("Modifiers:")}\n${theme.muted(" --browser Only affect browser containers (not regular sandbox)")}\n${theme.muted(" --force Skip confirmation prompt")}`).action(createRunner((opts) => sandboxRecreateCommand({
all: Boolean(opts.all),
session: opts.session,
agent: opts.agent,
browser: Boolean(opts.browser),
force: Boolean(opts.force)
}, defaultRuntime)));
sandbox.command("explain").description("Explain effective sandbox/tool policy for a session/agent").option("--session <key>", "Session key to inspect (defaults to agent main)").option("--agent <id>", "Agent id to inspect (defaults to derived agent)").option("--json", "Output result as JSON", false).addHelpText("after", () => `\n${theme.heading("Examples:")}\n${formatHelpExamples(SANDBOX_EXAMPLES.explain)}\n`).action(createRunner((opts) => sandboxExplainCommand({
session: opts.session,
agent: opts.agent,
json: Boolean(opts.json)
}, defaultRuntime)));
}
//#endregion
export { registerSandboxCli };