feat(sandbox): add sandbox explain inspector

This commit is contained in:
Peter Steinberger
2026-01-10 20:28:34 +01:00
parent 4533dd6e5d
commit 9f9098406c
20 changed files with 951 additions and 56 deletions

View File

@@ -29,10 +29,9 @@ import {
resolveConfiguredModelRef,
resolveModelRefFromString,
} from "../../agents/model-selection.js";
import { resolveSandboxConfigForAgent } from "../../agents/sandbox.js";
import { resolveSandboxRuntimeStatus } from "../../agents/sandbox.js";
import type { ClawdbotConfig } from "../../config/config.js";
import {
resolveAgentMainSessionKey,
type SessionEntry,
saveSessionStore,
} from "../../config/sessions.js";
@@ -72,6 +71,31 @@ const withOptions = (line: string, options: string) =>
const formatElevatedRuntimeHint = () =>
`${SYSTEM_MARK} Runtime is direct; sandboxing does not apply.`;
function formatElevatedUnavailableText(params: {
runtimeSandboxed: boolean;
failures?: Array<{ gate: string; key: string }>;
sessionKey?: string;
}): string {
const lines: string[] = [];
lines.push(
`elevated is not available right now (runtime=${params.runtimeSandboxed ? "sandboxed" : "direct"}).`,
);
const failures = params.failures ?? [];
if (failures.length > 0) {
lines.push(
`Failing gates: ${failures.map((f) => `${f.gate} (${f.key})`).join(", ")}`,
);
} else {
lines.push(
"Fix-it keys: tools.elevated.enabled, tools.elevated.allowFrom.<provider>, agents.list[].tools.elevated.*",
);
}
if (params.sessionKey) {
lines.push(`See: clawdbot sandbox explain --session ${params.sessionKey}`);
}
return lines.join("\n");
}
const maskApiKey = (value: string): string => {
const trimmed = value.trim();
if (!trimmed) return "missing";
@@ -452,6 +476,8 @@ export async function handleDirectiveOnly(params: {
storePath?: string;
elevatedEnabled: boolean;
elevatedAllowed: boolean;
elevatedFailures?: Array<{ gate: string; key: string }>;
messageProviderKey?: string;
defaultProvider: string;
defaultModel: string;
aliasIndex: ModelAliasIndex;
@@ -496,22 +522,10 @@ export async function handleDirectiveOnly(params: {
config: params.cfg,
});
const agentDir = resolveAgentDir(params.cfg, activeAgentId);
const runtimeIsSandboxed = (() => {
const sessionKey = params.sessionKey?.trim();
if (!sessionKey) return false;
const agentId = resolveSessionAgentId({
sessionKey,
config: params.cfg,
});
const sandboxCfg = resolveSandboxConfigForAgent(params.cfg, agentId);
if (sandboxCfg.mode === "off") return false;
const mainKey = resolveAgentMainSessionKey({
cfg: params.cfg,
agentId,
});
if (sandboxCfg.mode === "all") return true;
return sessionKey !== mainKey;
})();
const runtimeIsSandboxed = resolveSandboxRuntimeStatus({
cfg: params.cfg,
sessionKey: params.sessionKey,
}).sandboxed;
const shouldHintDirectRuntime =
directives.hasElevatedDirective && !runtimeIsSandboxed;
@@ -709,7 +723,13 @@ export async function handleDirectiveOnly(params: {
if (directives.hasElevatedDirective && !directives.elevatedLevel) {
if (!directives.rawElevatedLevel) {
if (!elevatedEnabled || !elevatedAllowed) {
return { text: "elevated is not available right now." };
return {
text: formatElevatedUnavailableText({
runtimeSandboxed: runtimeIsSandboxed,
failures: params.elevatedFailures,
sessionKey: params.sessionKey,
}),
};
}
const level = currentElevatedLevel ?? "off";
return {
@@ -729,7 +749,13 @@ export async function handleDirectiveOnly(params: {
directives.hasElevatedDirective &&
(!elevatedEnabled || !elevatedAllowed)
) {
return { text: "elevated is not available right now." };
return {
text: formatElevatedUnavailableText({
runtimeSandboxed: runtimeIsSandboxed,
failures: params.elevatedFailures,
sessionKey: params.sessionKey,
}),
};
}
if (