mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-04 14:39:42 +02:00
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:
+17
-4
@@ -8,7 +8,7 @@
|
||||
* - SessionConfig — creation-time config (id, workingDir, createdAt)
|
||||
* - SessionOutput — captured stdout/stderr/exitCode
|
||||
* - SessionStatus — 'idle' | 'busy' | 'stopped' | 'error'
|
||||
* - SessionMode — 'claude' | 'shell' | 'opencode' | 'codex' | 'gemini' | 'antigravity' | 'pi' | 'grok' | 'deepseek' (which CLI backend)
|
||||
* - SessionMode — 'claude' | 'shell' | 'opencode' | 'codex' | 'gemini' | 'antigravity' | 'pi' | 'grok' | 'deepseek' | 'omp' (which CLI backend)
|
||||
* - ClaudeMode — CLI permission mode ('dangerously-skip-permissions' | 'auto' | 'normal' | 'allowedTools')
|
||||
* - SessionColor — visual differentiation color
|
||||
* - OpenCodeConfig — OpenCode-specific settings (model, autoAllowTools, continueSession)
|
||||
@@ -55,11 +55,12 @@ export type SessionMode =
|
||||
| 'antigravity'
|
||||
| 'pi'
|
||||
| 'grok'
|
||||
| 'deepseek';
|
||||
| 'deepseek'
|
||||
| 'omp';
|
||||
|
||||
export type RemoteCommandMode = Extract<
|
||||
SessionMode,
|
||||
'shell' | 'claude' | 'opencode' | 'codex' | 'gemini' | 'antigravity' | 'pi' | 'grok' | 'deepseek'
|
||||
'shell' | 'claude' | 'opencode' | 'codex' | 'gemini' | 'antigravity' | 'pi' | 'grok' | 'deepseek' | 'omp'
|
||||
>;
|
||||
|
||||
/**
|
||||
@@ -168,7 +169,7 @@ export interface RemoteSessionInfo {
|
||||
/** Which CLI backends a Docker case can run (same set as remote). */
|
||||
export type DockerCommandMode = Extract<
|
||||
SessionMode,
|
||||
'shell' | 'claude' | 'opencode' | 'codex' | 'gemini' | 'antigravity' | 'pi' | 'grok' | 'deepseek'
|
||||
'shell' | 'claude' | 'opencode' | 'codex' | 'gemini' | 'antigravity' | 'pi' | 'grok' | 'deepseek' | 'omp'
|
||||
>;
|
||||
|
||||
/** Container engine. Docker and Podman differ in the uid/userns + host-gateway alias. */
|
||||
@@ -383,6 +384,16 @@ export interface AntigravityConfig {
|
||||
resumeConversationId?: string;
|
||||
}
|
||||
|
||||
/** OMP CLI session configuration */
|
||||
export interface OmpConfig {
|
||||
/** Model identifier (e.g., "crof/glm-5.2"). Passed via --model. */
|
||||
model?: string;
|
||||
/** Resume a previous conversation (passed via --resume). */
|
||||
resumeSessionId?: string;
|
||||
/** Continue the most recent session in this directory (passed via --continue). */
|
||||
continueSession?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pi CLI (pi.dev) session configuration.
|
||||
*
|
||||
@@ -660,6 +671,8 @@ export interface SessionState {
|
||||
grokConfig?: GrokConfig;
|
||||
/** DeepSeek Harness configuration (only for mode === 'deepseek') */
|
||||
deepSeekConfig?: DeepSeekConfig;
|
||||
/** OMP-specific configuration (only for mode === 'omp') */
|
||||
ompConfig?: OmpConfig;
|
||||
/** Claude conversation session ID to resume after reboot (set by restore script) */
|
||||
resumeSessionId?: string;
|
||||
/** Claude CLI effort level (soft default via --settings, switchable in-session via /effort) */
|
||||
|
||||
+54
-3
@@ -7,6 +7,10 @@
|
||||
* (see `dataPath('update-status.json')`) that the browser polls across the
|
||||
* restart boundary.
|
||||
*
|
||||
* The Docker Compose deployment updates in place too (same script, same status
|
||||
* file) — see `docs/docker-self-update.md` for how the container restarts itself
|
||||
* and what the environment gate refuses.
|
||||
*
|
||||
* Backend logic: `src/web/self-update.ts`. Routes: `src/web/routes/system-routes.ts`
|
||||
* (`/api/system/update/check`, `POST /api/system/update`, `/api/system/update/status`).
|
||||
*
|
||||
@@ -17,11 +21,56 @@
|
||||
* Which init system supervises the running server (decides how we restart it).
|
||||
* `launchd-daemon` = a KeepAlive system-level LaunchDaemon (headless Macs, no GUI
|
||||
* login): restart works by killing the server and letting launchd respawn it.
|
||||
* `docker-compose` = the Compose deployment (`docker/docker-compose.yaml`): the
|
||||
* "restart" is the server exiting so the container's `restart: unless-stopped`
|
||||
* policy relaunches it on the freshly built `dist/`.
|
||||
*/
|
||||
export type SupervisorKind = 'systemd' | 'launchd' | 'launchd-daemon' | 'none';
|
||||
export type SupervisorKind = 'systemd' | 'launchd' | 'launchd-daemon' | 'docker-compose' | 'none';
|
||||
|
||||
/** How Codeman was installed — only `git` installs can self-update in place. */
|
||||
export type InstallKind = 'git' | 'npm' | 'unknown';
|
||||
/**
|
||||
* How Codeman was installed. `git` and `docker-compose` can self-update in
|
||||
* place; `docker-compose` is a git checkout bind-mounted into the container, so
|
||||
* the pull/build happen on the host filesystem and survive container recreation.
|
||||
*/
|
||||
export type InstallKind = 'git' | 'docker-compose' | 'npm' | 'unknown';
|
||||
|
||||
/**
|
||||
* Why an in-place container update is refused. Each is derived mechanically from
|
||||
* the target release's own files — nothing here depends on a human remembering
|
||||
* to declare something at release time.
|
||||
*
|
||||
* - `dockerfile-changed` / `compose-changed`: the release changes the ENVIRONMENT,
|
||||
* which a self-restart cannot apply (a restart reuses the existing container's
|
||||
* image and config). Needs a rebuild + recreate from the host.
|
||||
* - `env-keys-missing`: the release's `docker/.env.example` gained keys the user's
|
||||
* `docker/.env` has no value for. Compose interpolates an unset `${VAR}` to the
|
||||
* EMPTY STRING and starts anyway, so without this check a new required setting
|
||||
* arrives as a silently blank env var.
|
||||
* - `no-auto-restart`: the container's restart policy would not bring it back
|
||||
* after the server exits, so applying the update would take Codeman down.
|
||||
*/
|
||||
export type EnvironmentBlockerKind = 'dockerfile-changed' | 'compose-changed' | 'env-keys-missing' | 'no-auto-restart';
|
||||
|
||||
/** One reason an in-place container update is refused, with UI-ready text. */
|
||||
export interface EnvironmentBlocker {
|
||||
kind: EnvironmentBlockerKind;
|
||||
/** One-line explanation shown in App Settings → Updates. */
|
||||
message: string;
|
||||
/** Optional specifics (e.g. the names of the missing env keys). */
|
||||
details?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of the environment gate for a candidate release. `checked: false` means
|
||||
* the gate did not run (not a container install, or the target tag's files could
|
||||
* not be read) — callers must not treat that as "no blockers".
|
||||
*/
|
||||
export interface EnvironmentGate {
|
||||
checked: boolean;
|
||||
blockers: EnvironmentBlocker[];
|
||||
/** The host command that resolves every blocker. */
|
||||
hostCommand: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lifecycle of a single update run. `idle`/`completed`/`failed`/
|
||||
@@ -96,6 +145,8 @@ export interface UpdateCheckResult {
|
||||
/** epoch ms of the check. */
|
||||
checkedAt: number;
|
||||
source: 'github-api' | 'git-ls-remote' | 'none';
|
||||
/** Environment gate for THIS candidate release (container installs only). */
|
||||
environment?: EnvironmentGate;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user