mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 20:49:41 +02:00
SessionMode gains 'pi', a first-class backend alongside Claude Code, OpenCode, Codex, Gemini and Antigravity: its own PTY, tmux session, rose tab identity, welcome button, run-mode entry, cron agentType, Docker and remote-SSH command defaults, and clone-repo Brain option. Pi is a different shape of CLI from the other four, and three decisions follow from that: - It has NO permission prompts and no sandbox, so there is no --dangerously-skip-permissions analog and none was invented. The privilege-shaped knob is the tri-state approveProjectTrust, which makes pi load and EXECUTE repo-local .pi/extensions TypeScript and install missing project packages. clampExternalCliBypassForOwner() therefore puts pi in the MATERIALIZE branch: a non-granted multi-user owner gets --no-approve even when no config was sent, because pi's own default is a prompt the session user could answer themselves. That helper had zero test coverage; it now has coverage for all four CLIs. - Only the PI_ prefix joins the env allowlist. Pi's ~34 provider key vars share no prefix and ALLOWED_ENV_PREFIXES is one global list with no mode context, so admitting them would widen the allowlist for every mode at once. Auth goes through pi's /login or the server's own environment. --api-key is deliberately never wired: it would put a provider secret on the spawn command line. - pi stays OUT of isAltScreenStripMode(). Its default TUI renders into the main screen with terminal-owned scrollback, and its 0.84.0 fullscreen mode is runtime-switchable via /settings; that flip was measured to put the pane into the alt screen, which the strip would have corrupted. pi-cli-resolver.ts additionally sanity-probes `pi --version` and requires semver-shaped output, because `pi` is a short generic name a stray binary can shadow; GET /api/pi/status surfaces path and version so a misresolution is diagnosable rather than presenting as a broken mode. Docker installs pi in its own --ignore-scripts step so that flag cannot affect the other four CLIs, and seeds its credentials per-file rather than whole-dir (~/.pi/agent also holds sessions, extensions and package trees). Verified end to end against pi 0.84.1 on an isolated instance: resolver search-dir fallback, flag construction, piConfig persistence across a full server restart, the trust prompt and its --no-approve suppression, the rose Run button on the default daylight-blue skin (the nested skin block eats per-mode gradients unless the rule lives inside it), and the buffer local-echo policy, which pi tolerates where codex did not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
85 lines
4.2 KiB
Docker
85 lines
4.2 KiB
Docker
# Codeman agent base image (built locally by scripts/build-agent-image.mjs).
|
|
#
|
|
# Contains the agent toolchain (node + the CLIs + git/tmux/ripgrep) but NO
|
|
# secrets: credentials are delivered at RUNTIME via bind mounts (~/.claude etc.)
|
|
# or name-only `docker exec --env`, never baked in, so `docker save` exports stay
|
|
# secret-free. tmux is a HARD prerequisite (the in-container tmux is what makes a
|
|
# reconnect durable), so it is installed here and probed before launch.
|
|
#
|
|
# HOME is made writable by an ARBITRARY host uid via the OpenShift "gid 0,
|
|
# group-writable" convention: on Linux we run `--user <hostUid>:0`, so the agent
|
|
# uid is the host uid (workspace files stay host-owned) while gid 0 keeps $HOME
|
|
# writable even though the uid is not the baked 1000.
|
|
FROM node:22-bookworm-slim
|
|
|
|
# Base toolchain. `curl` is needed for the hook callbacks (`curl -sk $CODEMAN_API_URL`),
|
|
# `procps` for `ps`, `tmux` for the durable in-container session.
|
|
RUN apt-get update \
|
|
&& apt-get install -y --no-install-recommends \
|
|
git \
|
|
tmux \
|
|
ripgrep \
|
|
curl \
|
|
ca-certificates \
|
|
less \
|
|
procps \
|
|
openssh-client \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# The npm-published agent CLIs. Pinning is left to the rebuild cadence (see
|
|
# docs/docker-cases-plan.md, user-decision 2).
|
|
RUN npm install -g \
|
|
@anthropic-ai/claude-code \
|
|
@openai/codex \
|
|
@google/gemini-cli \
|
|
opencode-ai \
|
|
&& npm cache clean --force
|
|
|
|
# Antigravity (`agy`) is NOT on npm — Google ships a standalone binary through its
|
|
# own installer, so it needs its own step. `--dir /usr/local/bin` is load-bearing:
|
|
# the installer's default target is `$HOME/.local/bin`, which at build time is
|
|
# root's home and would be unreachable by the `agent` user the container runs as.
|
|
# ⚠️ This binary is ~190MB on its own; it is the single largest layer in the image.
|
|
RUN curl -fsSL https://antigravity.google/cli/install.sh | bash -s -- --dir /usr/local/bin \
|
|
&& chmod 755 /usr/local/bin/agy \
|
|
&& agy --version
|
|
|
|
# Pi (pi.dev). Upstream documents --ignore-scripts (pi needs no lifecycle scripts);
|
|
# kept out of the shared npm block above so the flag cannot silently change how the
|
|
# other four CLIs install.
|
|
RUN npm install -g --ignore-scripts @earendil-works/pi-coding-agent \
|
|
&& npm cache clean --force \
|
|
&& pi --version
|
|
|
|
# `agent` user (gid 0) with an arbitrary-uid-writable HOME. The uid is
|
|
# auto-assigned (node:22-slim already occupies uid 1000 with its `node` user); at
|
|
# runtime Codeman overrides with `--user <hostUid>:0` on Linux, so the baked uid
|
|
# only matters for a hand-run / Docker Desktop container. gid 0 + group-writable
|
|
# HOME (OpenShift arbitrary-uid convention) keeps $HOME writable for any uid.
|
|
# UTF-8 locale so tmux/Ink render Unicode box-drawing instead of VT100 ACS `q`
|
|
# glyphs (C.UTF-8 is built into glibc; no locales package needed). Codeman also
|
|
# sets these at run time so containers built before this line still get UTF-8.
|
|
ENV LANG=C.UTF-8 LC_ALL=C.UTF-8
|
|
ENV HOME=/home/agent
|
|
# `.claude` (+ `.claude/projects` mount point) and `.codex` (+ `.codex/sessions`) are
|
|
# pre-created gid-0 group-writable so the container owns its OWN credential config
|
|
# dirs: tokens/settings/config are seeded in as writable copies and each CLI's runtime
|
|
# state (backups, tasks, refreshed tokens) stays container-local, while ONLY the shared
|
|
# transcript/rollout dirs (`.claude/projects`, `.codex/sessions`) are bind-mounted from
|
|
# the host. (gemini/gcloud/opencode are whole seed-copies and need no pre-created dir;
|
|
# Antigravity nests its state inside `.gemini/antigravity-cli`, so it rides that seed.)
|
|
# `.pi/agent` IS pre-created: pi is seeded per-FILE (auth/settings/trust/models), and a
|
|
# per-file seed copy, unlike a whole-dir one, does not create its parent directory.
|
|
RUN useradd -g 0 -m -d /home/agent -s /bin/bash agent \
|
|
&& mkdir -p /home/agent/.npm /home/agent/.cache /home/agent/.config /home/agent/.codeman \
|
|
/home/agent/.claude/projects /home/agent/.codex/sessions /home/agent/.pi/agent \
|
|
&& chgrp -R 0 /home/agent \
|
|
&& chmod -R g=u /home/agent
|
|
|
|
USER agent
|
|
WORKDIR /home/agent
|
|
|
|
# Codeman overrides the command with `sleep infinity` at create time; this is the
|
|
# fallback so a hand-run container also idles rather than exiting.
|
|
CMD ["sleep", "infinity"]
|