···1111 server = "https://omni.lassul.us";
1212 omp = inputs.llm-agents.packages.${system}.omp;
13131414- # The agent this machine runs. omp speaks the Agent Client Protocol
1515- # (`omp acp`), so omnigent's generic `acp` harness drives it with no
1616- # adapter code. The command is the absolute store path because the spec
1717- # is consumed by the runner started here, and that path is in this
1818- # wrapper's closure — unlike the server-side catalog entry in
1919- # 2configs/omnigent.nix, which has to stay unqualified.
2020- #
2121- # os_env is what unlocks the dashboard's file panel: without it the
2222- # runner reports no OS environment and the changed-files/diff endpoints
2323- # 404. sandbox none because omp needs its own ~/.omp credential store and
2424- # unrestricted egress to the model APIs; confinement, if we want it, has
2525- # to happen a layer down (a separate uid, or nspawn), not here.
2626- #
2727- # omnigent_mcp: false is load-bearing. With it on, omnigent hands omp an
2828- # `mcpServers` entry in `session/new` whose command is
2929- # `sys.executable -I -m omnigent.claude_native_bridge serve-mcp`; `-I`
3030- # (isolated) makes that child ignore PYTHONPATH, so under a nixpkgs
3131- # console-script wrapping it cannot import omnigent. omp starts every
3232- # declared MCP server eagerly and fails the whole session when one dies —
3333- # `ACP session/new failed: Internal error`. The relay only bridges
3434- # *omnigent's* builtin tools into the agent, and omp brings its own, so
3535- # turning it off costs nothing here.
3636- spec = pkgs.writeText "omp-agent.yaml" ''
3737- name: omp
3838- prompt: You are omp (Oh My Pi), a concise coding assistant.
3939-4040- executor:
4141- harness: acp
4242- acp_agent:
4343- name: Oh My Pi
4444- command: ${omp}/bin/omp acp
4545- omnigent_mcp: false
4646-4747- os_env:
4848- type: caller_process
4949- cwd: "."
5050- sandbox:
5151- type: none
5252- '';
1414+ # omnigent's `pi-native` harness runs the agent's own TUI in a tmux pane,
1515+ # attaches your TTY to it, and mirrors the session (transcript, tool
1616+ # calls, terminal, files) to the server — the shape covibe always had,
1717+ # now with omnigent's per-session ACLs behind it. It resolves the CLI by
1818+ # the literal name `pi` (onboarding/harness_install.py: the readiness gate
1919+ # is `resolve_cli_binary("pi")`, which ignores OMNIGENT_PI_PATH and the
2020+ # config override), so omp has to reach it under that name. Version-gated
2121+ # flags are satisfied by omp reporting 17.x >= pi's declared 0.79.0 floor.
2222+ piShim = pkgs.writeShellApplication {
2323+ name = "pi";
2424+ text = ''
2525+ OMP_BIN=${omp}/bin/omp
2626+ ''
2727+ + builtins.readFile ./pi-shim.sh;
2828+ };
5329 in
5430 {
5555- # Launch omp as a session on the omni.lassul.us dashboard: the agent and
5656- # its runner stay on this machine (local runner + remote server
5757- # topology), while the server holds the transcript, the diff view and the
5858- # per-session ACLs, so a session can be shared read-only or read-write
5959- # with another pocket-id account. The terminal REPL and the browser drive
6060- # the same session.
3131+ # Launch omp as a session on the omni.lassul.us dashboard: omp's own TUI
3232+ # runs here, in a tmux pane owned by the local runner, while the server
3333+ # holds the transcript, the diff view and the per-session ACLs — so a
3434+ # session can be shared read-only or read-write with another pocket-id
3535+ # account, and a teammate's message from the browser executes in this
3636+ # TUI.
6137 #
6238 # First use per machine opens a browser for the pocket-id login; the
6339 # session JWT is cached in ~/.omnigent/auth_tokens.json afterwards.
6464- # Arguments go to `omnigent run`, so `covibe -p "fix the tests"` runs one
6565- # non-interactive turn and `covibe --fork <session-id>` continues someone
6666- # else's session here.
4040+ # Arguments go through to omp, e.g. `covibe --continue`; `covibe --resume`
4141+ # (no value) opens omnigent's session picker.
6742 packages.covibe =
6843 (pkgs.writeShellApplication {
6944 name = "covibe";
7045 runtimeInputs = [
7146 self'.packages.omnigent
7272- omp
4747+ piShim
4848+ pkgs.tmux
7349 pkgs.jq
7450 pkgs.git
7551 ];
···8056 echo "covibe: no session for $server yet, logging in" >&2
8157 omnigent login "$server"
8258 fi
8383- exec omnigent run ${spec} --server "$server" "$@"
5959+ exec omnigent pi --server "$server" "$@"
8460 '';
8561 }).overrideAttrs
8662 { passthru.usage = builtins.readFile ./usage.kdl; };
···11+# shellcheck shell=bash
22+# Argument translation between omnigent's `pi-native` harness and omp.
33+#
44+# omnigent builds `--extension <js> [--approve] --session-dir <dir>
55+# [--session <id>] <user args>` (omnigent/runner/native/orchestration.py
66+# _build_pi_native_args) against the upstream `pi` CLI at 0.79.x. omp 17.x
77+# differs in three ways:
88+#
99+# 1. `--approve` (pi's project-trust override) does not exist; omp exits
1010+# "unknown flag: --approve". Drop it — a human is attached to the TUI and
1111+# omp's own approval prompts are what we want.
1212+# 2. `--session <id>` is spelled `--resume <id>` in omp.
1313+# 3. omnigent's bridge extension is CommonJS (`module.exports = fn`), while
1414+# omp dynamic-imports the file and accepts only `module` /
1515+# `module.default` as the factory — a CJS file fails with "Extension does
1616+# not export a valid factory function". Point omp at a generated ESM
1717+# adapter that re-exports the CJS factory as a default export instead.
1818+# Verified: the status line then reads "· linked", prompts typed in the
1919+# TUI appear in the dashboard transcript, and messages sent from the
2020+# dashboard execute in the TUI.
2121+argv=()
2222+while [ "$#" -gt 0 ]; do
2323+ case "$1" in
2424+ --approve)
2525+ shift
2626+ ;;
2727+ --session)
2828+ argv+=(--resume "$2")
2929+ shift 2
3030+ ;;
3131+ --extension)
3232+ ext=$2
3333+ shift 2
3434+ adapter="$(dirname "$ext")/omnigent-esm-adapter.mjs"
3535+ if [ ! -e "$adapter" ]; then
3636+ cat >"$adapter" <<EOF
3737+import { createRequire } from "node:module";
3838+const require = createRequire(import.meta.url);
3939+export default require("$ext");
4040+EOF
4141+ fi
4242+ argv+=(--extension "$adapter")
4343+ ;;
4444+ *)
4545+ argv+=("$1")
4646+ shift
4747+ ;;
4848+ esac
4949+done
5050+5151+exec "$OMP_BIN" "${argv[@]}"
···11bin "covibe"
22-about "Run omp as a session on the omni.lassul.us omnigent dashboard (agent stays local, transcript + diff view + sharing live on the server)"
22+about "Run omp's TUI as a shared session on the omni.lassul.us omnigent dashboard (transcript, diff view and read-only/read-write sharing live on the server)"
3344-flag "-p --prompt" help="Run one non-interactive turn with this prompt and exit"
55-flag "--fork" help="Continue an existing session id from its fork point on this machine"
66-arg "[omnigent-run-args...]" help="Arguments passed through to `omnigent run`"
44+flag "-c --continue" help="Continue the previous omp session in this directory"
55+flag "-r --resume" help="Resume an omnigent session (no value opens the picker)"
66+arg "[omp-args...]" help="Arguments passed through to omp"