diff --git a/full-ai-cluster/nixos/hosts/control-plane/configuration.nix b/full-ai-cluster/nixos/hosts/control-plane/configuration.nix index 0bc6880c33..8cb23209ef 100644 --- a/full-ai-cluster/nixos/hosts/control-plane/configuration.nix +++ b/full-ai-cluster/nixos/hosts/control-plane/configuration.nix @@ -40,9 +40,9 @@ # architectural pattern). Operator can disable any persona via # `systemctl disable zeta-` per NCI HC-8 revocable consent. zeta.aiAgents.enable.otto = true; - zeta.aiAgents.enable.lior = true; # B-0850.3d SHIPPED (Gemini CLI 2nd vendor — Anthropic + Google) + zeta.aiAgents.enable.lior = true; # B-0850.3d SHIPPED (Gemini CLI 2nd vendor) + zeta.aiAgents.enable.vera = true; # B-0850.3c SHIPPED (Codex 3rd vendor — hits ≥3 BFT floor: Anthropic + Google + OpenAI) # zeta.aiAgents.enable.alexa = true; # B-0850.3a pending (Kiro/Qwen) - # zeta.aiAgents.enable.vera = true; # B-0850.3c pending (Codex/OpenAI) # zeta.aiAgents.enable.riven = true; # B-0850.3b pending (Grok) # Static IP recommended so worker nodes have a stable serverAddr. diff --git a/full-ai-cluster/nixos/modules/zeta-ai-agent.nix b/full-ai-cluster/nixos/modules/zeta-ai-agent.nix index e372dbb955..c3b47bfa97 100644 --- a/full-ai-cluster/nixos/modules/zeta-ai-agent.nix +++ b/full-ai-cluster/nixos/modules/zeta-ai-agent.nix @@ -51,6 +51,10 @@ let otto = { vendor = "anthropic"; binary = "claude"; + # Claude Code uses --print for non-interactive one-shot mode + # with the <> sentinel triggering the + # autonomous-loop skill (per `.claude/rules/tick-must-never-stop.md`). + invocationArgs = [ "--print" "<>" ]; description = "Otto AI agent — Claude Code (Anthropic)"; }; @@ -60,6 +64,7 @@ let alexa = { vendor = "alibaba-qwen"; binary = "kiro"; # placeholder; verify per sub-row + invocationArgs = [ ]; # placeholder per sub-row description = "Alexa AI agent — Kiro (Qwen Coder)"; }; @@ -67,20 +72,31 @@ let riven = { vendor = "xai-grok"; binary = "grok"; # placeholder; grok-build CLI per peer-call + invocationArgs = [ ]; # placeholder per sub-row description = "Riven AI agent — Grok / Grok-Build (xAI)"; }; - # Sub-row B-0850.3c target — Codex/OpenAI integration + # Sub-row B-0850.3c — Codex/OpenAI integration (shipped this PR). + # Codex uses `exec` SUBCOMMAND (not a --print flag) for non- + # interactive mode per the codex CLI docs. The <> + # string is a Claude Code sentinel; codex sees it as a literal + # prompt and responds conversationally (acceptable for first ship; + # B-0850 Phase 3.x can introduce per-vendor prompt mapping). vera = { vendor = "openai"; - binary = "codex"; # placeholder; verify per sub-row + binary = "codex"; + invocationArgs = [ "exec" "<>" ]; description = "Vera AI agent — Codex (OpenAI)"; }; - # Sub-row B-0850.3d target — Gemini CLI integration + # Sub-row B-0850.3d — Gemini CLI integration (shipped via PR #5397). + # Gemini uses -p flag (NOT --print) for non-interactive prompts. + # Like codex, the <> sentinel is a Claude Code + # convention; gemini sees it as a literal prompt. lior = { vendor = "google-gemini"; - binary = "gemini"; # placeholder; gemini-cli per peer-call + binary = "gemini"; + invocationArgs = [ "-p" "<>" ]; description = "Lior AI agent — Gemini CLI (Google)"; }; }; @@ -118,8 +134,11 @@ let sleep 10 # Autonomous-loop ticks — fresh persona invocation per tick; # substrate continuity via repo memory + git + bus envelopes. + # Per-persona invocationArgs (P0 fix per Copilot review on + # PR #5398: each CLI has different non-interactive flags — + # claude --print, gemini -p, codex exec — NOT all --print). while true; do - ${cfg.home}/.bun/bin/${persona.binary} --print "<>" 2>&1 || true + ${cfg.home}/.bun/bin/${persona.binary} ${lib.concatStringsSep " " (map (a: "\"${a}\"") persona.invocationArgs)} 2>&1 || true sleep ${toString cfg.tickIntervalSec} done ''; @@ -223,15 +242,10 @@ in ExecStart (binary grok doesn't exist at ~/.bun/bin/grok). ''; } - { - assertion = !cfg.enable.vera; - message = '' - zeta.aiAgents.enable.vera = true requires B-0850.3c - (Vera/Codex install + login substrate) which has not shipped. - Enabling now would create a zeta-vera.service that fails - ExecStart (binary codex doesn't exist at ~/.bun/bin/codex). - ''; - } + # B-0850.3c (Vera/Codex) shipped this PR — assertion removed. + # zeta-install.sh Step 6.95a-codex installs @openai/codex via + # bun + Step 6.95b-codex runs `codex login --device-auth`. + # Binary lands at ~/.bun/bin/codex; creds at ~/.codex/auth.json. # B-0850.3d (Lior/Gemini) shipped this PR — assertion removed. # zeta-install.sh Step 6.95a-gemini installs @google/gemini-cli # via bun + Step 6.95b-gemini runs interactive gemini auth login. diff --git a/full-ai-cluster/usb-nixos-installer/zeta-install.sh b/full-ai-cluster/usb-nixos-installer/zeta-install.sh index a1e3117dea..091d14a46a 100755 --- a/full-ai-cluster/usb-nixos-installer/zeta-install.sh +++ b/full-ai-cluster/usb-nixos-installer/zeta-install.sh @@ -1104,8 +1104,12 @@ if [ -d "$ZETA_HOME" ]; then sudo mkdir -p "$ZETA_HOME/.bun/bin" sudo chown -R "$ZETA_UID:$ZETA_GID" "$ZETA_HOME/.bun" # Source mise activation so the subshell finds bun via mise shims. + # tail -5 INSIDE the bash -c so pipefail covers the WHOLE pipeline + # (per Copilot review on PR #5398: outer pipe to tail -5 was masking + # bun install exit status; tail outside bash -c isn't covered by + # the inner shell's pipefail setting). sudo HOME="$ZETA_HOME" BUN_INSTALL="$ZETA_HOME/.bun" -u "#$ZETA_UID" \ - bash -c 'set -o pipefail; eval "$(mise activate bash 2>/dev/null || true)"; bun install --global @anthropic-ai/claude-code' 2>&1 | tail -5 || \ + bash -c 'set -o pipefail; eval "$(mise activate bash 2>/dev/null || true)"; bun install --global @anthropic-ai/claude-code 2>&1 | tail -5' || \ echo "[iter-5.5.0] WARN: bun install claude-code FAILED — can retry post-reboot via 'bun install --global @anthropic-ai/claude-code'" # 6.95a-gemini — install @google/gemini-cli via bun (B-0850 Phase 3d). @@ -1115,9 +1119,19 @@ if [ -d "$ZETA_HOME" ]; then # at implementation time (npm @google/gemini-cli is bun-compat). echo "[iter-5.5.0] installing @google/gemini-cli via mise-managed bun (B-0850 Phase 3d Lior 2nd vendor)..." sudo HOME="$ZETA_HOME" BUN_INSTALL="$ZETA_HOME/.bun" -u "#$ZETA_UID" \ - bash -c 'set -o pipefail; eval "$(mise activate bash 2>/dev/null || true)"; bun install --global @google/gemini-cli' 2>&1 | tail -5 || \ + bash -c 'set -o pipefail; eval "$(mise activate bash 2>/dev/null || true)"; bun install --global @google/gemini-cli 2>&1 | tail -5' || \ echo "[iter-5.5.0] WARN: bun install gemini-cli FAILED — can retry post-reboot via 'bun install --global @google/gemini-cli'" + # 6.95a-codex — install @openai/codex via bun (B-0850 Phase 3c). + # 3rd vendor — hits the ≥3 BFT floor (Anthropic + Google + OpenAI). + # WebSearch verified per dep-pin-search-first-authority at + # implementation time: npm @openai/codex is bun-compat; binary + # lands at ~/.bun/bin/codex. + echo "[iter-5.5.0] installing @openai/codex via mise-managed bun (B-0850 Phase 3c Vera 3rd vendor — hits ≥3 BFT floor)..." + sudo HOME="$ZETA_HOME" BUN_INSTALL="$ZETA_HOME/.bun" -u "#$ZETA_UID" \ + bash -c 'set -o pipefail; eval "$(mise activate bash 2>/dev/null || true)"; bun install --global @openai/codex 2>&1 | tail -5' || \ + echo "[iter-5.5.0] WARN: bun install codex FAILED — can retry post-reboot via 'bun install --global @openai/codex'" + # 6.95b — interactive claude login (mirror iter-5.4.0 gh auth login) CLAUDE_BIN="$ZETA_HOME/.bun/bin/claude" if [ -x "$CLAUDE_BIN" ]; then @@ -1181,6 +1195,43 @@ if [ -d "$ZETA_HOME" ]; then echo "[iter-5.5.0] gemini binary not found at $GEMINI_BIN; skipping interactive login" fi + # 6.95b-codex — interactive codex login (B-0850 Phase 3c Vera). + # 3rd vendor login — codex CLI has the most explicit device-flow + # via `codex login --device-auth` (Anthropic claude device-flow + # analog; works on headless / no-local-browser systems by + # printing URL+code for paste into ANY browser). Credentials + # cache at ~/.codex/auth.json (NOT ~/.config/codex/ — codex + # uses its own dotdir convention per the codex docs). + CODEX_BIN="$ZETA_HOME/.bun/bin/codex" + if [ -x "$CODEX_BIN" ]; then + echo + echo "[iter-5.5.0] Trigger Codex CLI interactive device-flow login NOW (B-0850 Phase 3c Vera)?" + echo "[iter-5.5.0] - Uses 'codex login --device-auth' (clean device-flow shape)." + echo "[iter-5.5.0] - Prints URL + one-time code; visit on ANY browser on ANY device; paste code." + echo "[iter-5.5.0] - ChatGPT Plus/Pro/Business/Edu/Enterprise plans include Codex access." + echo "[iter-5.5.0] - Credentials land at $ZETA_HOME/.codex/auth.json (NOT ~/.config/codex)." + echo "[iter-5.5.0] - Default YES (press Enter); 'n' to skip + login post-reboot manually." + read -r -p "[iter-5.5.0] Run codex login --device-auth now? [Y/n]: " CODEX_AUTH_REPLY + case "${CODEX_AUTH_REPLY:-y}" in + [Yy]*|"") + echo "[iter-5.5.0] running 'codex login --device-auth' (interactive)..." + sudo HOME="$ZETA_HOME" -u "#$ZETA_UID" "$CODEX_BIN" login --device-auth || \ + echo "[iter-5.5.0] WARN: codex login failed; can re-run post-reboot" + # Codex stores at ~/.codex/auth.json (not ~/.config/codex); + # restrict perms accordingly. + if [ -d "$ZETA_HOME/.codex" ]; then + sudo chown -R "$ZETA_UID:$ZETA_GID" "$ZETA_HOME/.codex" + sudo chmod -R go-rwx "$ZETA_HOME/.codex" + fi + ;; + *) + echo "[iter-5.5.0] SKIPPED codex login; run 'codex login --device-auth' on first login" + ;; + esac + else + echo "[iter-5.5.0] codex binary not found at $CODEX_BIN; skipping interactive login" + fi + # 6.95c — persist gh credentials from installer-root to installed-zeta # Closes the iter-5.4.0 credential-persistence gap (Bug 8). if [ -d /root/.config/gh ]; then