From 46e00ad5e7d7a1a35b6a3f1321e24b4af72dc3f9 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 10 Jan 2026 21:37:04 +0100 Subject: [PATCH] fix: describe sandboxed elevated in prompt --- CHANGELOG.md | 1 + docs/concepts/system-prompt.md | 1 + src/agents/pi-embedded-runner.test.ts | 41 +++++++++++++++++++++++++++ src/agents/pi-embedded-runner.ts | 24 ++++++++++++++-- src/agents/system-prompt.test.ts | 17 +++++++++++ src/agents/system-prompt.ts | 20 ++++++++++++- 6 files changed, 101 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b32d9552..8de6b8fbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - CLI: add `clawdbot update` (safe-ish git checkout update) + `--update` shorthand. (#673) — thanks @fm1randa. ### Fixes +- Agents/System: clarify sandboxed runtime in system prompt and surface elevated availability when sandboxed. - Auto-reply: prefer `RawBody` for command/directive parsing (WhatsApp + Discord) and prevent fallback runs from clobbering concurrent session updates. (#643) — thanks @mcinteerj. - WhatsApp: fix group reactions by preserving message IDs and sender JIDs in history; normalize participant phone numbers to JIDs in outbound reactions. (#640) — thanks @mcinteerj. - WhatsApp: expose group participant IDs to the model so reactions can target the right sender. diff --git a/docs/concepts/system-prompt.md b/docs/concepts/system-prompt.md index 165dd070a..c357312c5 100644 --- a/docs/concepts/system-prompt.md +++ b/docs/concepts/system-prompt.md @@ -19,6 +19,7 @@ The prompt is intentionally compact and uses fixed sections: - **Clawdbot Self-Update**: how to run `config.apply` and `update.run`. - **Workspace**: working directory (`agents.defaults.workspace`). - **Workspace Files (injected)**: indicates bootstrap files are included below. +- **Sandbox** (when enabled): indicates sandboxed runtime, sandbox paths, and whether elevated bash is available. - **Time**: UTC default + the user’s local time (already converted). - **Reply Tags**: optional reply tag syntax for supported providers. - **Heartbeats**: heartbeat prompt and ack behavior. diff --git a/src/agents/pi-embedded-runner.test.ts b/src/agents/pi-embedded-runner.test.ts index ea2cdd6bf..705ad8dcf 100644 --- a/src/agents/pi-embedded-runner.test.ts +++ b/src/agents/pi-embedded-runner.test.ts @@ -61,6 +61,47 @@ describe("buildEmbeddedSandboxInfo", () => { browserNoVncUrl: "http://localhost:6080", }); }); + + it("includes elevated info when allowed", () => { + const sandbox = { + enabled: true, + sessionKey: "session:test", + workspaceDir: "/tmp/clawdbot-sandbox", + agentWorkspaceDir: "/tmp/clawdbot-workspace", + workspaceAccess: "none", + containerName: "clawdbot-sbx-test", + containerWorkdir: "/workspace", + docker: { + image: "clawdbot-sandbox:bookworm-slim", + containerPrefix: "clawdbot-sbx-", + workdir: "/workspace", + readOnlyRoot: true, + tmpfs: ["/tmp"], + network: "none", + user: "1000:1000", + capDrop: ["ALL"], + env: { LANG: "C.UTF-8" }, + }, + tools: { + allow: ["bash"], + deny: ["browser"], + }, + } satisfies SandboxContext; + + expect( + buildEmbeddedSandboxInfo(sandbox, { + enabled: true, + allowed: true, + defaultLevel: "on", + }), + ).toEqual({ + enabled: true, + workspaceDir: "/tmp/clawdbot-sandbox", + workspaceAccess: "none", + agentWorkspaceMount: undefined, + elevated: { allowed: true, defaultLevel: "on" }, + }); + }); }); describe("resolveSessionAgentIds", () => { diff --git a/src/agents/pi-embedded-runner.ts b/src/agents/pi-embedded-runner.ts index dbd794767..d0c19af57 100644 --- a/src/agents/pi-embedded-runner.ts +++ b/src/agents/pi-embedded-runner.ts @@ -479,6 +479,10 @@ type EmbeddedSandboxInfo = { agentWorkspaceMount?: string; browserControlUrl?: string; browserNoVncUrl?: string; + elevated?: { + allowed: boolean; + defaultLevel: "on" | "off"; + }; }; function resolveSessionLane(key: string) { @@ -552,8 +556,10 @@ function describeUnknownError(error: unknown): string { export function buildEmbeddedSandboxInfo( sandbox?: Awaited>, + bashElevated?: BashElevatedDefaults, ): EmbeddedSandboxInfo | undefined { if (!sandbox?.enabled) return undefined; + const elevatedAllowed = Boolean(bashElevated?.enabled && bashElevated.allowed); return { enabled: true, workspaceDir: sandbox.workspaceDir, @@ -562,6 +568,14 @@ export function buildEmbeddedSandboxInfo( sandbox.workspaceAccess === "ro" ? "/agent" : undefined, browserControlUrl: sandbox.browser?.controlUrl, browserNoVncUrl: sandbox.browser?.noVncUrl, + ...(elevatedAllowed + ? { + elevated: { + allowed: true, + defaultLevel: bashElevated?.defaultLevel ?? "off", + }, + } + : {}), }; } @@ -887,7 +901,10 @@ export async function compactEmbeddedPiSession(params: { provider: runtimeProvider, capabilities: runtimeCapabilities, }; - const sandboxInfo = buildEmbeddedSandboxInfo(sandbox); + const sandboxInfo = buildEmbeddedSandboxInfo( + sandbox, + params.bashElevated, + ); const reasoningTagHint = provider === "ollama"; const userTimezone = resolveUserTimezone( params.config?.agents?.defaults?.userTimezone, @@ -1264,7 +1281,10 @@ export async function runEmbeddedPiAgent(params: { node: process.version, model: `${provider}/${modelId}`, }; - const sandboxInfo = buildEmbeddedSandboxInfo(sandbox); + const sandboxInfo = buildEmbeddedSandboxInfo( + sandbox, + params.bashElevated, + ); const reasoningTagHint = provider === "ollama"; const userTimezone = resolveUserTimezone( params.config?.agents?.defaults?.userTimezone, diff --git a/src/agents/system-prompt.test.ts b/src/agents/system-prompt.test.ts index 7d20383b1..a479bada2 100644 --- a/src/agents/system-prompt.test.ts +++ b/src/agents/system-prompt.test.ts @@ -169,4 +169,21 @@ describe("buildAgentSystemPrompt", () => { expect(prompt).toContain("provider=telegram"); expect(prompt).toContain("capabilities=inlineButtons"); }); + + it("describes sandboxed runtime and elevated when allowed", () => { + const prompt = buildAgentSystemPrompt({ + workspaceDir: "/tmp/clawd", + sandboxInfo: { + enabled: true, + workspaceDir: "/tmp/sandbox", + workspaceAccess: "ro", + agentWorkspaceMount: "/agent", + elevated: { allowed: true, defaultLevel: "on" }, + }, + }); + + expect(prompt).toContain("You are running in a sandboxed runtime"); + expect(prompt).toContain("User can toggle with /elevated on|off."); + expect(prompt).toContain("Current elevated level: on"); + }); }); diff --git a/src/agents/system-prompt.ts b/src/agents/system-prompt.ts index ead95e2c5..829a82fab 100644 --- a/src/agents/system-prompt.ts +++ b/src/agents/system-prompt.ts @@ -31,6 +31,10 @@ export function buildAgentSystemPrompt(params: { agentWorkspaceMount?: string; browserControlUrl?: string; browserNoVncUrl?: string; + elevated?: { + allowed: boolean; + defaultLevel: "on" | "off"; + }; }; }) { const toolSummaries: Record = { @@ -219,7 +223,7 @@ export function buildAgentSystemPrompt(params: { params.sandboxInfo?.enabled ? "## Sandbox" : "", params.sandboxInfo?.enabled ? [ - "Tool execution is isolated in a Docker sandbox.", + "You are running in a sandboxed runtime (tools execute in Docker).", "Some tools may be unavailable due to sandbox policy.", params.sandboxInfo.workspaceDir ? `Sandbox workspace: ${params.sandboxInfo.workspaceDir}` @@ -237,6 +241,20 @@ export function buildAgentSystemPrompt(params: { params.sandboxInfo.browserNoVncUrl ? `Sandbox browser observer (noVNC): ${params.sandboxInfo.browserNoVncUrl}` : "", + params.sandboxInfo.elevated?.allowed + ? "Elevated bash is available for this session." + : "", + params.sandboxInfo.elevated?.allowed + ? "User can toggle with /elevated on|off." + : "", + params.sandboxInfo.elevated?.allowed + ? "You may also send /elevated on|off when needed." + : "", + params.sandboxInfo.elevated?.allowed + ? `Current elevated level: ${ + params.sandboxInfo.elevated.defaultLevel + } (on runs bash on host; off runs in sandbox).` + : "", ] .filter(Boolean) .join("\n")