diff --git a/.gitignore b/.gitignore index d2a5cf8d..30c4bd74 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,6 @@ docs/ /.ripgreprc /.zprofile /.zshrc + +# Local runtime state from memory/sidecar processes (not source) +/data/ diff --git a/README.md b/README.md index fec233bd..be457922 100644 --- a/README.md +++ b/README.md @@ -115,10 +115,19 @@ When you're happy with the result, merge the branch back to main from the sideba - **macOS** — `.dmg` (universal) - **Linux** — `.AppImage` or `.deb` -2. **Install at least one AI coding CLI:** [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [Codex CLI](https://github.com/openai/codex), [Gemini CLI](https://github.com/google-gemini/gemini-cli), or [Copilot CLI](https://docs.github.com/en/copilot/concepts/agents/about-copilot-cli) +2. **Install at least one AI coding CLI:** [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [Codex CLI](https://github.com/openai/codex), [Gemini CLI](https://github.com/google-gemini/gemini-cli), [Antigravity CLI](https://antigravity.google/), or [Copilot CLI](https://docs.github.com/en/copilot/concepts/agents/about-copilot-cli) 3. **Open Parallel Code**, point it at a git repo, and start dispatching tasks. +
+Antigravity CLI: run natively, not in Docker isolation + +Antigravity (`agy`) signs in interactively and caches credentials in your OS keyring (Keychain/libsecret). The keyring cannot be reached from inside a Linux container — it needs a secret-service daemon the agent container doesn't run — and `agy` has no API-key fallback, so **Docker-isolated Antigravity tasks cannot authenticate. Run Antigravity as a native (non-Docker) task.** + +The bundled image still ships the `agy` binary, and `~/.gemini/antigravity-cli` (settings/plugins) is shared when "Share agent auth" is enabled, so Docker support is forward-compatible if a file-based or API-key auth path appears later. + +
+
Build from source diff --git a/docker/Dockerfile b/docker/Dockerfile index a717cbb8..f775de19 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -52,6 +52,14 @@ RUN ln -sf "$(command -v fdfind)" /usr/local/bin/fd 2>/dev/null || true # AI agent CLIs — must be present so Docker-mode tasks can execute them RUN npm install -g @anthropic-ai/claude-code @openai/codex @google/gemini-cli opencode-ai +# Antigravity CLI (agy) — distributed as a Go binary via the official installer +# (not on npm). The installer's `--dir` flag drops the binary straight into a +# system path on PATH, so the non-root `agent` user that runs containers resolves +# it. A failed fetch makes the build step fail, surfacing the break at image build +# time rather than at task launch. +RUN curl -fsSL https://antigravity.google/cli/install.sh | bash -s -- --dir /usr/local/bin \ + && agy --version + # Default git config so commits work out of the box inside containers RUN git config --system init.defaultBranch main \ && git config --system advice.detachedHead false diff --git a/electron/ipc/agents.ts b/electron/ipc/agents.ts index 8f17147d..44041a7b 100644 --- a/electron/ipc/agents.ts +++ b/electron/ipc/agents.ts @@ -66,6 +66,17 @@ const DEFAULT_AGENTS: AgentDef[] = [ // settle before sending, without being so long that the user notices the wait. prompt_ready_delay_ms: 1_000, }, + { + id: 'antigravity', + name: 'Antigravity CLI', + command: 'agy', + args: [], + resume_args: ['-c'], + skip_permissions_args: ['--dangerously-skip-permissions'], + description: "Google's Antigravity CLI agent (successor to Gemini CLI)", + // Antigravity paints a TUI that needs a beat to settle before auto-send. + prompt_ready_delay_ms: 1_000, + }, ]; async function isCommandAvailable(command: string): Promise { diff --git a/electron/ipc/pty.test.ts b/electron/ipc/pty.test.ts index 696fc32c..5655aa4f 100644 --- a/electron/ipc/pty.test.ts +++ b/electron/ipc/pty.test.ts @@ -352,6 +352,7 @@ describe('spawnAgent docker mode', () => { ['gemini', '.gemini'], ['opencode', '.config/opencode'], ['copilot', '.config/github-copilot'], + ['agy', '.gemini/antigravity-cli'], ])( '%s bind-mounts a user-owned host directory when shareDockerAgentAuth is enabled', (command, relDir) => { diff --git a/electron/ipc/pty.ts b/electron/ipc/pty.ts index 76423e12..5cf49440 100644 --- a/electron/ipc/pty.ts +++ b/electron/ipc/pty.ts @@ -710,6 +710,7 @@ const AGENT_CONFIG_DIRS: Record = { gemini: ['.gemini'], opencode: ['.config/opencode'], copilot: ['.config/github-copilot'], + agy: ['.gemini/antigravity-cli'], }; // Config files (not directories) each agent CLI uses for auth, relative to HOME. diff --git a/electron/mcp/agent-args.test.ts b/electron/mcp/agent-args.test.ts index c8e3a65d..d551a077 100644 --- a/electron/mcp/agent-args.test.ts +++ b/electron/mcp/agent-args.test.ts @@ -1,6 +1,11 @@ import { describe, expect, it } from 'vitest'; -import { buildCodexMcpConfigOverride, buildMcpLaunchArgs, isCodexCommand } from './agent-args.js'; +import { + buildCodexMcpConfigOverride, + buildMcpLaunchArgs, + isAntigravityCommand, + isCodexCommand, +} from './agent-args.js'; const config = { mcpServers: { @@ -50,4 +55,14 @@ describe('MCP agent launch args', () => { '/tmp/config.json', ]); }); + + it('detects antigravity commands by executable name', () => { + expect(isAntigravityCommand('agy')).toBe(true); + expect(isAntigravityCommand('/home/agent/.local/bin/agy')).toBe(true); + expect(isAntigravityCommand('claude')).toBe(false); + }); + + it('emits no --mcp-config for Antigravity', () => { + expect(buildMcpLaunchArgs('agy', '/tmp/config.json', config)).toEqual([]); + }); }); diff --git a/electron/mcp/agent-args.ts b/electron/mcp/agent-args.ts index 2f6153fa..195551f4 100644 --- a/electron/mcp/agent-args.ts +++ b/electron/mcp/agent-args.ts @@ -14,6 +14,10 @@ export function isCodexCommand(command: string): boolean { return command.split('/').pop()?.includes('codex') === true; } +export function isAntigravityCommand(command: string): boolean { + return command.split('/').pop() === 'agy'; +} + function tomlString(value: string): string { return JSON.stringify(value); } @@ -41,5 +45,9 @@ export function buildMcpLaunchArgs( if (isCodexCommand(command)) { return ['--config', buildCodexMcpConfigOverride(config)]; } + // Antigravity (agy) has no `--mcp-config` flag; emit nothing so launch is not broken. + if (isAntigravityCommand(command)) { + return []; + } return configPath ? ['--mcp-config', configPath] : []; } diff --git a/openspec/changes/add-antigravity-agent/.openspec.yaml b/openspec/changes/add-antigravity-agent/.openspec.yaml new file mode 100644 index 00000000..1194417e --- /dev/null +++ b/openspec/changes/add-antigravity-agent/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-05-29 diff --git a/openspec/changes/add-antigravity-agent/design.md b/openspec/changes/add-antigravity-agent/design.md new file mode 100644 index 00000000..ebbb07db --- /dev/null +++ b/openspec/changes/add-antigravity-agent/design.md @@ -0,0 +1,169 @@ +## Context + +parallel-code launches coding-agent CLIs in node-pty terminals, one per git +worktree. The agent catalog is a static `DEFAULT_AGENTS` array in +`electron/ipc/agents.ts`; each entry is an `AgentDef` (`{ id, name, command, +args, resume_args, skip_permissions_args, description, prompt_ready_delay_ms?, +mcp_config_flag? }`). `AgentDef` is **duplicated** — the canonical copy lives in +`src/ipc/types.ts` and a structurally identical copy in `electron/ipc/agents.ts`. + +At launch the agent is started **interactively** with `args` (the initial prompt +is typed into the TUI afterward via bracketed paste, not passed as a CLI argument). +`buildTaskAgentArgs` appends `resume_args` on resume and `skip_permissions_args` +when the task opts into skip-permissions. Availability is probed with `which +` (TTL-cached). The renderer's `AgentSelector` renders each agent as a +text button keyed off `agent.id` with **no per-agent icon or color map**, so a new +agent needs no front-end asset. + +The Electron main process resolves the user's full login+interactive shell PATH +(`zsh/bash -ilc`, `electron/main.ts:52`), so binaries on `~/.local/bin` are +discoverable as long as the user's shell profile adds that directory. + +For Docker-isolated tasks, `AGENT_CONFIG_DIRS` (`electron/ipc/pty.ts:707`) maps a +command basename to host config dirs that are bind-mounted into the container when +"Share agent auth" is enabled. The bundled `docker/Dockerfile` installs the other +agents via `npm install -g`. + +Ground truth for `agy` (verified against the installed binary, v1.0.3 — `agy +--help`): + +- Interactive launch: `agy` +- Resume most recent conversation: `-c` / `--continue` +- Skip permissions: `--dangerously-skip-permissions` +- Sandbox (`nsjail`/`sandbox-exec`) is **opt-in** via `--sandbox`; off by default. +- Non-interactive print mode: `-p` / `--print` (not used by this app). +- Config/app-data dir is `~/.gemini/antigravity-cli` (settings.json, mcp_config.json, + plugins/, conversations/) — verified against the installed binary; it is **not** + `~/.config/antigravity`. Interactive auth is OAuth cached in the OS keyring + (Keychain/libsecret); `agy` has **no** API-key environment fallback (the + `ANTIGRAVITY_API_KEY` variable is not consumed — confirmed behaviorally). + +## Goals / Non-Goals + +**Goals:** + +- Add `agy` as a built-in default agent with correct launch/resume/skip-permission + args, fully working on native (macOS/Linux) tasks. +- Ship the Docker bits that are additive and forward-compatible (bundle the `agy` + binary, mount its config dir), while documenting clearly — in user-facing docs and + the PR — that Docker-isolated Antigravity cannot authenticate and must be run + natively. +- Keep Gemini CLI fully intact. +- Make the integration data-driven — no new IPC, no per-id special casing. + +**Non-Goals:** + +- Removing or deprecating Gemini CLI (separate future change). +- Wiring `agy`'s OAuth keyring credentials into Docker containers (not file-mountable). +- Supporting `agy --sandbox` inside Docker (nested sandbox; left off by default). +- A custom agent-branding/icon system. + +## Decisions + +**1. Launch interactively with `command: "agy"`, `args: []`.** +Matches every other agent: the app types the prompt into the live TUI rather than +passing `-p`. `-p`/`--print` is one-shot and prints-then-exits, which would defeat +the interactive session model. Rejected `-i`/`--prompt-interactive` because the app +already delivers the prompt via terminal input after a readiness delay. + +**2. `resume_args: ["-c"]`, `skip_permissions_args: ["--dangerously-skip-permissions"]`.** +Both verified against the installed binary. `--dangerously-skip-permissions` is +real (it coincidentally matches Claude's flag); the Gemini `--yolo` flag does **not** +carry over. + +**3. `prompt_ready_delay_ms` set to a modest stability delay (~1000 ms), mirroring +Copilot.** `agy`'s first interactive run shows onboarding (Google sign-in, theme, +file-permission prompts); steady-state runs still paint a TUI that needs a beat to +settle before auto-send. This only tunes the readiness recheck; it does not bypass +first-run OAuth, which is interactive regardless (same caveat as other agents). + +**4. Omit `mcp_config_flag` AND exclude `agy` from the `--mcp-config` paths.** `agy` +has no `--mcp-config` flag (MCP is configured via plugins/config), so passing it would +break launch. Two code paths can emit it and both must exclude `agy`: + +- `legacyMcpConfigArgs` (`src/lib/agent-args.ts`) injects `--mcp-config ` for any + non-Codex command when a task carries a legacy `mcpConfigPath` and no `mcpLaunchArgs` + — reachable only by older persisted tasks. +- `buildMcpLaunchArgs` (`electron/mcp/agent-args.ts`, imported by `coordinator.ts`) is + the modern per-agent builder used by coordinator/MCP tasks. + Excluding `agy` (the same way Codex is excluded via `isCodexCommand`) is the safe + default: it guarantees `agy` is never handed an unsupported flag regardless of whether + a given task takes the legacy or modern path. Leaving `mcp_config_flag` unset keeps it + off the flag-driven path as well. + +**5. Mount the config dir; Docker auth is unsupported (native only).** Add +`agy: ['.gemini/antigravity-cli']` to `AGENT_CONFIG_DIRS` so settings/plugins are +shared. Auth, however, cannot work in Docker: login credentials live in the host OS +keyring (Keychain/libsecret), which needs a secret-service daemon the agent container +does not run, and `agy` has no API-key environment fallback (the `ANTIGRAVITY_API_KEY` +claim was unverified and is false — behaviorally `agy` still enters OAuth with it set). +So Antigravity is documented as a native-only agent. The mount + bundled binary are +kept because they are additive and forward-compatible if a file-based or API-key auth +path appears. + +**6. Install `agy` in the Docker image via the official installer, not npm.** `agy` +ships as a Go binary from `https://antigravity.google/cli/install.sh`, so the existing +`npm install -g` line cannot add it. The installer supports `-d|--dir`, so the step +installs straight to a system PATH dir: +`RUN curl -fsSL … | bash -s -- --dir /usr/local/bin && agy --version`. Verified the +script runs non-interactively when piped and `mkdir -p`s its target. The trailing +`agy --version` makes the build assert the install at image-build time. + +## Risks / Trade-offs + +- **Docker auth is impossible, not just limited** → `agy` login is keyring-only OAuth, + the container has no secret-service daemon, and there is no API-key fallback, so an + in-container `agy` cannot authenticate at all. Mitigation: native tasks work fully; + docs and PR state plainly that Antigravity is native-only and Docker isolation is + unsupported for it. The config-dir mount + bundled binary are kept as additive, + forward-compatible scaffolding, not a working auth path. +- **PATH discovery** → If the user never ran the installer's PATH line, `~/.local/bin` + is absent from the resolved shell PATH and `agy` shows "(not installed)". Mitigation: + this is the same behavior as any un-PATHed agent; the availability probe degrades + gracefully. Document running `agy install`. +- **`--mcp-config` injected by either MCP path** → `legacyMcpConfigArgs` (old tasks) + or `buildMcpLaunchArgs` (coordinator tasks) could hand `agy` an unknown flag and + break launch. Mitigation: exclude `agy` from both, the way Codex is excluded via + `isCodexCommand` — chosen as the safe default rather than relying on the legacy path + being unreachable. **Functional consequence (accepted):** `buildMcpLaunchArgs` + returning `[]` for `agy` means Antigravity tasks receive no parallel-code MCP + coordinator wiring — no in-`agy` subagent spawning. `agy` configures MCP via + plugins/config rather than a CLI flag, so coordinator integration is a separate + follow-up if desired; this change intentionally ships without it. +- **Installer-script drift in Docker builds** → Piping a remote installer into the + image is less reproducible and could break builds if the URL/format changes. + Mitigation: pin behavior to the documented installer; treat a failed fetch as a + build error so it is caught at image-build time, not at task launch. +- **Duplicate `AgentDef` definitions drift** → The entry and the type live in two + files. Mitigation: update both `electron/ipc/agents.ts` and `src/ipc/types.ts` in + lockstep; TypeScript strict build catches shape mismatches. + +## Migration Plan + +Additive and behind agent availability detection — no data migration. `agy` appears +in the selector only when the binary is on PATH; existing Gemini tasks are untouched. +Rollback is removing the `DEFAULT_AGENTS` entry (and the Dockerfile/ config-dir +lines); no persisted state depends on it. + +## Resolved Decisions + +- **Antigravity is native-only; Docker isolation is unsupported for it.** The earlier + `ANTIGRAVITY_API_KEY` Docker auth path was an unverified claim and is false. We keep + the additive Docker scaffolding (bundled `agy` binary, mounted + `~/.gemini/antigravity-cli` config dir) but document Antigravity as native-only. + Decided with the user after the Codex review surfaced the wrong config path and the + non-functional env var. +- **`agy` is excluded from both `--mcp-config` paths** (`legacyMcpConfigArgs` and + `buildMcpLaunchArgs`), as the safe default — no dependence on the legacy path being + unreachable. + +## Open Questions + +- In-container PATH is resolved: the installer's `--dir` flag installs `agy` to + `/usr/local/bin` (on PATH for the non-root `agent` user); the build-step + `agy --version` verifies it. A full `docker build` is the only step needing a Docker + daemon. +- **Untested:** whether `agy` can read a file-based credential it shares with the + Gemini CLI (`~/.gemini/gemini-credentials.json` / `google_accounts.json` sit in the + parent dir). If it does, that could become a real Docker auth path later. Not claimed + or relied on here. diff --git a/openspec/changes/add-antigravity-agent/proposal.md b/openspec/changes/add-antigravity-agent/proposal.md new file mode 100644 index 00000000..f993ae28 --- /dev/null +++ b/openspec/changes/add-antigravity-agent/proposal.md @@ -0,0 +1,50 @@ +## Why + +Google is transitioning the Gemini CLI to the new **Antigravity CLI** (`agy`). On +**June 18, 2026** Gemini CLI stops serving Google AI Pro/Ultra and free Code Assist +users; it remains only on paid/enterprise API keys. Users who pick the "Gemini CLI" +agent today will increasingly hit a dead end, while `agy` becomes the supported +Google terminal agent. parallel-code should offer Antigravity CLI as a first-class +agent so users have a working migration path — without yet removing Gemini CLI, +which paid/enterprise users still rely on. + +## What Changes + +- Add **Antigravity CLI** (`agy`) as a built-in default agent alongside Claude Code, + Codex, Gemini CLI, OpenCode, and Copilot. +- Register its launch contract: interactive launch (`agy`), resume (`-c`), and + skip-permissions (`--dangerously-skip-permissions`). +- Add its config directory (`~/.gemini/antigravity-cli`) to the Docker shared-auth + mount map for settings/plugins, and document that Antigravity is **native-only**: + its login credentials live in the OS keyring (no secret-service daemon in the + container) and `agy` has no API-key env fallback, so Docker-isolated Antigravity + cannot authenticate. +- Install `agy` in the bundled Docker agent image (via the official installer script, + not npm) so the binary is present and the integration is forward-compatible. +- Keep Gemini CLI unchanged. **No removal, no breaking change.** + +## Capabilities + +### New Capabilities + +- `agent-cli-registry`: The set of coding-agent CLIs parallel-code can launch, and the + per-agent contract (command, launch/resume/skip-permission arguments, availability + detection, config-directory auth mounting for Docker isolation) that governs how each + agent is started, resumed, and authenticated. + +### Modified Capabilities + + + +## Impact + +- **Code:** `electron/ipc/agents.ts` (`DEFAULT_AGENTS`), `src/ipc/types.ts` + + `electron/ipc/agents.ts` (`AgentDef` — duplicated, both updated), + `electron/ipc/pty.ts` (`AGENT_CONFIG_DIRS`), `docker/Dockerfile` (install `agy`). +- **No new IPC channels or payload-type changes** — Antigravity reuses the existing + data-driven `AgentDef` plumbing; the selector renders it from the agent list with no + per-id branding. +- **Dependencies:** the Docker image gains the `agy` binary fetched from + `https://antigravity.google/cli/install.sh`. +- **Tests:** agent-registry and persistence tests that enumerate agents may need the + new entry accounted for. diff --git a/openspec/changes/add-antigravity-agent/specs/agent-cli-registry/spec.md b/openspec/changes/add-antigravity-agent/specs/agent-cli-registry/spec.md new file mode 100644 index 00000000..d3c95d7c --- /dev/null +++ b/openspec/changes/add-antigravity-agent/specs/agent-cli-registry/spec.md @@ -0,0 +1,78 @@ +## ADDED Requirements + +### Requirement: Antigravity CLI is an available built-in agent + +The application SHALL offer Antigravity CLI as a built-in default agent, identified +by the command `agy`, alongside the existing Claude Code, Codex, Gemini CLI, +OpenCode, and Copilot agents. Adding Antigravity SHALL NOT remove or alter the +Gemini CLI agent. + +#### Scenario: Antigravity appears when its binary is installed + +- **WHEN** the application lists available agents and `agy` is resolvable on the + user's PATH +- **THEN** an agent named "Antigravity CLI" is presented as available for selection + +#### Scenario: Antigravity is shown unavailable when its binary is missing + +- **WHEN** the application lists available agents and `agy` is not resolvable on the + user's PATH +- **THEN** the Antigravity CLI agent is marked not installed and cannot be launched + +#### Scenario: Gemini CLI remains offered + +- **WHEN** the application lists available agents +- **THEN** the Gemini CLI agent is still present and unchanged + +### Requirement: Antigravity launch, resume, and skip-permission arguments + +The application SHALL launch the Antigravity CLI interactively with no extra +arguments, SHALL resume the most recent conversation using `-c` when a task is +resumed, and SHALL pass `--dangerously-skip-permissions` only when the task opts +into skipping permission prompts. + +#### Scenario: Fresh interactive launch + +- **WHEN** a new task starts with the Antigravity agent and skip-permissions is off +- **THEN** the CLI is started as `agy` with no resume or skip-permission arguments + +#### Scenario: Resuming a previous session + +- **WHEN** a task using the Antigravity agent is resumed +- **THEN** the CLI is started with its resume argument `-c` + +#### Scenario: Launch with permissions skipped + +- **WHEN** a task using the Antigravity agent is started with skip-permissions enabled +- **THEN** `--dangerously-skip-permissions` is included in the launch arguments + +### Requirement: Antigravity config sharing for isolated tasks + +For Docker-isolated tasks, the application SHALL treat `~/.gemini/antigravity-cli` as +the Antigravity config directory eligible for the shared-auth mount. The application +SHALL NOT claim a working Docker authentication path for Antigravity: its login +credentials live in the host OS keyring, which is not reachable from the container, +and `agy` provides no API-key environment fallback, so Antigravity is supported only +as a native (non-Docker) task. + +#### Scenario: Config directory is shared into the container + +- **WHEN** a Docker-isolated Antigravity task runs with shared agent auth enabled +- **THEN** the host `~/.gemini/antigravity-cli` directory is bind-mounted into the + container's home so settings and plugins are available to the in-container agent + +#### Scenario: Docker isolation does not provide authentication + +- **WHEN** a Docker-isolated Antigravity task is launched without host keyring access +- **THEN** the agent cannot authenticate inside the container, and the supported path + is to run the Antigravity task natively instead + +### Requirement: Antigravity is runnable in the bundled Docker image + +The bundled Docker agent image SHALL include the `agy` binary on its PATH so that +Docker-isolated tasks can launch the Antigravity agent. + +#### Scenario: agy is present in the image + +- **WHEN** a Docker-isolated task selects the Antigravity agent using the bundled image +- **THEN** `agy` resolves on the container PATH and the agent process starts diff --git a/openspec/changes/add-antigravity-agent/tasks.md b/openspec/changes/add-antigravity-agent/tasks.md new file mode 100644 index 00000000..ae4ff2af --- /dev/null +++ b/openspec/changes/add-antigravity-agent/tasks.md @@ -0,0 +1,31 @@ +## 1. Register the agent + +- [x] 1.1 Add an `antigravity` entry to `DEFAULT_AGENTS` in `electron/ipc/agents.ts`: `id: 'antigravity'`, `name: 'Antigravity CLI'`, `command: 'agy'`, `args: []`, `resume_args: ['-c']`, `skip_permissions_args: ['--dangerously-skip-permissions']`, a description, and `prompt_ready_delay_ms: 1000`. +- [x] 1.2 Confirm `AgentDef` in `src/ipc/types.ts` already covers every field used (it does today); update it only if a new field is introduced, keeping it in lockstep with the `electron/ipc/agents.ts` copy. +- [x] 1.3 Do NOT set `mcp_config_flag` for the entry (agy has no `--mcp-config` flag). + +## 2. Docker isolation support + +- [x] 2.1 Add `agy: ['.gemini/antigravity-cli']` to `AGENT_CONFIG_DIRS` in `electron/ipc/pty.ts`. (Corrected from `.config/antigravity` — verified against the installed binary: `agy` v1.0.3 writes its app data to `~/.gemini/antigravity-cli`, and `~/.config/antigravity` does not exist.) +- [x] 2.2 ~~Forward `ANTIGRAVITY_API_KEY` into the container.~~ **Dropped.** `agy` does not consume any API-key env var (the `ANTIGRAVITY_API_KEY` claim was unverified and is false — Codex confirmed `agy` still enters OAuth with it set). Docker-isolated `agy` cannot authenticate (keyring-only OAuth, no secret-service daemon in the container); Antigravity is native-only. +- [x] 2.3 Add a Docker image step in `docker/Dockerfile` to install `agy` via `curl -fsSL https://antigravity.google/cli/install.sh | bash -s -- --dir /usr/local/bin` (separate from the `npm install -g` line). Ground-truthed the installer script: it supports `-d|--dir`, runs non-interactively when piped (no `read` prompts), and `mkdir -p`s the target; installing straight to `/usr/local/bin` puts `agy` on PATH for the non-root `agent` user. +- [x] 2.4 Confirm `agy --version` resolves from the install. The Dockerfile RUN ends with `&& agy --version`, so the image build itself asserts the binary is installed and on PATH — a failed install fails the build. Full `docker build` run still pending Docker (this env has no daemon), but the verification is now baked into the build step. + +## 3. MCP-config guard (exclude agy from both paths) + +- [x] 3.1 Exclude `agy` from `legacyMcpConfigArgs` in `src/lib/agent-args.ts` so a task with only a legacy `mcpConfigPath` is not handed `--mcp-config` (same treatment as `isCodexCommand` for Codex). +- [x] 3.2 Exclude `agy` from `buildMcpLaunchArgs` in `electron/mcp/agent-args.ts` (imported by `coordinator.ts`) so coordinator/MCP tasks never emit `--mcp-config` for `agy`. +- [x] 3.3 Add/adjust a unit test asserting `buildTaskAgentArgs`/`buildMcpLaunchArgs` emit no `--mcp-config` for the `agy` command. + +## 4. Verification + +- [x] 4.1 `npm run typecheck` clean across renderer and electron targets — 0 errors (after restoring deps; see note below). +- [x] 4.2 Run the agent-registry and persistence tests: `persistence.test.ts`, `agents.test.ts`, `src/lib/agent-args.test.ts`, `electron/mcp/agent-args.test.ts`, `pty.test.ts` — 111 tests pass. `pty.test.ts` config-dir-mount table extended with `agy`. +- [ ] 4.3 Manual native smoke test: with `agy` installed, the selector shows "Antigravity CLI" available; start a task, confirm the prompt is delivered and the session runs; resume a task and confirm `-c` is used. **Pending** — needs the running app. +- [ ] 4.4 Manual skip-permissions smoke test: start an Antigravity task with skip-permissions enabled and confirm `--dangerously-skip-permissions` is passed. **Pending** — needs the running app. +- [x] 4.5 `openspec validate --all --strict` passes. + +## 5. Docs & PR + +- [x] 5.1 Document in user-facing docs that Antigravity is native-only: login is keyring-only OAuth, the container has no secret-service daemon, and `agy` has no API-key fallback, so Docker-isolated Antigravity cannot authenticate. Added to `README.md` (Antigravity in the CLI install list + a "run natively, not in Docker isolation" details block). +- [x] 5.2 Call out the same native-only constraint in the PR description (works natively; Docker isolation unsupported for Antigravity), so reviewers and users see it up front. Drafted in `/tmp/pr-body.md` (use with `gh pr create --body-file`). diff --git a/src/lib/agent-args.test.ts b/src/lib/agent-args.test.ts index c4cc65fa..319bb18e 100644 --- a/src/lib/agent-args.test.ts +++ b/src/lib/agent-args.test.ts @@ -22,6 +22,16 @@ const claudeAgent = { skip_permissions_args: ['--dangerously-skip-permissions'], }; +const antigravityAgent = { + id: 'antigravity', + name: 'Antigravity CLI', + description: 'Antigravity agent', + command: 'agy', + args: [], + resume_args: ['-c'], + skip_permissions_args: ['--dangerously-skip-permissions'], +}; + describe('buildTaskAgentArgs', () => { it('uses explicit MCP launch args when provided', () => { expect( @@ -68,4 +78,30 @@ describe('buildTaskAgentArgs', () => { ), ).toEqual(['--mcp-config', '/tmp/mcp.json']); }); + + it('does not fall back to --mcp-config for Antigravity', () => { + expect( + buildTaskAgentArgs( + antigravityAgent, + { + skipPermissions: false, + mcpConfigPath: '/tmp/mcp.json', + }, + false, + ), + ).toEqual([]); + }); + + it('passes the resume flag for Antigravity without --mcp-config', () => { + expect( + buildTaskAgentArgs( + antigravityAgent, + { + skipPermissions: false, + mcpConfigPath: '/tmp/mcp.json', + }, + true, + ), + ).toEqual(['-c']); + }); }); diff --git a/src/lib/agent-args.ts b/src/lib/agent-args.ts index 3474a9da..e1dd94e7 100644 --- a/src/lib/agent-args.ts +++ b/src/lib/agent-args.ts @@ -5,8 +5,13 @@ function isCodexCommand(command: string): boolean { return command.split('/').pop()?.includes('codex') === true; } +function isAntigravityCommand(command: string): boolean { + return command.split('/').pop() === 'agy'; +} + function legacyMcpConfigArgs(command: string, mcpConfigPath: string | undefined): string[] { - if (!mcpConfigPath || isCodexCommand(command)) return []; + // Codex and Antigravity have no `--mcp-config` flag; passing it would break launch. + if (!mcpConfigPath || isCodexCommand(command) || isAntigravityCommand(command)) return []; return ['--mcp-config', mcpConfigPath]; }