feat(cli): deco sandbox new/chat/exec

Merged
PE
2d ago
+794-32

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 as SANDBOX_START): starts or resumes a chat's sandbox, resolving the agent and branch the way dispatch does. It reuses the create-time prewarm, now startThreadSandbox, 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. With wait: false it 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 /bash to the daemon's existing bash route. 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 with runtime: "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) until finish. 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 /bash directly. 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), exec URL, body and exit code, timeout → 124, the address cache (start+bash, then bash only, then 404 → start+bash), chat request order and body, new on the default agent with wait: 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:
    • new returned in 0.35s, and the VM was created in the background.
    • The first exec took 2.35s, of which 1.8s was the first command on a fresh VM. Cached exec took 0.3–0.4s.
    • exec returned stdout and stderr, exit 7, argv with spaces, and 124 on timeout.
    • chat had Claude Code write a file, read back with exec, 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 on tool-input-available.

How to Test

  1. deco auth login --target <studio>.
  2. deco sandbox new --org <slug> → prints a chat id.
  3. deco sandbox exec <id> --org <slug> -- 'pwd; ls' → /app/repo, exit 0. Run it again; it skips the start round trip.
  4. deco sandbox chat <id> "create hello.txt with hi" --org <slug>, then deco sandbox exec <id> --org <slug> -- cat hello.txt → hi.
  5. 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

  • new creates a sandbox chat and starts booting its sandbox without waiting; chat streams text deltas and tool names until finish; exec runs 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_START tool (same permission group as SANDBOX_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.