Files
Codeman/docs/omp-integration.md
T
Codeman maintainer 112c533ac7 fix(omp): read omp's status bar so an omp turn can end
An omp session stayed "working" for good once a turn started, the same
latch pi had: omp's braille spinner trips the SPINNER_PATTERN fast path,
and only a composer glyph arms the idle confirmation. omp declared none,
so it fell back to Claude's `❯`, which omp never draws once its setup
wizard is done.

Measured on live omp 18.8.6 and 18.0.11 panes, holding a turn open
against a local endpoint that never answers: the input row is `╰─ <text>`
and is redrawn at submit, at the end of a turn, at launch and on
reattach. While a turn runs, the status bar's leading `π` becomes a
braille spinner plus the elapsed time (` ⠼ 14s > ⬢ model > ...`; 18.0.11
pads it with two spaces, past a minute it reads `1m`), with a
`⎋ Working…` row above it. The registry entry now names the input row as
the glyph and either working signal as the working line.

The glyph also switches the submit verifier on for omp, which reads the
input row the way it reads Claude's composer. A prompt sent mid-turn goes
to omp's Steering queue and clears the row, so the verifier stands down.
Text left in the row after an Enter is the one case it re-presses.

Verified end to end on a sandboxed instance (own HOME and PATH, omp
18.8.6): session:idle at launch, session:working during a turn,
session:idle about 3 s after it ended, and a restored pane settled idle
about 3 s after a server restart.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-09 02:43:22 +02:00

10 KiB
Raw Blame History

OMP (Oh My Pi) sessions

Codeman can drive OMP (omp, Oh My Pi) as a session backend, alongside Claude Code, OpenCode, Codex, Gemini, Antigravity, Pi, Grok and DeepSeek Harness. omp is the ninth CLI backend (tenth SessionMode, counting shell): its own PTY, its own tmux session, its own tab identity. It is not a location overlay like Docker or remote-SSH cases, and it is not a web tab.

Install

curl -fsSL https://omp.sh/install | sh

The installer places the binary in ~/.local/bin (verified against a real --no-cache Docker build — see docker/agent.Dockerfile; an earlier guess of ~/.omp/bin was wrong). Codeman resolves the binary via the server PATH and then the usual install locations (~/.local/bin first, then ~/.omp/bin, /usr/local/bin, ~/.bun/bin, ~/.npm-global/bin, ~/bin).

omp is a short name, so like pi and grok the resolver does not trust a PATH hit on its own: it runs omp --version and requires omp/<semver>-shaped output (e.g. omp/18.0.8) before accepting a candidate. Check what it resolved:

curl -s localhost:3000/api/omp/status | jq
# { "available": true, "path": "/home/you/.local/bin", "version": "18.0.8" }

Authenticate

OMP owns its own auth and provider configuration entirely in ~/.omp — there is no Codeman-side login flow, API key field, or bypass switch to configure. Run omp directly once outside Codeman to complete whatever onboarding the CLI itself asks for; every session started through Codeman afterward inherits that config.

What Codeman wires up

OmpConfig (per session, persisted in state.json, round-trips through respawn):

Field Flag Notes
model --model <v> Regex-validated ([a-zA-Z0-9._-/]+); provider/model forms like crof/glm-5.2 pass
continueSession --continue omp's own "most recent conversation in this directory" heuristic
resumeSessionId --resume <id> Ids only, id-regexed; wins over --continue when both are present

Every value is regex-validated and dropped (not escaped) if it fails, because the result is interpolated into the pane's spawn command.

omp reads its own model routing and hooks from ~/.omp, so no trust or permission flags are needed — unlike every sibling CLI in this family, there is no bypass-permissions equivalent to wire up, so buildOmpCommand() only ever passes --model/--resume/--continue. ⚠️ That does NOT mean omp is unrestricted: its documented default tools.approvalMode is yolo, so an omp pane auto-approves exec with no flag from Codeman — the CLI's own config, not Codeman, is what would need to change that.

Env overrides: the OMP_* prefix is allowlisted, and per omp's own docs/environment-variables.md it is not the narrow surface it looks like. omp reads roughly 40 provider keys from the environment (ANTHROPIC_API_KEY, OPENAI_API_KEY, XAI_API_KEY, HF_TOKEN, ...) — pi's 34-key problem in the same shape — which is why none of those get a dedicated allowlist entry; a session authenticates from ~/.omp config or the server process's own env instead, like pi. omp's own documented knobs are mostly PI_*, not OMP_* (PI_CONFIG_DIR, PI_CODING_AGENT_DIR, PI_CODING_AGENT_SESSION_DIR, PI_SUBPROCESS_CMD, PI_SHELL_PREFIX, OMP_PROFILE/PI_PROFILE), and PI_* is already allowlisted globally because pi mode needs it — so an omp session today already accepts all of those. The first three also move the tree omp-session-resolver.ts and omp-transcript.ts hardcode (resolveOmpHome() assumes ~/.omp unconditionally), so pinning and history quietly stop working under a redirected config root; this is a known gap, not fixed here.

The OMP_ prefix itself brings in OMP_AUTH_BROKER_URL / OMP_AUTH_BROKER_TOKEN, where omp resolves credentials from — the same shape DEEPSEEK_BASE_URL is dropped for in clampEnvOverridesForOwner() (session-routes.ts), so both are clamped there for a non-granted owner in multi-user mode. None of this matters in single-user mode.

Exact-id pinning: why --resume, not just --continue

--continue alone is ambiguous the moment any other omp conversation has touched the same working directory more recently — it just picks the newest session file on disk, silently. That happens routinely: a closed-then-resumed Codeman row plus a still-running duplicate, two Codeman sessions pointed at the same case, or a plain reattach after a server restart.

src/utils/omp-session-resolver.ts resolves and pins the exact conversation id once (findLatestOmpSessionId() reads ~/.omp/agent/sessions/<mangled-workingDir>/, the newest .jsonl file's embedded uuid), then every later respawn reuses that pinned id via --resume instead of re-guessing with --continue.

⚠️ The directory mangling is NOT a straight / → - replace. Unlike Claude Code's ~/.claude/projects/* convention (which keeps the full path, e.g. -home-user-codeman-cases-foo), omp strips the $HOME prefix FIRST and only then dash-replaces (/home/user/codeman-cases/foo → -codeman-cases-foo; a path outside $HOME, like /tmp/..., is dash-replaced as-is with no stripping). Getting this wrong doesn't error — findLatestOmpSessionId() just silently returns null for every case under $HOME (virtually all real Codeman cases), so pinning quietly degrades to omp's own ambiguous --continue. This was found and fixed 2026-08-27 after months of testing had only ever exercised /tmp-based working directories, where the bug's wrong output happened to coincidentally match the right one.

Surviving a full session kill

src/omp-transcript.ts scans ~/.omp/agent/sessions/**/*.jsonl directly — a second, independent history source alongside Codeman's own state. This means an OMP conversation's history (working directory, first/last prompt, size) is recoverable in the Past Sessions list even when both the Codeman session record and the underlying tmux pane are gone — verified live against a full OS reboot, not just a "Kill Tmux" button click.

Terminal behavior

OMP renders inside tmux like every external CLI (narrow scrollback strip — alt-screen toggles only, not the full Claude/Codex/Gemini strip). It stays out of the alt-screen-strip list and lands on the 'buffer' local-echo policy via the _updateLocalEchoState fallthrough, same as grok and pi.

Docker cases

The agent image installs omp in its own Dockerfile step (not npm; omp's installer targets $HOME/.local/bin with no --dir override, the same shape as grok's installer). Rebuild with the mandatory --no-cache:

node scripts/build-agent-image.mjs --no-cache

⚠️ --resume pinning does not currently reach an in-container omp process. Docker panes are built from defaultDockerCommandForMode, which never sees ompConfig — appendResumeFlag()'s case 'omp' keys off the top-level resumeSessionId field, which nothing populates for omp today. Host-side history recovery still works (the shared sessions/ mount below), but a respawned in-container omp pane falls back to its own ambiguous --continue, not a pinned id. Flagged in upstream review, not yet fixed.

Credentials are mostly seeded, but sessions/ is the one exception in this CLI family: ~/.omp/agent/{config.yml,mcp.json,models.yml,settings.yml} are seeded (read-only mount, copied into the container's own ~/.omp/agent once), so an in-container omp never writes refreshed config back to the host and docker commit exports stay secret-free. But ~/.omp/agent/sessions/ is shared (RW), not seeded — the same treatment as codex's sessions/, and for the identical reason: Codeman reads it host-side (omp-transcript.ts, omp-session-resolver.ts) for history recovery and --resume pinning. Seeding it instead of sharing it would make an in-container OMP conversation invisible to Codeman's own history/resume logic, silently breaking Docker support for the kill-survival feature above. The rest of ~/.omp/agent (agent.db/history.db/models.db SQLite caches, terminal-sessions/, blobs/, cache/) stays container-local and is neither shared nor seeded.

Remote SSH cases

omp mode is routed through an interactive login shell (exec "$SHELL" -i -l -c 'omp'), because sshd's remote-command PATH does not include ~/.local/bin. Per-session config and envOverrides do not cross ssh and are rejected rather than silently ignored; use the per-host command override instead.

⚠️ A respawn or reattach of a remote omp session runs omp --continue, not a bare omp, so it lands back in the same conversation. It is deliberately --continue rather than the exact --resume <id> the local and docker paths pin: omp-session-resolver.ts only ever reads THIS host's ~/.omp/agent/sessions/, and a remote conversation's session file lives on the remote host under the remote user's home, so resolving locally would pin a stranger's id. See Respawn / reattach continuation.

Known gaps

  • No idle/completion hook. Idle detection reads the screen instead: the registry entry's workDetect names omp's ╰─ input row as the glyph that arms the idle check, and the status bar's spinner plus elapsed time ( ⠼ 14s > ⬢ …) or the ⎋ Working… row as the working line, measured on omp 18.8.6 and 18.0.11. Without it an omp session that had started a turn never left busy. If omp ever ships a hooks system, a Codeman hook POSTing to /api/hook-event would still be the highest-value follow-up.
  • Killing a pane mid-turn loses the conversation for real. tmux kill-session before an in-TUI /exit beats omp's own session-file flush — confirmed by direct testing (kill after a clean /exit resumes correctly; kill without /exit first does not). This is not something Codeman can compensate for from outside the process; it would need an upstream omp fix (e.g. flush-on-SIGTERM).
  • Unverified: $HOME as a symlink. The directory-mangling fix above compares against the literal homedir() string, not a realpath()-resolved one. Whether omp itself canonicalizes symlinks before mangling is unconfirmed — this has not been tested against a symlinked-home setup.
  • Ralph, respawn heuristics, token/CLI-info parsing and the ❯ readiness probe are off for omp, as for every external CLI.