feat(cli): deco sandbox new/chat/exec
Merged
PE
2d ago+794+794+012345678901234567890123456789-32-32-01234567890123456789
What is this contribution about?
Adds sandbox commands to the deco CLI. A CLI sandbox is a chat's sandbox, so the same chat shows up in Studio and can be driven from either side.
deco sandbox new --org <slug> [--agent <id>] # prints the chat id
deco sandbox chat <chat-id> "run the tests" --org <slug> # streams the agent's reply
deco sandbox exec <chat-id> --org <slug> -- git status # exits with the command's code
THREAD_SANDBOX_START(new, app-only, same permission group asSANDBOX_START): starts or resumes a chat's sandbox, resolving the agent and branch the way dispatch does. It reuses the create-time prewarm, nowstartThreadSandbox, so the CLI lands on the sandbox the chat's runs use instead of a second one. It returns the agent and branch that address the sandbox under/api/:org/sandbox/:agent/:branch. Withwait: falseit returns once the boot has started; the boot continues in the background the way the prewarm already does. CMS chats are refused.- Sandbox proxy: forwards
POST /bashto the daemon's existingbashroute. Callers who can reach a sandbox could already write files into it, so this grants no new access. Commands get no stdin and no TTY; the daemon awaits them with a 30s default and a 120s cap. new: creates a chat withruntime: "sandbox", on the org's default agent (no repo) or--agent(an agent with a repo gives a repo sandbox), and starts the boot without waiting.chat: opens the thread stream before posting, so an idle thread's reply isn't missed. It prints text deltas and tool names (tool-input-available, which both claude-code and Decopilot send) untilfinish. Which harness answers is still decided by the org and agent settings.exec: remembers each chat's sandbox address in the CLI home (24h, 100 entries) and calls/bashdirectly. On 404/410 no sandbox answered and the command did not run, so it starts the sandbox and retries once.
new racing the create-time prewarm (SANDBOX_PREWARM_ON_THREAD_CREATE) is safe: ensure is deduped per handle in a pod, and the providers handle an existing handle across pods.
How did you verify your code works?
apps/api/src/cli/commands/sandbox.test.ts(10 tests): argv quoting, reply streaming (text, tool lines, error, early close),execURL, body and exit code, timeout → 124, the address cache (start+bash, then bash only, then 404 → start+bash),chatrequest order and body,newon the default agent withwait: false, and tool errors.- Focused suites: tool registry and metadata, access control, built-in role permissions, CLI,
SANDBOX_START/SANDBOX_DELETE(239 pass). Lint, knip, and typecheck for api, shared, and web are clean. - End to end against a local Studio running chats on claude-code in Freestyle sandboxes:
newreturned in 0.35s, and the VM was created in the background.- The first
exectook 2.35s, of which 1.8s was the first command on a fresh VM. Cachedexectook 0.3–0.4s. execreturned stdout and stderr,exit 7, argv with spaces, and 124 on timeout.chathad Claude Code write a file, read back withexec, and printed[bash]tool lines. A reply took ~17–19s.
- The live run caught that claude-code never sends
tool-input-start; fixed by printing ontool-input-available.
How to Test
deco auth login --target <studio>.deco sandbox new --org <slug>→ prints a chat id.deco sandbox exec <id> --org <slug> -- 'pwd; ls'→/app/repo, exit 0. Run it again; it skips the start round trip.deco sandbox chat <id> "create hello.txt with hi" --org <slug>, thendeco sandbox exec <id> --org <slug> -- cat hello.txt→hi.- Open the chat in Studio: the messages are there.
Review Checklist
- PR title is clear and descriptive
- Changes are tested and working
- Documentation is updated (if needed)
- No breaking changes
Summary by cubic
Adds deco sandbox new/chat/exec so the CLI can create a coding chat, stream its agent's replies, and run shell commands in the same sandbox chat that Studio shows.
New Features
newcreates a sandbox chat and starts booting its sandbox without waiting;chatstreams text deltas and tool names untilfinish;execruns one command with no stdin or TTY (30s default, 120s cap), exits with the command's code, and caches each chat's sandbox address for 24h, retrying once after a start on 404/410.- New app-only
THREAD_SANDBOX_STARTtool (same permission group asSANDBOX_START) starts or resumes a chat's sandbox and returns the agent and branch that address it under/api/:org/sandbox/:agent/:branch; the sandbox proxy now forwards/bash.