Skip to content

feat(providers): docker provider foundation (pty / filesystem / git)#1349

Open
mvanhorn wants to merge 2 commits intostablyai:mainfrom
mvanhorn:feat/docker-provider-foundation
Open

feat(providers): docker provider foundation (pty / filesystem / git)#1349
mvanhorn wants to merge 2 commits intostablyai:mainfrom
mvanhorn:feat/docker-provider-foundation

Conversation

@mvanhorn
Copy link
Copy Markdown
Contributor

@mvanhorn mvanhorn commented May 2, 2026

Summary

Adds Docker as a fourth provider triple alongside local and SSH. Pure plumbing: pty / filesystem / git providers + docker engine detection + image build + container lifecycle + bind-mount path resolution. No UI, no dispatch wiring.

This is the foundation PR for an opt-in Docker isolation feature. Follow-up PRs add the worktree-level toggle and dispatch routing. Worktree-native stays the default and remains zero-friction for everyone else.

Motivation: Sculptor (Imbue) ships per-task Docker container isolation as its pillar feature, and users in agent-orchestration discussions ask for the same primitive on Orca. Issue #993 ("Something like an ai orchestrator") and Discussion #681 (multi-agent orchestration coordinator pattern, 15 comments) both signal demand for sandboxed agent execution.

Architectural fit: src/main/providers/ already abstracts pty / filesystem / git through three interfaces with two implementations (local, SSH). Docker is a third target. The new triple mirrors the SSH triple's API exactly so the upcoming dispatch PR slots in cleanly.

Screenshots

No visual UI change. This PR is foundation-only: provider classes plus docker engine modules with no dispatch wiring or runtime behavior yet (the follow-up PR adds the user-facing toggle, the third PR wires it up).

A simulated demo of the architectural change this PR adds:

Simulated demo: docker provider triple foundation

Simulated demo (Remotion). Shows the three provider triples (local, ssh, docker-new) and the receipts: 17 files, 2474 lines, 41 tests, 0 dispatch edits.

Testing

  • pnpm lint
  • pnpm typecheck
  • pnpm test --run src/main/providers/docker-*.test.ts src/main/docker/
  • pnpm build
  • Added 41 tests across 7 files covering happy and error paths through the engine fake. Cross-platform branches (macOS Colima + Desktop, Linux Engine + rootless, Windows WSL2 path translation) covered via injected platform. Real Docker integration is out of scope for this PR and gated behind RUN_DOCKER_E2E=1 for follow-up work.

AI Review Report

A code review pass was run before submission. Risks checked:

  • Cross-platform compatibility: macOS (Docker Desktop, Colima socket), Linux (Engine + rootless socket), Windows (Docker Desktop WSL2 named pipe + drive-letter to /mnt/<drive> path translation). All platform branches covered in docker-engine-detect.test.ts and docker-mount.test.ts via injected platform so the same suite passes on each OS.
  • Provider contract parity with the local / SSH implementations: getDiff and getBranchDiff return originalContent and modifiedContent (not unified-patch text); rename parsing keeps path as the new path and oldPath as the old; porcelain v2 u records surface as conflict entries; getRemoteFileUrl resolves the actual default branch via origin/HEAD instead of hardcoding main. These were flagged and fixed before submission.
  • TTY allocation: DockerPtyProvider requests tty: true from the engine client so interactive programs see a real terminal. Caveat below.
  • AGENTS.md compliance: file naming (concrete domain names, no helpers.ts/utils.ts), project types in .ts not .d.ts, no hardcoded metaKey, all paths via path.join or path.posix.join.

Known caveat surfaced by review: when wired in a future PR, docker exec -t from a piped-stdio Electron parent can fail with "input device is not a TTY" on some packaged builds. The cleanest fix is wrapping the docker CLI under node-pty, matching the local provider's pattern. This PR's TTY flag is correct; the wrapping happens in the dispatch PR. Search-flag handling (useRegex, wholeWord, include/exclude patterns) and binary-blob metadata in diffs are also follow-up items, called out for the wiring PR.

Security Audit

Input handling: only the host paths the user already trusts (worktree path, Dockerfile path) flow into engine commands. No user-supplied strings are interpolated into shell.

Command execution: the engine client builds argv arrays for child_process.spawn (no shell). All user paths land as separate argv entries, never concatenated.

Path handling: bind-mount source is the host worktree; mount target is /workspace inside the container. Windows paths are translated via the dedicated helper with vitest coverage so the translation is auditable.

IPC and preload: this PR does not add or change any IPC handler. The provider classes are not yet routed through provider-dispatch. No new exposed surface from main to renderer.

Dependencies: no package.json or pnpm-lock.yaml changes.

Auth and secrets: no auth flow changes. The agent inside a container would run with the host user's uid/gid (when wired in PR 3); credentials remain on the host filesystem and are not copied into images.

Follow-up: the dispatch PR will need its own audit covering how routing decisions are gated and how the toggle persists.

Notes

This is PR 1 of a 3-PR series. The other two PRs add the user-facing toggle (worktree row, repo settings) and the dispatch wiring (provider-dispatch + orca-runtime + image cache UX). Each lands independently; PR 1 has no observable behavior change on its own.

Tested on macOS. Linux and Windows behavior covered in unit tests via injected platform.

X: @mvanhorn

AI was used for assistance.

mvanhorn added 2 commits May 1, 2026 23:36
Adds Docker as a fourth provider triple alongside local and SSH.
Pure plumbing: pty / filesystem / git providers + docker engine
detection + image build + container lifecycle + bind-mount path
resolution. No user-facing UI yet, no dispatch wiring.

Mirrors the SSH provider triple's API. Cross-platform: macOS
(Docker Desktop / Colima), Linux (Docker Engine, rootless), Windows
(Docker Desktop WSL2 named pipe).

Tests cover happy and error paths through a docker-engine-fake.ts
recorder, so no real Docker daemon is required.

Foundation PR for the docker isolation series. Follow-ups will add
the user-facing toggle and dispatch wiring.
- Allocate TTY for Docker PTY sessions via tty flag on spawnExec, so
  isatty() returns true for shells, vim, and ncurses programs.
- Return originalContent + modifiedContent for working-tree diffs
  instead of returning a unified patch as modifiedContent.
- Return blob contents for branch diffs and honor options.oldPath for
  renames, matching the local provider's GitDiffResult shape.
- Preserve the new path as path and the old path as oldPath when
  parsing R100 rename entries (was reversed).
- Surface git porcelain v2 u records as unresolved conflict entries
  so docker worktrees in conflict show their conflicted files.
- Resolve repo default branch via origin/HEAD before browseFile,
  instead of hardcoding 'main' for remote file URLs.
@AmethystLiang
Copy link
Copy Markdown
Contributor

@nwparker should be able to get to this soon!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants