UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

526 lines (525 loc) • 26.8 kB
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 };