Merge pull request #357 from dignfei/feat/docker-adopt-existing-container

feat(docker): attach a case to an already-running container

Conflicts came from work that landed after the PR was opened, and each is
resolved onto the newer abstraction rather than by keeping the older code:

- `defaultDockerCommandForMode` is registry-driven since #347, so the PR's
  `runsAsRoot` arm became `overlays.docker.rootCommand` (claude only). Claude
  Code still refuses `--dangerously-skip-permissions` as root in 2.1.261 and the
  refusal is visible only inside the container, so an adopted root container
  otherwise just shows a dead pane. Which flag to drop is a per-CLI fact, and
  `test/cli-registry-no-id-branching.test.ts` forbids expressing it as a branch.

- The probe's mode list and its mode -> binary table both duplicated the
  registry. They now read `enabledCliIds()` / `discovery.binaries[0]`, which is
  also what fixes the merge's silent regression: the hand-written list predates
  `omp`, and the run menu gates every docker case on this probe, so owned
  containers would have lost that mode. `shell` needs no arm — it declares no
  binary, so it is dropped from the lookup and reported available regardless.

- The per-mode `mode === 'claude' && !cliDir` chain in `tmux-manager.ts` is one
  `missingCliMessage(mode)` gate since #347; the PR's docker exemption moved onto
  it. Its test now pins the single gate instead of counting seven arms.

- The create arm keeps #349's swap-limit warning filter, which the adopted arm
  never reaches; the run-mode list gains `omp` from #353.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TecFD9hvPYJ1mkkMtBQbT1
This commit is contained in:
Codeman maintainer
2026-09-05 16:21:51 +02:00
144 changed files with 11972 additions and 1772 deletions
+3 -7
View File
@@ -72,7 +72,7 @@ import {
probeAdoptableContainer,
listDockerContainers,
browseInContainer,
DOCKER_ADOPT_PROBE_MODES,
dockerAdoptProbeModes,
readDockerCases,
readDockerHosts,
removeDockerContainer,
@@ -845,11 +845,7 @@ export function registerCaseRoutes(app: FastifyInstance, ctx: EventPort & Config
// hostWorkspacePath only because that is what an owned container's bind
// mount guarantees; adoption mounts nothing, so the probe has to prove it.
const adoptDocker = toSessionDocker(host, dockerCase);
const probe = await probeAdoptableContainer(
adoptDocker,
[...DOCKER_ADOPT_PROBE_MODES],
adoptDocker.containerWorkdir
);
const probe = await probeAdoptableContainer(adoptDocker, dockerAdoptProbeModes(), adoptDocker.containerWorkdir);
if (!probe.ok) {
return createErrorResponse(ApiErrorCode.OPERATION_FAILED, probe.error || 'container is not adoptable');
}
@@ -930,7 +926,7 @@ export function registerCaseRoutes(app: FastifyInstance, ctx: EventPort & Config
daemonHost: host.daemonHost,
containerName: body.container,
},
[...DOCKER_ADOPT_PROBE_MODES],
dockerAdoptProbeModes(),
body.containerWorkdir
);
return { success: true, data: probe };