feat(sandbox): THREAD_SANDBOX_START creates the chat when threadId is omitted; --agent becomes --project

Merged
PE
8h ago
+190-131

Includes and supersedes #8432 (the decocms-studio skill section for sandboxes).

What is this contribution about?

THREAD_SANDBOX_START required a threadId, so an agent calling the tool directly had to create a chat first with COLLECTION_THREADS_CREATE, and deco sandbox new made two calls plus an org lookup. Now:

  • threadId is optional. Without it the tool creates a sandbox chat (runtime: "sandbox") on virtualMcpId, or the org's default agent, then binds repository if given and starts the sandbox. The output gains threadId. Passing both threadId and virtualMcpId is rejected. Asking for a repository on a project fails before any chat is created, so the refusal leaves no orphan chat.
  • deco sandbox new is one tool call, and no longer looks up the org id through /api/_me/organizations (that helper is removed).
  • --agent is now --project. "Project" is what people call an agent that has its own repository. This breaks --agent, which shipped in decocms 4.564.0 a few hours ago.
  • Skill: adds the sandbox section from #8432, uses --project, tells agents to use the commands rather than calling THREAD_SANDBOX_START (exec and chat reach routes no tool covers), and defines project in the vocabulary. Plugin 1.1.0.

The chat is created through COLLECTION_THREADS_CREATE.execute, so the caller also needs that tool's permission, and the create-time prewarm and chat_started event behave as for any other new chat.

How did you verify your code works?

  • apps/api/src/tools/sandbox/thread-start.test.ts: without a threadId the handler creates the chat on the default agent with runtime: "sandbox", starts the sandbox on it, and returns threadId; a repository with a project virtualMcpId is refused before the create is called; the input schema rejects threadId + virtualMcpId. The create and start are spied, not run.
  • apps/api/src/cli/commands/sandbox.test.ts: new, new --repo and new --project each make exactly one THREAD_SANDBOX_START call with the right body; --repo with --project exits 1 without a request.
  • registry.test.ts, decopilot/routes.test.ts pass. oxlint, knip, biome format, and tsc --noEmit for api, shared and web are clean.
  • Not run: an end-to-end boot against a running Studio.

How to Test

  1. deco sandbox new --org <slug> --repo <owner>/<name> → prints a chat id; the chat shows in Studio with the repository loaded.
  2. deco sandbox exec <id> --org <slug> -- git remote get-url origin → that repository.
  3. deco tools call THREAD_SANDBOX_START --org <slug> -d '{"wait":false}' → returns a new threadId.
  4. deco sandbox new --org <slug> --project <id> --repo <owner>/<name> → exits 1.

Review Checklist

  • PR title is clear and descriptive
  • Changes are tested and working
  • Documentation is updated (if needed)
  • No breaking changes — --agent is renamed to --project

Summary by cubic

THREAD_SANDBOX_START now creates a sandbox chat when threadId is omitted, so agents and deco sandbox new no longer need a separate create call. The CLI flag --agent is renamed to --project — a breaking change shipped in decocms 4.564.0 hours ago.

Behavior changes

  • Omitted threadId creates a chat with runtime: "sandbox" on virtualMcpId (or the org's default agent), binds repository if given, and starts the sandbox; the output now includes threadId.
  • Passing both threadId and virtualMcpId is rejected, and a repository request on a project fails before any chat is created, so the refusal leaves no orphan chat.
  • deco sandbox new makes one tool call and no longer resolves the org id via /api/_me/organizations; that helper was removed.
  • The tool creates the chat through COLLECTION_THREADS_CREATE, so the caller needs that tool's permission.
  • The decocms-studio skill (now 1.1.1) teaches agents to use deco sandbox new|exec|chat instead of calling THREAD_SANDBOX_START directly, and defines project in its vocabulary.

Migration

  • Replace --agent <id> with --project <id> on deco sandbox new.