diff --git a/README.md b/README.md index acb0ddec6..1b1be4cbb 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ multica setup # Connect to Multica Cloud, log in, start daemon multica setup # Configure, authenticate, and start the daemon ``` -The daemon runs in the background and auto-detects agent CLIs (`claude`, `codex`, `copilot`, `openclaw`, `opencode`, `hermes`, `gemini`, `pi`, `cursor-agent`, `kimi`, `kiro-cli`) on your PATH. +The daemon runs in the background and auto-detects agent CLIs (`claude`, `codex`, `copilot`, `openclaw`, `opencode`, `hermes`, `gemini`, `pi`, `cursor-agent`, `kimi`, `kiro-cli`, `agy`) on your PATH. ### 2. Verify your runtime @@ -124,7 +124,7 @@ Open your workspace in the Multica web app. Navigate to **Settings → Runtimes* ### 3. Create an agent -Go to **Settings → Agents** and click **New Agent**. Pick the runtime you just connected and choose a provider (Claude Code, Codex, GitHub Copilot CLI, OpenClaw, OpenCode, Hermes, Gemini, Pi, Cursor Agent, Kimi, or Kiro CLI). Give your agent a name — this is how it will appear on the board, in comments, and in assignments. +Go to **Settings → Agents** and click **New Agent**. Pick the runtime you just connected and choose a provider (Claude Code, Codex, GitHub Copilot CLI, OpenClaw, OpenCode, Hermes, Gemini, Pi, Cursor Agent, Kimi, Kiro CLI, or Antigravity). Give your agent a name — this is how it will appear on the board, in comments, and in assignments. ### 4. Assign your first task diff --git a/apps/docs/content/docs/agents-create.mdx b/apps/docs/content/docs/agents-create.mdx index 2c2ae3563..764614658 100644 --- a/apps/docs/content/docs/agents-create.mdx +++ b/apps/docs/content/docs/agents-create.mdx @@ -21,7 +21,7 @@ The form has only two required fields: **name** (unique within the workspace) an ## Pick an AI coding tool -Each runtime is backed by a specific AI coding tool. Multica supports 11 of them. The most common choices: +Each runtime is backed by a specific AI coding tool. Multica supports 12 of them. The most common choices: | Tool | Good for | |---|---| @@ -31,7 +31,7 @@ Each runtime is backed by a specific AI coding tool. Multica supports 11 of them | **Copilot** | Teams leveraging their GitHub account entitlements | | **Gemini** | Users in the Google ecosystem | -The other six (Hermes, Kimi, Kiro CLI, OpenCode, Pi, OpenClaw), along with each tool's full capability matrix (session resume, MCP, skill injection path, model selection), are covered in [AI coding tools comparison](/providers). +The other seven (Antigravity, Hermes, Kimi, Kiro CLI, OpenCode, Pi, OpenClaw), along with each tool's full capability matrix (session resume, MCP, skill injection path, model selection), are covered in [AI coding tools comparison](/providers). ## Writing system instructions @@ -123,5 +123,5 @@ Archived agents can't be assigned new tasks. ## Next steps - [Skills](/skills) — attach knowledge packs to an agent -- [AI coding tools comparison](/providers) — full capability matrix across all 11 tools +- [AI coding tools comparison](/providers) — full capability matrix across all 12 tools - [Assigning issues to agents](/assigning-issues) — put your new agent to work diff --git a/apps/docs/content/docs/agents-create.zh.mdx b/apps/docs/content/docs/agents-create.zh.mdx index 25a8e55f7..857397971 100644 --- a/apps/docs/content/docs/agents-create.zh.mdx +++ b/apps/docs/content/docs/agents-create.zh.mdx @@ -21,7 +21,7 @@ multica agent create ## 选一款 AI 编程工具 -运行时背后是一款具体的 AI 编程工具。Multica 支持 11 款,最常用的几款: +运行时背后是一款具体的 AI 编程工具。Multica 支持 12 款,最常用的几款: | 工具 | 适合 | |---|---| @@ -31,7 +31,7 @@ multica agent create | **Copilot** | 用 GitHub 账号权益的团队 | | **Gemini** | Google 生态用户 | -另外 6 款(Hermes、Kimi、Kiro CLI、OpenCode、Pi、OpenClaw)以及每款工具的完整能力差别(会话恢复、MCP、skill 注入路径、模型选择)见 [AI 编程工具对照](/providers)。 +另外 7 款(Antigravity、Hermes、Kimi、Kiro CLI、OpenCode、Pi、OpenClaw)以及每款工具的完整能力差别(会话恢复、MCP、skill 注入路径、模型选择)见 [AI 编程工具对照](/providers)。 ## 写系统指令 @@ -123,5 +123,5 @@ claude --model --max-turns 100 --append-system-prompt "always respond in ## 下一步 - [Skills](/skills) —— 给智能体挂专业知识包 -- [AI 编程工具对照](/providers) —— 11 款工具的完整能力差别 +- [AI 编程工具对照](/providers) —— 12 款工具的完整能力差别 - [把 issue 分配给智能体](/assigning-issues) —— 创建完之后怎么用起来 diff --git a/apps/docs/content/docs/cloud-quickstart.mdx b/apps/docs/content/docs/cloud-quickstart.mdx index 1585eb2c8..58ef96115 100644 --- a/apps/docs/content/docs/cloud-quickstart.mdx +++ b/apps/docs/content/docs/cloud-quickstart.mdx @@ -7,7 +7,7 @@ import { Callout } from "fumadocs-ui/components/callout"; This page walks you end-to-end through Multica Cloud — **sign up → install the [CLI](/cli) → start the [daemon](/daemon-runtimes) → create an [agent](/agents) → assign your first [task](/tasks)**. Takes about 5 minutes. -One prerequisite: you already have at least one [AI coding tool](/providers) installed locally ([Claude Code](/providers#claude-code), [Codex](/providers#codex), [Cursor](/providers#cursor), [Copilot](/providers#copilot), [Gemini](/providers#gemini), [Hermes](/providers#hermes), [Kimi](/providers#kimi), [Kiro CLI](/providers#kiro-cli), [OpenCode](/providers#opencode), [OpenClaw](/providers#openclaw), or [Pi](/providers#pi)). The daemon auto-detects them on startup and refuses to start if none are present. +One prerequisite: you already have at least one [AI coding tool](/providers) installed locally ([Antigravity](/providers#antigravity), [Claude Code](/providers#claude-code), [Codex](/providers#codex), [Cursor](/providers#cursor), [Copilot](/providers#copilot), [Gemini](/providers#gemini), [Hermes](/providers#hermes), [Kimi](/providers#kimi), [Kiro CLI](/providers#kiro-cli), [OpenCode](/providers#opencode), [OpenClaw](/providers#openclaw), or [Pi](/providers#pi)). The daemon auto-detects them on startup and refuses to start if none are present. ## 1. Create an account @@ -114,6 +114,6 @@ The web UI updates in **real time** (via WebSocket) — no refresh needed. - [Daemon and runtimes](/daemon-runtimes) — how the daemon operates and what runtimes mean - [Tasks](/tasks) — task lifecycle and retry rules -- [AI coding tools compared](/providers) — capability differences across the 11 tools +- [AI coding tools compared](/providers) — capability differences across the 12 tools - [Desktop app](/desktop-app) — if you'd rather not run the daemon yourself - [Self-host quickstart](/self-host-quickstart) — run your own backend diff --git a/apps/docs/content/docs/cloud-quickstart.zh.mdx b/apps/docs/content/docs/cloud-quickstart.zh.mdx index afc46a194..52d4ad753 100644 --- a/apps/docs/content/docs/cloud-quickstart.zh.mdx +++ b/apps/docs/content/docs/cloud-quickstart.zh.mdx @@ -7,7 +7,7 @@ import { Callout } from "fumadocs-ui/components/callout"; 这一页带你走一遍 Multica Cloud 的端到端流程——**注册 → 装 [命令行工具](/cli) → 启动 [守护进程](/daemon-runtimes) → 创建 [智能体](/agents) → 分配第一个 [任务](/tasks)**,约 5 分钟完成。 -前置只有一个:你本地已经装了至少一款 [AI 编程工具](/providers)([Claude Code](/providers#claude-code)、[Codex](/providers#codex)、[Cursor](/providers#cursor)、[Copilot](/providers#copilot)、[Gemini](/providers#gemini)、[Hermes](/providers#hermes)、[Kimi](/providers#kimi)、[Kiro CLI](/providers#kiro-cli)、[OpenCode](/providers#opencode)、[OpenClaw](/providers#openclaw)、[Pi](/providers#pi))中的一款。守护进程启动时会自动探测它们,没装任何一个的话守护进程会直接拒绝启动。 +前置只有一个:你本地已经装了至少一款 [AI 编程工具](/providers)([Antigravity](/providers#antigravity)、[Claude Code](/providers#claude-code)、[Codex](/providers#codex)、[Cursor](/providers#cursor)、[Copilot](/providers#copilot)、[Gemini](/providers#gemini)、[Hermes](/providers#hermes)、[Kimi](/providers#kimi)、[Kiro CLI](/providers#kiro-cli)、[OpenCode](/providers#opencode)、[OpenClaw](/providers#openclaw)、[Pi](/providers#pi))中的一款。守护进程启动时会自动探测它们,没装任何一个的话守护进程会直接拒绝启动。 ## 1. 注册账号 @@ -114,6 +114,6 @@ Web 界面会**实时**(通过 WebSocket)显示进度——不需要刷新 - [守护进程与运行时](/daemon-runtimes) —— 守护进程怎么运作、运行时概念 - [执行任务](/tasks) —— 任务生命周期、重试规则 -- [AI 编程工具对照](/providers) —— 11 款工具的能力差异 +- [AI 编程工具对照](/providers) —— 12 款工具的能力差异 - [桌面应用](/desktop-app) —— 不想自己跑守护进程的话 - [Self-Host 快速上手](/self-host-quickstart) —— 在自己服务器上跑一套 diff --git a/apps/docs/content/docs/daemon-runtimes.mdx b/apps/docs/content/docs/daemon-runtimes.mdx index cbf4e3a1d..4480dd260 100644 --- a/apps/docs/content/docs/daemon-runtimes.mdx +++ b/apps/docs/content/docs/daemon-runtimes.mdx @@ -21,7 +21,7 @@ multica daemon start On startup it does four things: 1. Reads the credentials saved when you logged in -2. Detects AI coding tools installed on your `PATH` (11 built-in: [Claude Code](/providers#claude-code), [Codex](/providers#codex), [Cursor](/providers#cursor), [Copilot](/providers#copilot), [Gemini](/providers#gemini), [Hermes](/providers#hermes), [Kimi](/providers#kimi), [Kiro CLI](/providers#kiro-cli), [OpenCode](/providers#opencode), [OpenClaw](/providers#openclaw), [Pi](/providers#pi)) +2. Detects AI coding tools installed on your `PATH` (12 built-in: [Antigravity](/providers#antigravity), [Claude Code](/providers#claude-code), [Codex](/providers#codex), [Cursor](/providers#cursor), [Copilot](/providers#copilot), [Gemini](/providers#gemini), [Hermes](/providers#hermes), [Kimi](/providers#kimi), [Kiro CLI](/providers#kiro-cli), [OpenCode](/providers#opencode), [OpenClaw](/providers#openclaw), [Pi](/providers#pi)) 3. Registers itself with the server, along with a runtime for each detected tool 4. Keeps **polling every 3 seconds** for tasks to pick up, and **sends a heartbeat every 15 seconds** @@ -108,4 +108,4 @@ More scenarios in [Troubleshooting](/troubleshooting). ## Next - [Tasks](/tasks) — the full lifecycle of a task once the daemon picks it up -- [Providers Matrix](/providers) — capability differences across the 11 AI coding tools +- [Providers Matrix](/providers) — capability differences across the 12 AI coding tools diff --git a/apps/docs/content/docs/daemon-runtimes.zh.mdx b/apps/docs/content/docs/daemon-runtimes.zh.mdx index b9c2eaad7..262cb2b80 100644 --- a/apps/docs/content/docs/daemon-runtimes.zh.mdx +++ b/apps/docs/content/docs/daemon-runtimes.zh.mdx @@ -21,7 +21,7 @@ multica daemon start 启动后它会做四件事: 1. 读取你登录时保存的凭证 -2. 探测本机 `PATH` 上已安装的 AI 编程工具(内置支持 11 款:[Claude Code](/providers#claude-code)、[Codex](/providers#codex)、[Cursor](/providers#cursor)、[Copilot](/providers#copilot)、[Gemini](/providers#gemini)、[Hermes](/providers#hermes)、[Kimi](/providers#kimi)、[Kiro CLI](/providers#kiro-cli)、[OpenCode](/providers#opencode)、[OpenClaw](/providers#openclaw)、[Pi](/providers#pi)) +2. 探测本机 `PATH` 上已安装的 AI 编程工具(内置支持 12 款:[Antigravity](/providers#antigravity)、[Claude Code](/providers#claude-code)、[Codex](/providers#codex)、[Cursor](/providers#cursor)、[Copilot](/providers#copilot)、[Gemini](/providers#gemini)、[Hermes](/providers#hermes)、[Kimi](/providers#kimi)、[Kiro CLI](/providers#kiro-cli)、[OpenCode](/providers#opencode)、[OpenClaw](/providers#openclaw)、[Pi](/providers#pi)) 3. 向服务器注册自己,以及每款检测到的工具对应的运行时 4. 持续**每 3 秒轮询一次**是否有任务要领,**每 15 秒发一次心跳** @@ -108,4 +108,4 @@ Multica 对并发有两层限额: ## 下一步 - [执行任务](/tasks) —— 守护进程领到任务后,它的完整生命周期 -- [Providers Matrix](/providers) —— 11 款 AI 编程工具的能力差异对照 +- [Providers Matrix](/providers) —— 12 款 AI 编程工具的能力差异对照 diff --git a/apps/docs/content/docs/how-multica-works.mdx b/apps/docs/content/docs/how-multica-works.mdx index 659c34ca2..a5b35a4db 100644 --- a/apps/docs/content/docs/how-multica-works.mdx +++ b/apps/docs/content/docs/how-multica-works.mdx @@ -13,7 +13,7 @@ Multica is a **distributed** platform. The web interface you see is just the fro - **Multica server** — the workspaces, issue lists, and comment threads you see all live in its database. It's also a WebSocket hub that pushes real-time updates between you and your teammates. It does **not** execute any agent tasks. - **Daemon** — part of the Multica CLI, running on your own machine. On start it detects which AI coding tools are installed locally, registers with the server, and begins polling for tasks every 3 seconds and sending heartbeats every 15 seconds. -- **AI coding tools** — one of the eleven (or several in parallel): [Claude Code](/providers#claude-code), [Codex](/providers#codex), [Cursor](/providers#cursor), [Copilot](/providers#copilot), [Gemini](/providers#gemini), [Hermes](/providers#hermes), [Kimi](/providers#kimi), [Kiro CLI](/providers#kiro-cli), [OpenCode](/providers#opencode), [OpenClaw](/providers#openclaw), [Pi](/providers#pi). Once the daemon has picked up a task, it uses these tools to actually do the work. +- **AI coding tools** — one of the twelve (or several in parallel): [Antigravity](/providers#antigravity), [Claude Code](/providers#claude-code), [Codex](/providers#codex), [Cursor](/providers#cursor), [Copilot](/providers#copilot), [Gemini](/providers#gemini), [Hermes](/providers#hermes), [Kimi](/providers#kimi), [Kiro CLI](/providers#kiro-cli), [OpenCode](/providers#opencode), [OpenClaw](/providers#openclaw), [Pi](/providers#pi). Once the daemon has picked up a task, it uses these tools to actually do the work. Because the toolchain stays local, **your API keys, code directories, and authorized tools** are only ever used on your machine — the Multica server never sees any of them. This holds whether you self-host or use Cloud. diff --git a/apps/docs/content/docs/how-multica-works.zh.mdx b/apps/docs/content/docs/how-multica-works.zh.mdx index 280eba860..8a9b045a0 100644 --- a/apps/docs/content/docs/how-multica-works.zh.mdx +++ b/apps/docs/content/docs/how-multica-works.zh.mdx @@ -13,7 +13,7 @@ Multica 是一个**分布式**平台。你看到的 Web 界面只是前台—— - **Multica 服务器**——你看到的工作区、issue 列表、评论线都存在它的数据库里。它同时是 WebSocket hub,把你和同事之间的实时更新推送过去。它**不**执行任何智能体任务。 - **守护进程**(daemon)——Multica CLI 的一部分,跑在你自己的机器上。启动后它探测本地装了哪些 AI 编程工具,注册到 server,开始每 3 秒领一次任务、每 15 秒发一次心跳。 -- **AI 编程工具**——[Claude Code](/providers#claude-code)、[Codex](/providers#codex)、[Cursor](/providers#cursor)、[Copilot](/providers#copilot)、[Gemini](/providers#gemini)、[Hermes](/providers#hermes)、[Kimi](/providers#kimi)、[Kiro CLI](/providers#kiro-cli)、[OpenCode](/providers#opencode)、[OpenClaw](/providers#openclaw)、[Pi](/providers#pi) 11 款之一(或多款并存)。守护进程领到任务后,用这些工具真正去写代码。 +- **AI 编程工具**——[Antigravity](/providers#antigravity)、[Claude Code](/providers#claude-code)、[Codex](/providers#codex)、[Cursor](/providers#cursor)、[Copilot](/providers#copilot)、[Gemini](/providers#gemini)、[Hermes](/providers#hermes)、[Kimi](/providers#kimi)、[Kiro CLI](/providers#kiro-cli)、[OpenCode](/providers#opencode)、[OpenClaw](/providers#openclaw)、[Pi](/providers#pi) 12 款之一(或多款并存)。守护进程领到任务后,用这些工具真正去写代码。 工具链在本地的结果:**你的 API 密钥、代码目录、已授权的工具**都只在本地使用;Multica 服务器一个都看不到。自部署还是用 Cloud 都不改变这一点。 diff --git a/apps/docs/content/docs/index.mdx b/apps/docs/content/docs/index.mdx index ff2e9a731..5e6fb98e3 100644 --- a/apps/docs/content/docs/index.mdx +++ b/apps/docs/content/docs/index.mdx @@ -13,7 +13,7 @@ This page explains where agents run and the ways you can start using Multica. Agents do **not** execute tasks on Multica's servers. Multica currently supports one runtime model: -- **Local [daemon](/daemon-runtimes)** — you run `multica daemon` on your own machine, and it drives the [AI coding tools](/providers) installed locally. Eleven are built in today: [Claude Code](/providers#claude-code), [Codex](/providers#codex), [Cursor](/providers#cursor), [Copilot](/providers#copilot), [Gemini](/providers#gemini), [Hermes](/providers#hermes), [Kimi](/providers#kimi), [Kiro CLI](/providers#kiro-cli), [OpenCode](/providers#opencode), [OpenClaw](/providers#openclaw), [Pi](/providers#pi). Your API keys, toolchain, and code directories stay on your machine. +- **Local [daemon](/daemon-runtimes)** — you run `multica daemon` on your own machine, and it drives the [AI coding tools](/providers) installed locally. Twelve are built in today: [Antigravity](/providers#antigravity), [Claude Code](/providers#claude-code), [Codex](/providers#codex), [Cursor](/providers#cursor), [Copilot](/providers#copilot), [Gemini](/providers#gemini), [Hermes](/providers#hermes), [Kimi](/providers#kimi), [Kiro CLI](/providers#kiro-cli), [OpenCode](/providers#opencode), [OpenClaw](/providers#openclaw), [Pi](/providers#pi). Your API keys, toolchain, and code directories stay on your machine. **Cloud runtimes are coming**, currently waitlist-only. Once live, you won't need a local daemon — agent tasks will execute on Multica Cloud directly. Sign up on the [Downloads](https://multica.ai/download) page to get notified. diff --git a/apps/docs/content/docs/index.zh.mdx b/apps/docs/content/docs/index.zh.mdx index a74ddc311..4fbaa8369 100644 --- a/apps/docs/content/docs/index.zh.mdx +++ b/apps/docs/content/docs/index.zh.mdx @@ -13,7 +13,7 @@ Multica 是一个任务协作平台,让人类和 AI [智能体](/agents) 在 智能体执行任务**不**发生在 Multica 服务器上。目前 Multica 支持一种运行方式: -- **本地 [守护进程](/daemon-runtimes)** — 你在自己的机器上运行 `multica daemon`,由它调用本地安装的 [AI 编程工具](/providers)。目前内置 11 款:[Claude Code](/providers#claude-code)、[Codex](/providers#codex)、[Cursor](/providers#cursor)、[Copilot](/providers#copilot)、[Gemini](/providers#gemini)、[Hermes](/providers#hermes)、[Kimi](/providers#kimi)、[Kiro CLI](/providers#kiro-cli)、[OpenCode](/providers#opencode)、[OpenClaw](/providers#openclaw)、[Pi](/providers#pi)。你的 API 密钥、工具链、代码目录都保留在本地。 +- **本地 [守护进程](/daemon-runtimes)** — 你在自己的机器上运行 `multica daemon`,由它调用本地安装的 [AI 编程工具](/providers)。目前内置 12 款:[Antigravity](/providers#antigravity)、[Claude Code](/providers#claude-code)、[Codex](/providers#codex)、[Cursor](/providers#cursor)、[Copilot](/providers#copilot)、[Gemini](/providers#gemini)、[Hermes](/providers#hermes)、[Kimi](/providers#kimi)、[Kiro CLI](/providers#kiro-cli)、[OpenCode](/providers#opencode)、[OpenClaw](/providers#openclaw)、[Pi](/providers#pi)。你的 API 密钥、工具链、代码目录都保留在本地。 **云端运行时即将开放**,目前处于等待名单阶段。上线后,你无需在本地运行守护进程,即可在 Multica Cloud 上直接执行智能体任务。在 [下载页面](https://multica.ai/download) 登记邮箱以获取通知。 diff --git a/apps/docs/content/docs/install-agent-runtime.mdx b/apps/docs/content/docs/install-agent-runtime.mdx index f5746054b..5b0012e7d 100644 --- a/apps/docs/content/docs/install-agent-runtime.mdx +++ b/apps/docs/content/docs/install-agent-runtime.mdx @@ -1,11 +1,11 @@ --- title: Install an agent runtime -description: Multica drives whichever AI coding tools you have on your machine. This page shows you how to install each of the 11 supported tools so the daemon can detect them. +description: Multica drives whichever AI coding tools you have on your machine. This page shows you how to install each of the 12 supported tools so the daemon can detect them. --- import { Callout } from "fumadocs-ui/components/callout"; -A **runtime** in Multica is the daemon on your machine paired with one AI coding tool the daemon found on your `PATH`. If the onboarding "Connect a runtime" step shows **No supported tools detected**, it means the daemon scanned `PATH` and didn't find any of the 11 tools it knows how to drive. Install one (or several) of the tools below, then come back to the step and re-scan — the runtime will show up within a few seconds. +A **runtime** in Multica is the daemon on your machine paired with one AI coding tool the daemon found on your `PATH`. If the onboarding "Connect a runtime" step shows **No supported tools detected**, it means the daemon scanned `PATH` and didn't find any of the 12 tools it knows how to drive. Install one (or several) of the tools below, then come back to the step and re-scan — the runtime will show up within a few seconds. This page is the install-side companion to: @@ -31,9 +31,9 @@ multica daemon restart Or, in the desktop app, just relaunch the app. The daemon re-scans `PATH` on every start. -## The 11 supported tools +## The 12 supported tools -Listed roughly from most to least common. Pick whichever ones you already have credentials for — you don't need all 11. +Listed roughly from most to least common. Pick whichever ones you already have credentials for — you don't need all 12. ### Claude Code (Anthropic) @@ -147,9 +147,20 @@ Minimalist. **Session resumption is unusual** — the resume id is the path to a | Install | See Inflection's CLI docs at [pi.ai](https://pi.ai/). | | Authentication | Per the vendor's docs. | +### Antigravity (Google) + +Google's Antigravity CLI (`agy`). Pairs with Google's Antigravity service and runs Gemini-backed models. Session resumption works through `--conversation `, captured by the daemon from the CLI log file. Model selection is managed inside the Antigravity CLI itself — Multica disables the per-agent model picker for this provider. Skills are written to `.agents/skills/` (the CLI inherits Gemini CLI's workspace skill layout — see [Antigravity docs](https://antigravity.google/docs/gcli-migration)). + +| | | +|---|---| +| Daemon looks for | `agy` | +| Install | Follow the official guide at [antigravity.google/docs/cli-overview](https://antigravity.google/docs/cli-overview). The CLI ships pre-built — run `agy install` once to wire up PATH and shell aliases. | +| Authentication | Run `agy` once interactively and complete the Google account login, or sign in via the Antigravity desktop app — the CLI reuses the keyring entry the GUI writes. | +| Notes | The CLI emits plain assistant text on stdout, not a structured event stream; intermediate "I will run X" lines and the final reply are both relayed to Multica as text. | + ## After installing -1. **Confirm the binary is on `PATH`.** Open a fresh terminal and run `which ` (for example `which claude`, `which cursor-agent`, `which kiro-cli`). If it prints a path, the daemon will find it. If it prints nothing, fix your shell `PATH` first (the typical cause is a per-shell rc file that wasn't reloaded). +1. **Confirm the binary is on `PATH`.** Open a fresh terminal and run `which ` (for example `which claude`, `which cursor-agent`, `which kiro-cli`, `which agy`). If it prints a path, the daemon will find it. If it prints nothing, fix your shell `PATH` first (the typical cause is a per-shell rc file that wasn't reloaded). 2. **Restart the daemon.** `multica daemon restart`, or relaunch the desktop app. The daemon only scans `PATH` at startup. 3. **Check the Runtimes page.** In the Multica UI, the **Runtimes** page should now list one row per `(workspace × tool)` combination. If the row says "offline", see [Daemon and runtimes → When a runtime is marked offline](/daemon-runtimes#when-a-runtime-is-marked-offline). 4. **Go back to onboarding.** The "Connect a runtime" step polls and will pick up the new runtime within a few seconds — no need to refresh. diff --git a/apps/docs/content/docs/install-agent-runtime.zh.mdx b/apps/docs/content/docs/install-agent-runtime.zh.mdx index 7e18e0d21..8711a5096 100644 --- a/apps/docs/content/docs/install-agent-runtime.zh.mdx +++ b/apps/docs/content/docs/install-agent-runtime.zh.mdx @@ -1,11 +1,11 @@ --- title: 安装一个 Agent 运行时 -description: Multica 驱动本机上已安装的 AI 编程工具。这一页讲清楚怎么安装目前支持的 11 款工具,让守护进程能扫到。 +description: Multica 驱动本机上已安装的 AI 编程工具。这一页讲清楚怎么安装目前支持的 12 款工具,让守护进程能扫到。 --- import { Callout } from "fumadocs-ui/components/callout"; -在 Multica 里,一个**运行时**(runtime)就是你机器上的守护进程,配上守护进程在 `PATH` 里扫到的某一款 AI 编程工具。如果 onboarding 的 "连接运行时" 这一步显示 **未检测到支持的工具**,说明守护进程扫了 `PATH`,但 11 款它认得的工具一个都没找到。装下面任意一款(或几款),回到这一步重新扫描,几秒内运行时就会出现。 +在 Multica 里,一个**运行时**(runtime)就是你机器上的守护进程,配上守护进程在 `PATH` 里扫到的某一款 AI 编程工具。如果 onboarding 的 "连接运行时" 这一步显示 **未检测到支持的工具**,说明守护进程扫了 `PATH`,但 12 款它认得的工具一个都没找到。装下面任意一款(或几款),回到这一步重新扫描,几秒内运行时就会出现。 这一页是装机的入口,和它配套的是: @@ -31,13 +31,13 @@ multica daemon restart 桌面端的话,重启 app 即可。守护进程只在启动时扫一次 `PATH`。 -## 11 款支持的工具 +## 12 款支持的工具 -大致按常见程度排序。挑你已经有账号 / API key 的那几款就行 —— 不需要 11 个全装。 +大致按常见程度排序。挑你已经有账号 / API key 的那几款就行 —— 不需要 12 个全装。 ### Claude Code(Anthropic) -集成最完整的一款。会话续接好用,MCP 好用,而且 **11 款里只有它真正会读 agent 配置里的 `mcp_config` 字段**(见[矩阵](/zh/providers))。 +集成最完整的一款。会话续接好用,MCP 好用,而且 **12 款里只有它真正会读 agent 配置里的 `mcp_config` 字段**(见[矩阵](/zh/providers))。 | | | |---|---| @@ -147,9 +147,20 @@ ACP 协议 agent(和 Kimi 共享传输层)。会话续接可用。Skill 注 | 安装 | 看 Inflection 的 CLI 文档 [pi.ai](https://pi.ai/)。 | | 认证 | 按厂商文档。 | +### Antigravity(Google) + +Google 的 Antigravity CLI(`agy`)。搭配 Google Antigravity 服务,默认走 Gemini 系列模型。会话续接通过 `--conversation ` 工作——守护进程从 CLI 的日志文件里抓取 conversation UUID。模型选择保存在 Antigravity CLI 自己的设置里——Multica 里这款工具的「模型」选择项被禁用。Skill 文件写入 `.agents/skills/`(CLI 沿用 Gemini CLI 的 workspace 布局——见 [Antigravity 文档](https://antigravity.google/docs/gcli-migration))。 + +| | | +|---|---| +| 守护进程扫描 | `agy` | +| 安装 | 看官方指引 [antigravity.google/docs/cli-overview](https://antigravity.google/docs/cli-overview)。CLI 是预编译的,跑一次 `agy install` 配好 PATH 和 shell 别名即可。 | +| 认证 | 交互式跑一次 `agy` 走 Google 账号登录流程;或者通过 Antigravity 桌面端登录——CLI 会复用 GUI 写入 keyring 的凭据。 | +| 备注 | CLI 的 stdout 是纯文本,不是结构化事件流;中间的 "I will run X" 思考过程和最终回复都会作为 text 消息送回 Multica。 | + ## 装完之后 -1. **确认可执行文件在 `PATH` 上。** 开一个新终端,跑 `which <名字>`(比如 `which claude`、`which cursor-agent`、`which kiro-cli`)。打印出路径,守护进程就找得到;什么都不打印,先修 shell 的 `PATH`(最常见原因是 rc 文件没重新加载)。 +1. **确认可执行文件在 `PATH` 上。** 开一个新终端,跑 `which <名字>`(比如 `which claude`、`which cursor-agent`、`which kiro-cli`、`which agy`)。打印出路径,守护进程就找得到;什么都不打印,先修 shell 的 `PATH`(最常见原因是 rc 文件没重新加载)。 2. **重启守护进程。** `multica daemon restart`,或者重启桌面端。守护进程只在启动时扫一次 `PATH`。 3. **看 Runtimes 页面。** Multica UI 的 **Runtimes** 页应该会出现一行 `(工作区 × 工具)`。如果显示 "offline",看[守护进程与运行时 → 运行时何时被标记为离线](/zh/daemon-runtimes#运行时何时被标记为离线)。 4. **回到 onboarding。** "连接运行时" 这一步会一直轮询,几秒内就能扫到新运行时,不需要手动刷新。 diff --git a/apps/docs/content/docs/providers.mdx b/apps/docs/content/docs/providers.mdx index 50be7d7d3..47588551d 100644 --- a/apps/docs/content/docs/providers.mdx +++ b/apps/docs/content/docs/providers.mdx @@ -1,11 +1,11 @@ --- title: AI coding tools matrix -description: Multica supports 11 AI coding tools; they implement the same interface, but the capability details diverge significantly. +description: Multica supports 12 AI coding tools; they implement the same interface, but the capability details diverge significantly. --- import { Callout } from "fumadocs-ui/components/callout"; -Multica ships with built-in support for **11 AI coding tools**. They all implement the same interface — queue, dispatch, execute, return results — so you can drive any of them from the same Multica board. **But the capability details diverge significantly**: whether session resumption actually works, whether MCP is supported, where skill files live, how models are selected. This page is the full matrix. +Multica ships with built-in support for **12 AI coding tools**. They all implement the same interface — queue, dispatch, execute, return results — so you can drive any of them from the same Multica board. **But the capability details diverge significantly**: whether session resumption actually works, whether MCP is supported, where skill files live, how models are selected. This page is the full matrix. For guidance on picking a tool when creating an agent, see [Creating and configuring agents](/agents-create). @@ -13,6 +13,7 @@ For guidance on picking a tool when creating an agent, see [Creating and configu | Tool | Vendor | Session resumption | MCP | Skill injection path | Model selection | |---|---|---|---|---|---| +| **Antigravity** | Google | ✅ (`--conversation `) | ❌ | `.agents/skills/` | Managed inside the Antigravity CLI itself | | **Claude Code** | Anthropic | ✅ | **✅ (the only one that actually uses it)** | `.claude/skills/` | Static + flag | | **Codex** | OpenAI | ⚠️ Code exists but unreachable | ❌ | `$CODEX_HOME/skills/` | Static | | **Copilot** | GitHub | ✅ | ❌ | `.github/skills/` | Static (determined by account entitlement) | @@ -27,6 +28,10 @@ For guidance on picking a tool when creating an agent, see [Creating and configu ## What each tool is for +### Antigravity + +From Google. CLI binary name is `agy`. Pairs with Google's Antigravity service and ships with a Gemini-backed default model. **Session resumption works** via `--conversation `; the daemon captures the conversation UUID from the CLI's log file because stdout is plain text rather than a structured event stream. There is no `--model` flag — model selection lives inside the Antigravity CLI settings, so Multica disables the per-agent model picker for this provider. Skills land in `.agents/skills/` (the CLI inherits Gemini CLI's workspace skill layout — see [Antigravity migration docs](https://antigravity.google/docs/gcli-migration)). + ### Claude Code From Anthropic. **First choice for new users** — the most complete feature set: session resumption actually works, it's the **only one of the 11 that truly reads MCP configuration**, and it supports fine-tuning flags like `--max-turns` and `--append-system-prompt`. Requires an Anthropic API key. @@ -77,7 +82,7 @@ The session resumption mechanism is covered in [Tasks](/tasks#can-a-task-continu | Status | Tools | Meaning | |---|---|---| -| ✅ Really works | Claude Code, Copilot, Hermes, Kimi, Kiro CLI, OpenCode, OpenClaw, Pi | Pass the resume id and it continues from the previous context | +| ✅ Really works | Antigravity, Claude Code, Copilot, Hermes, Kimi, Kiro CLI, OpenCode, OpenClaw, Pi | Pass the resume id and it continues from the previous context | | ⚠️ Code exists but unreachable | Codex, Cursor | Resume paths exist in the code but aren't actually reached (Codex silently falls back; Cursor doesn't return session id) — **treat as unsupported** | | ❌ None | Gemini | The CLI has no resume mechanism | @@ -85,7 +90,7 @@ The session resumption mechanism is covered in [Tasks](/tasks#can-a-task-continu ## MCP configuration: only Claude Code actually reads it -**Of the 11 tools, only Claude Code actually consumes `mcp_config`**. The other 10 accept the field but **completely ignore it** — no error, no warning, the config just has no effect. +**Of the 12 tools, only Claude Code actually consumes `mcp_config`**. The other 11 accept the field but **completely ignore it** — no error, no warning, the config just has no effect. If you set `mcp_config` in an agent configuration but pick a tool other than Claude Code, your MCP servers have **no effect** on that agent. MCP integration currently covers Claude Code only. @@ -105,6 +110,7 @@ Each tool uses **its own** skill discovery path. Before a task runs, the Multica | Kiro CLI | `.kiro/skills/` | ✅ Native | | OpenCode | `.opencode/skills/` | ✅ Native | | Pi | `.pi/skills/` | ✅ Native | +| Antigravity | `.agents/skills/` | ✅ Native (inherits Gemini CLI's workspace layout — see [Antigravity docs](https://antigravity.google/docs/gcli-migration)) | | Gemini | `.agent_context/skills/` | ⚠️ Generic fallback | | Hermes | `.agent_context/skills/` | ⚠️ Generic fallback | | OpenClaw | `.agent_context/skills/` | ⚠️ Generic fallback | @@ -118,3 +124,4 @@ For creating and using skills, see [Skills](/skills). - [Creating and configuring agents](/agents-create) — pick a tool for your agent - [Tasks](/tasks) — task lifecycle and session-resumption mechanics - [Daemon and runtimes](/daemon-runtimes) — where the tools run and how they connect to Multica +- [Install an agent runtime](/install-agent-runtime) — installation and authentication for each of the 12 supported tools diff --git a/apps/docs/content/docs/providers.zh.mdx b/apps/docs/content/docs/providers.zh.mdx index ddea6051a..0c462f340 100644 --- a/apps/docs/content/docs/providers.zh.mdx +++ b/apps/docs/content/docs/providers.zh.mdx @@ -1,11 +1,11 @@ --- title: AI 编程工具对照 -description: Multica 支持 11 款 AI 编程工具;它们实现同一套接口,但能力细节差异很大。 +description: Multica 支持 12 款 AI 编程工具;它们实现同一套接口,但能力细节差异很大。 --- import { Callout } from "fumadocs-ui/components/callout"; -Multica 内置支持 **11 款 AI 编程工具**。它们都实现了同一套接口——排队、派发、执行、结果回传,所以你可以从 Multica 的同一个看板上指挥任意一款。**但它们在能力细节上差异很大**:会话恢复是否真用、是否支持 MCP、skill 文件该放在哪里、模型怎么选。这一页是完整对照。 +Multica 内置支持 **12 款 AI 编程工具**。它们都实现了同一套接口——排队、派发、执行、结果回传,所以你可以从 Multica 的同一个看板上指挥任意一款。**但它们在能力细节上差异很大**:会话恢复是否真用、是否支持 MCP、skill 文件该放在哪里、模型怎么选。这一页是完整对照。 创建智能体时挑选工具的指引见 [创建和配置智能体](/agents-create)。 @@ -13,6 +13,7 @@ Multica 内置支持 **11 款 AI 编程工具**。它们都实现了同一套接 | 工具 | 厂商 | 会话恢复 | MCP | Skill 注入路径 | 模型选择 | |---|---|---|---|---|---| +| **Antigravity** | Google | ✅(`--conversation `)| ❌ | `.agents/skills/` | 由 Antigravity CLI 自己管理 | | **Claude Code** | Anthropic | ✅ | **✅(唯一真用)** | `.claude/skills/` | 静态 + flag | | **Codex** | OpenAI | ⚠️ 代码存在但不可达 | ❌ | `$CODEX_HOME/skills/` | 静态 | | **Copilot** | GitHub | ✅ | ❌ | `.github/skills/` | 静态(账号权益决定)| @@ -27,9 +28,13 @@ Multica 内置支持 **11 款 AI 编程工具**。它们都实现了同一套接 ## 每款工具的定位 +### Antigravity + +Google 出品。CLI 二进制名为 `agy`,搭配 Google Antigravity 服务,默认走 Gemini 系列模型。**会话恢复真用**——通过 `--conversation `;因为 stdout 是纯文本而非结构化事件流,守护进程从 CLI 的日志文件里抓取 conversation UUID。CLI 没有 `--model` flag——模型选择保存在 Antigravity 自己的设置里,因此 Multica 禁用了这款工具的模型选择控件。Skill 文件写入 `.agents/skills/`(CLI 沿用 Gemini CLI 的 workspace 布局——见 [Antigravity 迁移文档](https://antigravity.google/docs/gcli-migration))。 + ### Claude Code -Anthropic 出品。**新用户首选**——功能最完整:会话恢复真用,是 **11 款里唯一真读 MCP 配置**的工具,支持 `--max-turns`、`--append-system-prompt` 等细调参数。需要一个 Anthropic API 密钥。 +Anthropic 出品。**新用户首选**——功能最完整:会话恢复真用,是 **12 款里唯一真读 MCP 配置**的工具,支持 `--max-turns`、`--append-system-prompt` 等细调参数。需要一个 Anthropic API 密钥。 ### Codex @@ -77,7 +82,7 @@ Inflection AI 出品,极简主义。**会话恢复机制特殊**——session | 状态 | 工具 | 含义 | |---|---|---| -| ✅ 真用 | Claude Code、Copilot、Hermes、Kimi、Kiro CLI、OpenCode、OpenClaw、Pi | 传 resume id,会从上次上下文接着继续 | +| ✅ 真用 | Antigravity、Claude Code、Copilot、Hermes、Kimi、Kiro CLI、OpenCode、OpenClaw、Pi | 传 resume id,会从上次上下文接着继续 | | ⚠️ 代码存在但不可达 | Codex、Cursor | 代码里有 resume 路径但实际走不到(Codex 静默回落、Cursor session id 不回传)—— **当作不支持** | | ❌ 无 | Gemini | CLI 无 resume 机制 | @@ -85,7 +90,7 @@ Inflection AI 出品,极简主义。**会话恢复机制特殊**——session ## MCP 配置:只有 Claude Code 真的读 -**11 款工具里只有 Claude Code 实际消费 `mcp_config`**。其他 10 款会接收这个字段但**完全忽略**——不报错、不警告,只是配置不生效。 +**12 款工具里只有 Claude Code 实际消费 `mcp_config`**。其他 11 款会接收这个字段但**完全忽略**——不报错、不警告,只是配置不生效。 如果你在智能体配置里设置了 `mcp_config`,但选了 Claude Code 之外的工具,你的 MCP server 对这个智能体**没有效果**。目前的 MCP 集成只覆盖 Claude Code。 @@ -105,6 +110,7 @@ Inflection AI 出品,极简主义。**会话恢复机制特殊**——session | Kiro CLI | `.kiro/skills/` | ✅ 原生 | | OpenCode | `.opencode/skills/` | ✅ 原生 | | Pi | `.pi/skills/` | ✅ 原生 | +| Antigravity | `.agents/skills/` | ✅ 原生(沿用 Gemini CLI 的 workspace 布局——见 [Antigravity 文档](https://antigravity.google/docs/gcli-migration))| | Gemini | `.agent_context/skills/` | ⚠️ 通用 fallback | | Hermes | `.agent_context/skills/` | ⚠️ 通用 fallback | | OpenClaw | `.agent_context/skills/` | ⚠️ 通用 fallback | diff --git a/apps/docs/content/docs/skills.mdx b/apps/docs/content/docs/skills.mdx index 1971a56e9..6205e0d7b 100644 --- a/apps/docs/content/docs/skills.mdx +++ b/apps/docs/content/docs/skills.mdx @@ -31,7 +31,7 @@ Both individual files and whole skill packs have size caps (single-file cap arou Once imported, a skill has to be **attached to a specific agent** to take effect. One agent can have multiple skills attached, and one skill can be attached to multiple agents. -After attaching, the agent picks up its skills the next time it starts a task — each AI coding tool has its own skill discovery path (Claude Code uses `.claude/skills/`, Cursor uses `.cursor/skills/`, etc.), and Multica drops files in the right place automatically. **However, three tools (Gemini, Hermes, OpenClaw) currently use the generic fallback path `.agent_context/skills/` — whether these tools actually read skills from that path depends on the tool itself.** Full path mapping and the native-discovery vs. fallback distinction is in [AI coding tools comparison → Where skill files go](/providers#where-skill-files-go). +After attaching, the agent picks up its skills the next time it starts a task — each AI coding tool has its own skill discovery path (Claude Code uses `.claude/skills/`, Cursor uses `.cursor/skills/`, Antigravity uses `.agents/skills/`, etc.), and Multica drops files in the right place automatically. **However, three tools (Gemini, Hermes, OpenClaw) currently use the generic fallback path `.agent_context/skills/` — whether these tools actually read skills from that path depends on the tool itself.** Full path mapping and the native-discovery vs. fallback distinction is in [AI coding tools comparison → Where skill files go](/providers#where-skill-files-go). After you edit a skill's contents, **only newly created tasks pick up the new version** — tasks already running continue with the old skill. @@ -64,4 +64,4 @@ By now you know what an agent is, how to create one, and how to attach skills. T - [Daemon and runtimes](/daemon-runtimes) — where agents actually run, and how to tell online from offline - [Executing tasks](/tasks) — the full lifecycle of one "agent work session" -- [AI coding tools comparison](/providers) — full comparison of all 11 tools (including each one's skill injection path) +- [AI coding tools comparison](/providers) — full comparison of all 12 tools (including each one's skill injection path) diff --git a/apps/docs/content/docs/skills.zh.mdx b/apps/docs/content/docs/skills.zh.mdx index 47578371a..6cb566b15 100644 --- a/apps/docs/content/docs/skills.zh.mdx +++ b/apps/docs/content/docs/skills.zh.mdx @@ -31,7 +31,7 @@ Multica 支持两种 Skill 来源: Skill 导入后需要**挂载到具体的智能体**才会生效。一个智能体能挂多个 Skill,一个 Skill 也能挂到多个智能体。 -挂上之后,智能体下次开工时会自动拿到挂着的 Skill——不同 AI 编程工具有各自的 Skill 发现路径(Claude Code 是 `.claude/skills/`、Cursor 是 `.cursor/skills/` 等),Multica 会自动放到对的位置。**但有 3 款工具(Gemini / Hermes / OpenClaw)当前走的是通用 fallback 路径 `.agent_context/skills/`——这些工具能否真的从这里读到 skill,取决于工具本身是否支持**。完整路径对照和原生发现 vs fallback 的区分见 [AI 编程工具对照 → skill 文件该放哪儿](/providers#skill-文件该放哪儿)。 +挂上之后,智能体下次开工时会自动拿到挂着的 Skill——不同 AI 编程工具有各自的 Skill 发现路径(Claude Code 是 `.claude/skills/`、Cursor 是 `.cursor/skills/`、Antigravity 是 `.agents/skills/` 等),Multica 会自动放到对的位置。**但有 3 款工具(Gemini / Hermes / OpenClaw)当前走的是通用 fallback 路径 `.agent_context/skills/`——这些工具能否真的从这里读到 skill,取决于工具本身是否支持**。完整路径对照和原生发现 vs fallback 的区分见 [AI 编程工具对照 → skill 文件该放哪儿](/providers#skill-文件该放哪儿)。 修改 Skill 的内容后,**只有之后新创建的任务会拿到新版本**——正在跑的任务继续用旧版 Skill。 @@ -64,4 +64,4 @@ Skill 导入后需要**挂载到具体的智能体**才会生效。一个智能 - [守护进程与运行时](/daemon-runtimes) —— 智能体到底跑在哪、怎么判断在线 / 离线 - [执行任务](/tasks) —— 一次"智能体工作"的完整生命周期 -- [AI 编程工具对照](/providers) —— 11 款工具的完整对比(含每款的 Skill 注入路径) +- [AI 编程工具对照](/providers) —— 12 款工具的完整对比(含每款的 Skill 注入路径) diff --git a/apps/docs/content/docs/tasks.mdx b/apps/docs/content/docs/tasks.mdx index f9e90394f..5beec6e6f 100644 --- a/apps/docs/content/docs/tasks.mdx +++ b/apps/docs/content/docs/tasks.mdx @@ -105,7 +105,7 @@ Multica pins the session ID **twice** during a task: once at the start (when the But **which AI coding tools actually support this** varies a lot: -- ✅ **Real support** — Claude Code, Copilot, Hermes, Kimi, Kiro CLI, OpenCode, OpenClaw, Pi +- ✅ **Real support** — Antigravity, Claude Code, Copilot, Hermes, Kimi, Kiro CLI, OpenCode, OpenClaw, Pi - ⚠️ **Code exists but unusable** — Codex, Cursor - ❌ **No support** — Gemini @@ -113,5 +113,5 @@ See [Providers Matrix → Session resumption](/providers#session-resumption-who- ## Next -- [Providers Matrix](/providers) — capability differences across the 11 AI coding tools (including the exact session-resumption status) +- [Providers Matrix](/providers) — capability differences across the 12 AI coding tools (including the exact session-resumption status) - [Assigning issues to agents](/assigning-issues) / [@-mentioning agents in comments](/mentioning-agents) / [Chat](/chat) / [Autopilots](/autopilots) — the four ways to trigger a task diff --git a/apps/docs/content/docs/tasks.zh.mdx b/apps/docs/content/docs/tasks.zh.mdx index 763f540cc..4e30e554f 100644 --- a/apps/docs/content/docs/tasks.zh.mdx +++ b/apps/docs/content/docs/tasks.zh.mdx @@ -105,7 +105,7 @@ Multica 在任务过程中**两次**保存会话 ID——任务一开始(AI 但**哪些 AI 编程工具真的支持**差别很大: -- ✅ **真支持**——Claude Code、Copilot、Hermes、Kimi、Kiro CLI、OpenCode、OpenClaw、Pi +- ✅ **真支持**——Antigravity、Claude Code、Copilot、Hermes、Kimi、Kiro CLI、OpenCode、OpenClaw、Pi - ⚠️ **代码看起来支持但实际不可用**——Codex、Cursor - ❌ **不支持**——Gemini @@ -113,5 +113,5 @@ Multica 在任务过程中**两次**保存会话 ID——任务一开始(AI ## 下一步 -- [Providers Matrix](/providers) —— 11 款 AI 编程工具的能力差异对照(包括会话恢复的精确状态) +- [Providers Matrix](/providers) —— 12 款 AI 编程工具的能力差异对照(包括会话恢复的精确状态) - [分配 issue 给智能体](/assigning-issues) / [在评论里 @智能体](/mentioning-agents) / [聊天](/chat) / [Autopilots](/autopilots) —— 触发执行任务的四种方式 diff --git a/apps/web/features/landing/i18n/en.ts b/apps/web/features/landing/i18n/en.ts index 1015eaa0e..7fe6061e7 100644 --- a/apps/web/features/landing/i18n/en.ts +++ b/apps/web/features/landing/i18n/en.ts @@ -101,7 +101,7 @@ export function createEnDict(allowSignup: boolean): LandingDict { label: "RUNTIMES", title: "One dashboard for all your compute", description: - "Local daemons and cloud runtimes, managed from a single panel. Real-time monitoring of online/offline status, usage charts, and activity heatmaps. Auto-detects 11 supported coding tools on your machine.", + "Local daemons and cloud runtimes, managed from a single panel. Real-time monitoring of online/offline status, usage charts, and activity heatmaps. Auto-detects 12 supported coding tools on your machine.", cards: [ { title: "Unified runtime panel", @@ -116,7 +116,7 @@ export function createEnDict(allowSignup: boolean): LandingDict { { title: "Auto-detection on first run", description: - "Multica scans for 11 supported coding tools \u2014 Claude Code, Codex, Cursor, Copilot, Gemini, Hermes, Kimi, Kiro CLI, OpenCode, OpenClaw, and Pi \u2014 and registers a runtime for each one it finds.", + "Multica scans for 12 supported coding tools \u2014 Antigravity, Claude Code, Codex, Cursor, Copilot, Gemini, Hermes, Kimi, Kiro CLI, OpenCode, OpenClaw, and Pi \u2014 and registers a runtime for each one it finds.", }, ], }, @@ -136,7 +136,7 @@ export function createEnDict(allowSignup: boolean): LandingDict { { title: "Install the CLI & connect your machine", description: - "Run multica setup \u2014 it walks you through OAuth, starts the daemon, and scans for the 11 supported coding tools (Claude Code, Codex, Cursor, Copilot, Gemini, Hermes, Kimi, Kiro CLI, OpenCode, OpenClaw, Pi). Whichever ones you already have installed get registered as runtimes automatically.", + "Run multica setup \u2014 it walks you through OAuth, starts the daemon, and scans for the 12 supported coding tools (Antigravity, Claude Code, Codex, Cursor, Copilot, Gemini, Hermes, Kimi, Kiro CLI, OpenCode, OpenClaw, Pi). Whichever ones you already have installed get registered as runtimes automatically.", }, { title: "Create your first agent", @@ -192,7 +192,7 @@ export function createEnDict(allowSignup: boolean): LandingDict { { question: "What coding agents does Multica support?", answer: - "Multica supports 11 coding tools out of the box: Claude Code, Codex, Cursor, Copilot, Gemini, Hermes, Kimi, Kiro CLI, OpenCode, OpenClaw, and Pi. The daemon auto-detects whichever CLIs you already have installed and registers a runtime for each one. Since it's open source, you can also add your own backends.", + "Multica supports 12 coding tools out of the box: Antigravity, Claude Code, Codex, Cursor, Copilot, Gemini, Hermes, Kimi, Kiro CLI, OpenCode, OpenClaw, and Pi. The daemon auto-detects whichever CLIs you already have installed and registers a runtime for each one. Since it's open source, you can also add your own backends.", }, { question: "Do I need to self-host, or is there a cloud version?", diff --git a/apps/web/features/landing/i18n/zh.ts b/apps/web/features/landing/i18n/zh.ts index c49809f94..a211cf2b0 100644 --- a/apps/web/features/landing/i18n/zh.ts +++ b/apps/web/features/landing/i18n/zh.ts @@ -101,7 +101,7 @@ export function createZhDict(allowSignup: boolean): LandingDict { label: "\u8fd0\u884c\u65f6", title: "\u4e00\u4e2a\u63a7\u5236\u53f0\u7ba1\u7406\u6240\u6709\u7b97\u529b", description: - "\u672c\u5730\u5b88\u62a4\u8fdb\u7a0b\u548c\u4e91\u7aef\u8fd0\u884c\u65f6\uff0c\u5728\u540c\u4e00\u4e2a\u9762\u677f\u4e2d\u7ba1\u7406\u3002\u5b9e\u65f6\u76d1\u63a7\u5728\u7ebf/\u79bb\u7ebf\u72b6\u6001\u3001\u4f7f\u7528\u91cf\u56fe\u8868\u548c\u6d3b\u52a8\u70ed\u529b\u56fe\u3002\u81ea\u52a8\u68c0\u6d4b\u672c\u673a\u5df2\u5b89\u88c5\u7684 11 \u6b3e\u652f\u6301\u7684 AI \u7f16\u7a0b\u5de5\u5177\u3002", + "\u672c\u5730\u5b88\u62a4\u8fdb\u7a0b\u548c\u4e91\u7aef\u8fd0\u884c\u65f6\uff0c\u5728\u540c\u4e00\u4e2a\u9762\u677f\u4e2d\u7ba1\u7406\u3002\u5b9e\u65f6\u76d1\u63a7\u5728\u7ebf/\u79bb\u7ebf\u72b6\u6001\u3001\u4f7f\u7528\u91cf\u56fe\u8868\u548c\u6d3b\u52a8\u70ed\u529b\u56fe\u3002\u81ea\u52a8\u68c0\u6d4b\u672c\u673a\u5df2\u5b89\u88c5\u7684 12 \u6b3e\u652f\u6301\u7684 AI \u7f16\u7a0b\u5de5\u5177\u3002", cards: [ { title: "\u7edf\u4e00\u8fd0\u884c\u65f6\u9762\u677f", @@ -116,7 +116,7 @@ export function createZhDict(allowSignup: boolean): LandingDict { { title: "\u9996\u6b21\u542f\u52a8\u81ea\u52a8\u6ce8\u518c", description: - "Multica \u626b\u63cf\u672c\u673a\u7684 11 \u6b3e\u652f\u6301\u7684 AI \u7f16\u7a0b\u5de5\u5177\u2014\u2014Claude Code\u3001Codex\u3001Cursor\u3001Copilot\u3001Gemini\u3001Hermes\u3001Kimi\u3001Kiro CLI\u3001OpenCode\u3001OpenClaw\u3001Pi\u2014\u2014\u5e76\u4e3a\u6bcf\u6b3e\u5df2\u5b89\u88c5\u7684\u5de5\u5177\u6ce8\u518c\u4e00\u4e2a\u8fd0\u884c\u65f6\u3002", + "Multica \u626b\u63cf\u672c\u673a\u7684 12 \u6b3e\u652f\u6301\u7684 AI \u7f16\u7a0b\u5de5\u5177\u2014\u2014Antigravity\u3001Claude Code\u3001Codex\u3001Cursor\u3001Copilot\u3001Gemini\u3001Hermes\u3001Kimi\u3001Kiro CLI\u3001OpenCode\u3001OpenClaw\u3001Pi\u2014\u2014\u5e76\u4e3a\u6bcf\u6b3e\u5df2\u5b89\u88c5\u7684\u5de5\u5177\u6ce8\u518c\u4e00\u4e2a\u8fd0\u884c\u65f6\u3002", }, ], }, @@ -136,7 +136,7 @@ export function createZhDict(allowSignup: boolean): LandingDict { { title: "\u5b89\u88c5 CLI \u5e76\u8fde\u63a5\u4f60\u7684\u673a\u5668", description: - "运行 multica setup——它会引导你完成 OAuth 登录、启动守护进程、并扫描 11 款支持的 AI 编程工具(Claude Code、Codex、Cursor、Copilot、Gemini、Hermes、Kimi、Kiro CLI、OpenCode、OpenClaw、Pi)。本机已安装的工具会被自动注册成运行时。", + "运行 multica setup——它会引导你完成 OAuth 登录、启动守护进程、并扫描 12 款支持的 AI 编程工具(Antigravity、Claude Code、Codex、Cursor、Copilot、Gemini、Hermes、Kimi、Kiro CLI、OpenCode、OpenClaw、Pi)。本机已安装的工具会被自动注册成运行时。", }, { title: "\u521b\u5efa\u4f60\u7684\u7b2c\u4e00\u4e2a 智能体", @@ -192,7 +192,7 @@ export function createZhDict(allowSignup: boolean): LandingDict { { question: "Multica \u652f\u6301\u54ea\u4e9b\u7f16\u7801 智能体\uff1f", answer: - "Multica \u5f00\u7bb1\u5373\u7528\u652f\u6301 11 \u6b3e AI \u7f16\u7a0b\u5de5\u5177\uff1aClaude Code\u3001Codex\u3001Cursor\u3001Copilot\u3001Gemini\u3001Hermes\u3001Kimi\u3001Kiro CLI\u3001OpenCode\u3001OpenClaw\u3001Pi\u3002\u5b88\u62a4\u8fdb\u7a0b\u4f1a\u81ea\u52a8\u68c0\u6d4b\u672c\u673a\u5df2\u5b89\u88c5\u7684 CLI \u5e76\u4e3a\u6bcf\u6b3e\u6ce8\u518c\u4e00\u4e2a\u8fd0\u884c\u65f6\u3002\u56e0\u4e3a\u5f00\u6e90\uff0c\u4f60\u4e5f\u53ef\u4ee5\u81ea\u5df1\u6dfb\u52a0\u540e\u7aef\u3002", + "Multica \u5f00\u7bb1\u5373\u7528\u652f\u6301 12 \u6b3e AI \u7f16\u7a0b\u5de5\u5177\uff1aAntigravity\u3001Claude Code\u3001Codex\u3001Cursor\u3001Copilot\u3001Gemini\u3001Hermes\u3001Kimi\u3001Kiro CLI\u3001OpenCode\u3001OpenClaw\u3001Pi\u3002\u5b88\u62a4\u8fdb\u7a0b\u4f1a\u81ea\u52a8\u68c0\u6d4b\u672c\u673a\u5df2\u5b89\u88c5\u7684 CLI \u5e76\u4e3a\u6bcf\u6b3e\u6ce8\u518c\u4e00\u4e2a\u8fd0\u884c\u65f6\u3002\u56e0\u4e3a\u5f00\u6e90\uff0c\u4f60\u4e5f\u53ef\u4ee5\u81ea\u5df1\u6dfb\u52a0\u540e\u7aef\u3002", }, { question: "\u9700\u8981\u81ea\u6258\u7ba1\u5417\uff0c\u8fd8\u662f\u6709\u4e91\u7248\u672c\uff1f", diff --git a/packages/views/agents/components/inspector/model-picker.tsx b/packages/views/agents/components/inspector/model-picker.tsx index bc3ed1e0f..8b17a95e0 100644 --- a/packages/views/agents/components/inspector/model-picker.tsx +++ b/packages/views/agents/components/inspector/model-picker.tsx @@ -19,9 +19,10 @@ import { useT } from "../../../i18n"; * it fits a single PropRow. Drops the "select a runtime first" state because * the inspector only renders this picker after a runtime is bound. * - * Unsupported providers (e.g. hermes, which reads its own config) render an - * inert italic "Managed by runtime" label instead of a clickable picker — - * the back-end ignores agent.model for those runtimes anyway. + * Unsupported providers (e.g. antigravity, whose `agy` CLI has no + * `--model` flag and reads model selection from its own settings) render + * an inert italic "Managed by runtime" label instead of a clickable + * picker — the back-end ignores agent.model for those runtimes anyway. */ export function ModelPicker({ runtimeId, diff --git a/packages/views/agents/components/model-dropdown.tsx b/packages/views/agents/components/model-dropdown.tsx index 91a59ed0d..83689005a 100644 --- a/packages/views/agents/components/model-dropdown.tsx +++ b/packages/views/agents/components/model-dropdown.tsx @@ -18,9 +18,10 @@ import { useT } from "../../i18n"; // It fetches the supported-model catalog from the selected runtime — the // daemon enumerates models on demand via heartbeat piggyback. Providers // that don't honour per-agent model selection at runtime (currently -// hermes) return supported=false, and the dropdown renders disabled -// with an explanation instead of silently accepting a value the -// backend would ignore. +// antigravity — `agy` has no `--model` flag and reads selection from +// its own settings) return supported=false, and the dropdown renders +// disabled with an explanation instead of silently accepting a value +// the backend would ignore. export function ModelDropdown({ runtimeId, runtimeOnline, diff --git a/packages/views/assets.d.ts b/packages/views/assets.d.ts new file mode 100644 index 000000000..812d21129 --- /dev/null +++ b/packages/views/assets.d.ts @@ -0,0 +1,11 @@ +// Asset imports — modern bundlers (Next.js / electron-vite / vite) resolve +// these to a URL string at build time. This file lets TypeScript accept +// `import logo from "./logo.png"` inside the shared package. +declare module "*.png" { + const src: string; + export default src; +} +declare module "*.svg" { + const src: string; + export default src; +} diff --git a/packages/views/runtimes/components/antigravity-logo.png b/packages/views/runtimes/components/antigravity-logo.png new file mode 100644 index 000000000..38e0defff Binary files /dev/null and b/packages/views/runtimes/components/antigravity-logo.png differ diff --git a/packages/views/runtimes/components/provider-logo.tsx b/packages/views/runtimes/components/provider-logo.tsx index ab9057b03..6a087851e 100644 --- a/packages/views/runtimes/components/provider-logo.tsx +++ b/packages/views/runtimes/components/provider-logo.tsx @@ -138,6 +138,15 @@ function GeminiLogo({ className }: { className: string }) { ); } +// Antigravity (Google) — official mark, shipped as a PNG asset next to +// this file. Bundler (Next.js / electron-vite) resolves the import to a +// URL string at build time. +import antigravityLogo from "./antigravity-logo.png"; + +function AntigravityLogo({ className }: { className: string }) { + return Antigravity; +} + // Kiro CLI — official icon sourced from kiro.dev/icon.svg. function KiroLogo({ className }: { className: string }) { const maskId = `kiro-logo-mask-${useId().replace(/:/g, "")}`; @@ -207,6 +216,8 @@ export function ProviderLogo({ return ; case "gemini": return ; + case "antigravity": + return ; default: return ; } diff --git a/server/internal/daemon/config.go b/server/internal/daemon/config.go index a4722a999..495a49fd4 100644 --- a/server/internal/daemon/config.go +++ b/server/internal/daemon/config.go @@ -61,7 +61,7 @@ type Config struct { CLIVersion string // multica CLI version (e.g. "0.1.13") LaunchedBy string // "desktop" when spawned by the Electron app, empty for standalone Profile string // profile name (empty = default) - Agents map[string]AgentEntry // keyed by provider: claude, codex, copilot, opencode, openclaw, hermes, gemini, pi, cursor, kimi, kiro + Agents map[string]AgentEntry // keyed by provider: claude, codex, copilot, opencode, openclaw, hermes, gemini, pi, cursor, kimi, kiro, antigravity WorkspacesRoot string // base path for execution envs (default: ~/multica_workspaces) KeepEnvAfterTask bool // preserve env after task for debugging HealthPort int // local HTTP port for health checks (default: 19514) @@ -213,8 +213,15 @@ func LoadConfig(overrides Overrides) (Config, error) { if e, ok := probe("MULTICA_KIRO_PATH", "kiro-cli", "MULTICA_KIRO_MODEL"); ok { agents["kiro"] = e } + // Antigravity has no `--model` flag and ModelSelectionSupported returns + // false for it (see server/pkg/agent/models.go). Pass an empty modelEnv + // so we don't seed AgentEntry.Model from an environment variable that + // the backend would silently ignore, and don't lead users to set it. + if e, ok := probe("MULTICA_ANTIGRAVITY_PATH", "agy", ""); ok { + agents["antigravity"] = e + } if len(agents) == 0 { - return Config{}, fmt.Errorf("no agent CLI found: install claude, codex, copilot, opencode, openclaw, hermes, gemini, pi, cursor-agent, kimi, or kiro-cli and ensure it is on PATH") + return Config{}, fmt.Errorf("no agent CLI found: install claude, codex, copilot, opencode, openclaw, hermes, gemini, pi, cursor-agent, kimi, kiro-cli, or agy and ensure it is on PATH") } claudeArgs, err := shellArgsFromEnv("MULTICA_CLAUDE_ARGS") @@ -549,7 +556,7 @@ func shellArgsFromEnv(name string) ([]string, error) { // invocation, instead of paying the cost-per-miss. var defaultAgentCommandNames = []string{ "claude", "codex", "opencode", "openclaw", "hermes", - "gemini", "pi", "cursor-agent", "copilot", "kimi", "kiro-cli", + "gemini", "pi", "cursor-agent", "copilot", "kimi", "kiro-cli", "agy", } var codexDesktopAppBundlePaths = func() []string { diff --git a/server/internal/daemon/execenv/context.go b/server/internal/daemon/execenv/context.go index fed6779aa..ee1ff43f1 100644 --- a/server/internal/daemon/execenv/context.go +++ b/server/internal/daemon/execenv/context.go @@ -12,16 +12,17 @@ import ( // writeContextFiles renders and writes .agent_context/issue_context.md and // skills into the appropriate provider-native location. // -// Claude: skills → {workDir}/.claude/skills/{name}/SKILL.md (native discovery) -// Codex: skills → handled separately in Prepare via codex-home -// Copilot: skills → {workDir}/.github/skills/{name}/SKILL.md (native project-level discovery) -// OpenCode: skills → {workDir}/.opencode/skills/{name}/SKILL.md (native discovery) -// OpenClaw: skills → {workDir}/skills/{name}/SKILL.md (native discovery — paired with a per-task synthesized openclaw-config.json that pins agents.defaults.workspace to workDir; see openclaw_config.go) -// Pi: skills → {workDir}/.pi/skills/{name}/SKILL.md (native discovery) -// Cursor: skills → {workDir}/.cursor/skills/{name}/SKILL.md (native discovery) -// Kimi: skills → {workDir}/.kimi/skills/{name}/SKILL.md (native discovery) -// Kiro: skills → {workDir}/.kiro/skills/{name}/SKILL.md (native discovery) -// Default: skills → {workDir}/.agent_context/skills/{name}/SKILL.md +// Claude: skills → {workDir}/.claude/skills/{name}/SKILL.md (native discovery) +// Codex: skills → handled separately in Prepare via codex-home +// Copilot: skills → {workDir}/.github/skills/{name}/SKILL.md (native project-level discovery) +// OpenCode: skills → {workDir}/.opencode/skills/{name}/SKILL.md (native discovery) +// OpenClaw: skills → {workDir}/skills/{name}/SKILL.md (native discovery — paired with a per-task synthesized openclaw-config.json that pins agents.defaults.workspace to workDir; see openclaw_config.go) +// Pi: skills → {workDir}/.pi/skills/{name}/SKILL.md (native discovery) +// Cursor: skills → {workDir}/.cursor/skills/{name}/SKILL.md (native discovery) +// Kimi: skills → {workDir}/.kimi/skills/{name}/SKILL.md (native discovery) +// Kiro: skills → {workDir}/.kiro/skills/{name}/SKILL.md (native discovery) +// Antigravity: skills → {workDir}/.agents/skills/{name}/SKILL.md (native discovery — see https://antigravity.google/docs/gcli-migration "Workspace skills") +// Default: skills → {workDir}/.agent_context/skills/{name}/SKILL.md func writeContextFiles(workDir, provider string, ctx TaskContextForEnv) error { contextDir := filepath.Join(workDir, ".agent_context") if err := os.MkdirAll(contextDir, 0o755); err != nil { @@ -163,6 +164,12 @@ func resolveSkillsDir(workDir, provider string) (string, error) { // Kiro CLI auto-discovers project-level skills from .kiro/skills/ // in the workdir. skillsDir = filepath.Join(workDir, ".kiro", "skills") + case "antigravity": + // Antigravity (`agy`) auto-discovers workspace-level skills from + // .agents/skills/ in the workdir. The CLI inherits Gemini CLI's + // workspace skill layout; see https://antigravity.google/docs/gcli-migration + // under "Workspace skills". + skillsDir = filepath.Join(workDir, ".agents", "skills") default: // Fallback: write to .agent_context/skills/ (referenced by meta config). skillsDir = filepath.Join(workDir, ".agent_context", "skills") diff --git a/server/internal/daemon/execenv/execenv_test.go b/server/internal/daemon/execenv/execenv_test.go index e19337b85..740e08131 100644 --- a/server/internal/daemon/execenv/execenv_test.go +++ b/server/internal/daemon/execenv/execenv_test.go @@ -1075,6 +1075,74 @@ func TestInjectRuntimeConfigKiro(t *testing.T) { } } +// TestInjectRuntimeConfigAntigravity pins that AGENTS.md for Antigravity +// advertises native skill discovery (rather than the .agent_context fallback) +// — the CLI inherits Gemini CLI's workspace skill layout at .agents/skills/. +func TestInjectRuntimeConfigAntigravity(t *testing.T) { + t.Parallel() + dir := t.TempDir() + + ctx := TaskContextForEnv{ + IssueID: "test-issue-id", + AgentSkills: []SkillContextForEnv{{Name: "Coding", Content: "Write good code."}}, + } + + if _, err := InjectRuntimeConfig(dir, "antigravity", ctx); err != nil { + t.Fatalf("InjectRuntimeConfig failed: %v", err) + } + + content, err := os.ReadFile(filepath.Join(dir, "AGENTS.md")) + if err != nil { + t.Fatalf("failed to read AGENTS.md: %v", err) + } + + s := string(content) + if !strings.Contains(s, "Multica Agent Runtime") { + t.Error("AGENTS.md missing meta skill header") + } + if !strings.Contains(s, "Coding") { + t.Error("AGENTS.md missing skill name") + } + if !strings.Contains(s, "discovered automatically") { + t.Error("AGENTS.md for Antigravity should advertise native skill discovery") + } + if strings.Contains(s, ".agent_context/skills/") { + t.Error("AGENTS.md for Antigravity must not reference the .agent_context/skills/ fallback") + } +} + +// TestWriteContextFilesAntigravityNativeSkills pins that skills for the +// antigravity provider land in {workDir}/.agents/skills//, matching the +// CLI's native workspace discovery path (Gemini CLI lineage). +func TestWriteContextFilesAntigravityNativeSkills(t *testing.T) { + t.Parallel() + dir := t.TempDir() + + ctx := TaskContextForEnv{ + IssueID: "antigravity-skill-test", + AgentSkills: []SkillContextForEnv{ + {Name: "Go Conventions", Content: "Follow Go conventions."}, + }, + } + + if err := writeContextFiles(dir, "antigravity", ctx); err != nil { + t.Fatalf("writeContextFiles failed: %v", err) + } + + skillMd, err := os.ReadFile(filepath.Join(dir, ".agents", "skills", "go-conventions", "SKILL.md")) + if err != nil { + t.Fatalf("failed to read .agents/skills/go-conventions/SKILL.md: %v", err) + } + if !strings.Contains(string(skillMd), "Follow Go conventions.") { + t.Error("SKILL.md missing content") + } + // The fallback path must NOT be written — Antigravity's scanner reads + // .agents/skills/, not .agent_context/skills/. + if _, err := os.Stat(filepath.Join(dir, ".agent_context", "skills")); !os.IsNotExist(err) { + t.Error(".agent_context/skills/ MUST NOT be written for antigravity — its scanner does not read that path") + } +} + func TestPrepareWithRepoContextOpencode(t *testing.T) { t.Parallel() workspacesRoot := t.TempDir() diff --git a/server/internal/daemon/execenv/runtime_config.go b/server/internal/daemon/execenv/runtime_config.go index 77769e8a9..5cd922d27 100644 --- a/server/internal/daemon/execenv/runtime_config.go +++ b/server/internal/daemon/execenv/runtime_config.go @@ -96,15 +96,16 @@ func formatProjectResource(r ProjectResourceForEnv) string { // For Gemini: writes {workDir}/GEMINI.md (discovered natively by the Gemini CLI) // For Pi: writes {workDir}/AGENTS.md (skills discovered natively from .pi/skills/) // For Cursor: writes {workDir}/AGENTS.md (skills discovered natively from .cursor/skills/) -// For Kimi: writes {workDir}/AGENTS.md (Kimi Code CLI reads AGENTS.md natively; skills auto-discovered from project skills dirs) -// For Kiro: writes {workDir}/AGENTS.md (Kiro CLI reads AGENTS.md natively; skills auto-discovered from project skills dirs) +// For Kimi: writes {workDir}/AGENTS.md (Kimi Code CLI reads AGENTS.md natively; skills auto-discovered from project skills dirs) +// For Kiro: writes {workDir}/AGENTS.md (Kiro CLI reads AGENTS.md natively; skills auto-discovered from project skills dirs) +// For Antigravity: writes {workDir}/AGENTS.md (agy CLI reads AGENTS.md natively; skills discovered natively from .agents/skills/ — see https://antigravity.google/docs/gcli-migration) func InjectRuntimeConfig(workDir, provider string, ctx TaskContextForEnv) (string, error) { content := buildMetaSkillContent(provider, ctx) switch provider { case "claude": return content, os.WriteFile(filepath.Join(workDir, "CLAUDE.md"), []byte(content), 0o644) - case "codex", "copilot", "opencode", "openclaw", "hermes", "pi", "cursor", "kimi", "kiro": + case "codex", "copilot", "opencode", "openclaw", "hermes", "pi", "cursor", "kimi", "kiro", "antigravity": return content, os.WriteFile(filepath.Join(workDir, "AGENTS.md"), []byte(content), 0o644) case "gemini": return content, os.WriteFile(filepath.Join(workDir, "GEMINI.md"), []byte(content), 0o644) @@ -402,17 +403,19 @@ func buildMetaSkillContent(provider string, ctx TaskContextForEnv) string { case "claude": // Claude discovers skills natively from .claude/skills/ — just list names. b.WriteString("You have the following skills installed (discovered automatically):\n\n") - case "codex", "copilot", "opencode", "openclaw", "pi", "cursor", "kimi", "kiro": - // Codex, Copilot, OpenCode, OpenClaw, Pi, Cursor, Kimi, and Kiro discover skills - // natively from their respective paths. For OpenClaw, the daemon also writes a - // per-task openclaw-config.json (exported via OPENCLAW_CONFIG_PATH) that pins - // agents.defaults.workspace to the task workdir so the CLI's scanner picks up - // {workDir}/skills/. + case "codex", "copilot", "opencode", "openclaw", "pi", "cursor", "kimi", "kiro", "antigravity": + // Codex, Copilot, OpenCode, OpenClaw, Pi, Cursor, Kimi, Kiro, and + // Antigravity discover skills natively from their respective paths. + // For OpenClaw, the daemon also writes a per-task openclaw-config.json + // (exported via OPENCLAW_CONFIG_PATH) that pins agents.defaults.workspace + // to the task workdir so the CLI's scanner picks up {workDir}/skills/. + // Antigravity inherits Gemini CLI's workspace skill layout — + // {workDir}/.agents/skills/ — see resolveSkillsDir. b.WriteString("You have the following skills installed (discovered automatically):\n\n") case "gemini", "hermes": - // Gemini reads GEMINI.md directly. Hermes has no native skills discovery - // path wired up in resolveSkillsDir; both fall back to referencing the - // files explicitly under .agent_context/skills/. + // Gemini reads GEMINI.md directly. Hermes has no native skill + // discovery path wired up in resolveSkillsDir; both fall back to + // referencing the files explicitly under .agent_context/skills/. b.WriteString("Detailed skill instructions are in `.agent_context/skills/`. Each subdirectory contains a `SKILL.md`.\n\n") default: b.WriteString("Detailed skill instructions are in `.agent_context/skills/`. Each subdirectory contains a `SKILL.md`.\n\n") diff --git a/server/internal/daemon/local_skills.go b/server/internal/daemon/local_skills.go index ffb61f4b5..05a6f184f 100644 --- a/server/internal/daemon/local_skills.go +++ b/server/internal/daemon/local_skills.go @@ -47,6 +47,8 @@ type runtimeLocalSkillBundle struct { // - Cursor: official forum guidance referencing the built-in /create-skill flow // (https://forum.cursor.com/t/cursor-doesnt-know-new-skills-arens-saved/158507) // - Kiro: project and user-level .kiro/skills directories discovered by Kiro CLI +// - Antigravity: ~/.gemini/antigravity-cli/skills user-level skill root +// (https://antigravity.google/docs/gcli-migration "Global skills") // // Longer-term this mapping would be better colocated with the provider // definitions under server/pkg/agent so adding a new runtime can't silently @@ -78,6 +80,10 @@ func localSkillRootForProvider(provider string) (string, bool, error) { return filepath.Join(home, ".cursor", "skills"), true, nil case "kiro": return filepath.Join(home, ".kiro", "skills"), true, nil + case "antigravity": + // agy inherits Gemini CLI's global skill root; see + // https://antigravity.google/docs/gcli-migration ("Global skills"). + return filepath.Join(home, ".gemini", "antigravity-cli", "skills"), true, nil default: return "", false, nil } diff --git a/server/pkg/agent/agent.go b/server/pkg/agent/agent.go index 9a08ea1bd..65fc142d6 100644 --- a/server/pkg/agent/agent.go +++ b/server/pkg/agent/agent.go @@ -1,7 +1,7 @@ // Package agent provides a unified interface for executing prompts via // coding agents (Claude Code, Codex, Copilot, OpenCode, OpenClaw, Hermes, -// Gemini, Pi, Cursor, Kimi, Kiro). It mirrors the happy-cli AgentBackend -// pattern, translated to idiomatic Go. +// Gemini, Pi, Cursor, Kimi, Kiro, Antigravity). It mirrors the happy-cli +// AgentBackend pattern, translated to idiomatic Go. package agent import ( @@ -101,13 +101,13 @@ type Result struct { // Config configures a Backend instance. type Config struct { - ExecutablePath string // path to CLI binary (claude, codex, copilot, opencode, openclaw, hermes, gemini, pi, cursor, kimi, kiro-cli) + ExecutablePath string // path to CLI binary (claude, codex, copilot, opencode, openclaw, hermes, gemini, pi, cursor, kimi, kiro-cli, agy) Env map[string]string // extra environment variables Logger *slog.Logger } // New creates a Backend for the given agent type. -// Supported types: "claude", "codex", "copilot", "opencode", "openclaw", "hermes", "gemini", "pi", "cursor", "kimi", "kiro". +// Supported types: "claude", "codex", "copilot", "opencode", "openclaw", "hermes", "gemini", "pi", "cursor", "kimi", "kiro", "antigravity". func New(agentType string, cfg Config) (Backend, error) { if cfg.Logger == nil { cfg.Logger = slog.Default() @@ -136,8 +136,10 @@ func New(agentType string, cfg Config) (Backend, error) { return &kimiBackend{cfg: cfg}, nil case "kiro": return &kiroBackend{cfg: cfg}, nil + case "antigravity": + return &antigravityBackend{cfg: cfg}, nil default: - return nil, fmt.Errorf("unknown agent type: %q (supported: claude, codex, copilot, opencode, openclaw, hermes, gemini, pi, cursor, kimi, kiro)", agentType) + return nil, fmt.Errorf("unknown agent type: %q (supported: claude, codex, copilot, opencode, openclaw, hermes, gemini, pi, cursor, kimi, kiro, antigravity)", agentType) } } @@ -153,17 +155,18 @@ func DetectVersion(ctx context.Context, executablePath string) (string, error) { // environment variables are deliberately omitted so the string is a hint // about *what* users are extending, not a dump of the full command line. var launchHeaders = map[string]string{ - "claude": "claude (stream-json)", - "codex": "codex app-server", - "copilot": "copilot (json)", - "cursor": "cursor-agent (stream-json)", - "gemini": "gemini (stream-json)", - "hermes": "hermes acp", - "openclaw": "openclaw agent (json)", - "opencode": "opencode run (json)", - "pi": "pi (json mode)", - "kimi": "kimi acp", - "kiro": "kiro-cli acp", + "antigravity": "agy -p (print mode)", + "claude": "claude (stream-json)", + "codex": "codex app-server", + "copilot": "copilot (json)", + "cursor": "cursor-agent (stream-json)", + "gemini": "gemini (stream-json)", + "hermes": "hermes acp", + "kimi": "kimi acp", + "kiro": "kiro-cli acp", + "openclaw": "openclaw agent (json)", + "opencode": "opencode run (json)", + "pi": "pi (json mode)", } // LaunchHeader returns the user-visible launch skeleton for agentType, or an diff --git a/server/pkg/agent/agent_test.go b/server/pkg/agent/agent_test.go index 8324b35a7..32895ed76 100644 --- a/server/pkg/agent/agent_test.go +++ b/server/pkg/agent/agent_test.go @@ -38,6 +38,17 @@ func TestNewReturnsCopilotBackend(t *testing.T) { } } +func TestNewReturnsAntigravityBackend(t *testing.T) { + t.Parallel() + b, err := New("antigravity", Config{ExecutablePath: "/nonexistent/agy"}) + if err != nil { + t.Fatalf("New(antigravity) error: %v", err) + } + if _, ok := b.(*antigravityBackend); !ok { + t.Fatalf("expected *antigravityBackend, got %T", b) + } +} + func TestNewRejectsUnknownType(t *testing.T) { t.Parallel() _, err := New("gpt", Config{}) @@ -71,7 +82,7 @@ func TestLaunchHeaderCoversAllSupportedBackends(t *testing.T) { // runtime the daemon actually spawns. If a new backend is added, add an // entry to launchHeaders in agent.go and extend this list. supported := []string{ - "claude", "codex", "copilot", "cursor", "gemini", + "antigravity", "claude", "codex", "copilot", "cursor", "gemini", "hermes", "kimi", "kiro", "openclaw", "opencode", "pi", } for _, t_ := range supported { diff --git a/server/pkg/agent/antigravity.go b/server/pkg/agent/antigravity.go new file mode 100644 index 000000000..9f1d7457b --- /dev/null +++ b/server/pkg/agent/antigravity.go @@ -0,0 +1,243 @@ +package agent + +import ( + "bufio" + "context" + "fmt" + "log/slog" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" + "time" +) + +// antigravityBackend implements Backend by spawning Google's Antigravity CLI +// (`agy -p `) in non-interactive print mode. Unlike Claude / Codex / +// Cursor / Gemini, the Antigravity CLI does not expose a structured event +// stream — stdout is plain assistant text (intermediate "I will run X" lines +// and the final reply, all interleaved). The backend therefore streams stdout +// line-by-line as `MessageText` events and accumulates the same text as the +// final `Result.Output`. +// +// Session resumption uses `--conversation `. The conversation id is not +// emitted on stdout; we capture it by routing `--log-file` to a temp file and +// scanning its glog-formatted lines for the `conversation=` token that +// printmode.go logs at message-send time. +type antigravityBackend struct { + cfg Config +} + +func (b *antigravityBackend) Execute(ctx context.Context, prompt string, opts ExecOptions) (*Session, error) { + execPath := b.cfg.ExecutablePath + if execPath == "" { + execPath = "agy" + } + if _, err := exec.LookPath(execPath); err != nil { + return nil, fmt.Errorf("agy executable not found at %q: %w", execPath, err) + } + + timeout := opts.Timeout + if timeout == 0 { + timeout = 20 * time.Minute + } + runCtx, cancel := context.WithTimeout(ctx, timeout) + + logFile, err := os.CreateTemp("", "multica-agy-log-*.log") + if err != nil { + cancel() + return nil, fmt.Errorf("create agy log file: %w", err) + } + logPath := logFile.Name() + _ = logFile.Close() + + args := buildAntigravityArgs(prompt, logPath, timeout, opts, b.cfg.Logger) + + cmd := exec.CommandContext(runCtx, execPath, args...) + hideAgentWindow(cmd) + b.cfg.Logger.Info("agent command", "exec", execPath, "args", args) + cmd.WaitDelay = 10 * time.Second + if opts.Cwd != "" { + cmd.Dir = opts.Cwd + } + cmd.Env = buildEnv(b.cfg.Env) + + stdout, err := cmd.StdoutPipe() + if err != nil { + cancel() + _ = os.Remove(logPath) + return nil, fmt.Errorf("agy stdout pipe: %w", err) + } + stderrBuf := newStderrTail(newLogWriter(b.cfg.Logger, "[agy:stderr] "), agentStderrTailBytes) + cmd.Stderr = stderrBuf + + if err := cmd.Start(); err != nil { + cancel() + _ = os.Remove(logPath) + return nil, fmt.Errorf("start agy: %w", err) + } + + b.cfg.Logger.Info("agy started", "pid", cmd.Process.Pid, "cwd", opts.Cwd, "model", opts.Model) + + msgCh := make(chan Message, 256) + resCh := make(chan Result, 1) + + go func() { + <-runCtx.Done() + _ = stdout.Close() + }() + + go func() { + defer cancel() + defer close(msgCh) + defer close(resCh) + defer os.Remove(logPath) + + startTime := time.Now() + var output strings.Builder + finalStatus := "completed" + var finalError string + + scanner := bufio.NewScanner(stdout) + scanner.Buffer(make([]byte, 0, 1024*1024), 10*1024*1024) + + trySend(msgCh, Message{Type: MessageStatus, Status: "running"}) + + for scanner.Scan() { + line := scanner.Text() + if output.Len() > 0 { + output.WriteByte('\n') + } + output.WriteString(line) + if strings.TrimSpace(line) != "" { + trySend(msgCh, Message{Type: MessageText, Content: line}) + } + } + if err := scanner.Err(); err != nil { + b.cfg.Logger.Warn("agy stdout scanner error", "err", err) + } + + waitErr := cmd.Wait() + duration := time.Since(startTime) + + sessionID := readAntigravityConversationID(logPath) + + if runCtx.Err() == context.DeadlineExceeded { + finalStatus = "timeout" + finalError = fmt.Sprintf("agy timed out after %s", timeout) + } else if runCtx.Err() == context.Canceled { + finalStatus = "aborted" + finalError = "execution cancelled" + } else if waitErr != nil && finalStatus == "completed" { + finalStatus = "failed" + finalError = fmt.Sprintf("agy exited with error: %v", waitErr) + } + if finalError != "" { + finalError = withAgentStderr(finalError, "agy", stderrBuf.Tail()) + } + + b.cfg.Logger.Info("agy finished", "pid", cmd.Process.Pid, "status", finalStatus, "duration", duration.Round(time.Millisecond).String()) + + resCh <- Result{ + Status: finalStatus, + Output: output.String(), + Error: finalError, + DurationMs: duration.Milliseconds(), + SessionID: sessionID, + // The Antigravity CLI doesn't surface per-turn token usage today; + // leave Usage empty rather than report misleading zeros under a + // guessed model name. + Usage: map[string]TokenUsage{}, + } + }() + + return &Session{Messages: msgCh, Result: resCh}, nil +} + +// antigravityConversationIDRe matches the glog line printmode.go writes when +// the CLI dispatches the user's message — the only place in the log that +// reliably surfaces the conversation UUID for both fresh and resumed turns. +// +// Example: `I0528 13:36:23.318877 73304 printmode.go:130] Print mode: +// conversation=b8b263a4-4b2f-4339-acc9-78b248e2b606, sending message` +var antigravityConversationIDRe = regexp.MustCompile( + `conversation=([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})`, +) + +// readAntigravityConversationID scans the per-run log file for the +// conversation UUID. Best-effort: returns "" if the log file is missing, the +// CLI exited before dispatching, or the format changes upstream. +func readAntigravityConversationID(logPath string) string { + if logPath == "" { + return "" + } + data, err := os.ReadFile(logPath) + if err != nil { + return "" + } + matches := antigravityConversationIDRe.FindAllSubmatch(data, -1) + if len(matches) == 0 { + return "" + } + // The CLI logs the conversation id repeatedly during a turn (one entry + // per dispatched message, plus stream-update lines). Any non-empty UUID + // in the file resolves to the same conversation, so the last match wins + // — that's what `--conversation` should be pinned to next turn. + return string(matches[len(matches)-1][1]) +} + +// antigravityBlockedArgs are flags hardcoded by the daemon that must not be +// overridden by user-configured custom_args. Overriding these would break +// non-interactive operation or the daemon's session-resume bookkeeping. +var antigravityBlockedArgs = map[string]blockedArgMode{ + "-p": blockedWithValue, + "--print": blockedWithValue, + "--prompt": blockedWithValue, + "-i": blockedStandalone, // interactive mode would block the daemon + "--prompt-interactive": blockedStandalone, + "-c": blockedStandalone, // resume via --conversation, not --continue + "--continue": blockedStandalone, + "--conversation": blockedWithValue, // managed via ExecOptions.ResumeSessionID + "--print-timeout": blockedWithValue, + "--dangerously-skip-permissions": blockedStandalone, // always-on in daemon mode + "--log-file": blockedWithValue, // daemon needs it for session capture +} + +// buildAntigravityArgs assembles the argv for a one-shot agy invocation. +// +// agy -p --dangerously-skip-permissions --print-timeout +// --log-file [--conversation ] [--add-dir ] +// +// The Antigravity CLI exposes neither --model nor --system-prompt today; +// model selection lives in the user's Antigravity settings, and runtime +// instructions are delivered via AGENTS.md in the task workdir. +func buildAntigravityArgs(prompt, logPath string, timeout time.Duration, opts ExecOptions, logger *slog.Logger) []string { + args := []string{ + "-p", prompt, + "--dangerously-skip-permissions", + "--print-timeout", antigravityFormatTimeout(timeout), + "--log-file", logPath, + } + if opts.ResumeSessionID != "" { + args = append(args, "--conversation", opts.ResumeSessionID) + } + if opts.Cwd != "" { + args = append(args, "--add-dir", filepath.Clean(opts.Cwd)) + } + args = append(args, filterCustomArgs(opts.ExtraArgs, antigravityBlockedArgs, logger)...) + args = append(args, filterCustomArgs(opts.CustomArgs, antigravityBlockedArgs, logger)...) + return args +} + +// antigravityFormatTimeout renders a Go duration in the `ms` shape the +// agy CLI accepts (e.g. 20m0s). Sub-second timeouts round up to 1s so the CLI +// doesn't reject the flag. +func antigravityFormatTimeout(d time.Duration) string { + if d < time.Second { + d = time.Second + } + // time.Duration.String() already produces shapes like "20m0s" / "1h30m0s" + // that agy parses via Go's stdlib flag.Duration on the receiving side. + return d.String() +} diff --git a/server/pkg/agent/antigravity_test.go b/server/pkg/agent/antigravity_test.go new file mode 100644 index 000000000..5144d9813 --- /dev/null +++ b/server/pkg/agent/antigravity_test.go @@ -0,0 +1,151 @@ +package agent + +import ( + "io" + "log/slog" + "os" + "path/filepath" + "slices" + "strings" + "testing" + "time" +) + +func quietAntigravityLogger() *slog.Logger { + return slog.New(slog.NewTextHandler(io.Discard, nil)) +} + +func TestBuildAntigravityArgsBasic(t *testing.T) { + t.Parallel() + + args := buildAntigravityArgs( + "hello", + "/tmp/agy.log", + 20*time.Minute, + ExecOptions{Cwd: "/work"}, + quietAntigravityLogger(), + ) + + want := []string{ + "-p", "hello", + "--dangerously-skip-permissions", + "--print-timeout", "20m0s", + "--log-file", "/tmp/agy.log", + "--add-dir", "/work", + } + if !slices.Equal(args, want) { + t.Fatalf("buildAntigravityArgs basic mismatch\n got: %v\nwant: %v", args, want) + } +} + +func TestBuildAntigravityArgsResume(t *testing.T) { + t.Parallel() + + args := buildAntigravityArgs( + "continue", + "/tmp/agy.log", + 20*time.Minute, + ExecOptions{ResumeSessionID: "b8b263a4-4b2f-4339-acc9-78b248e2b606"}, + quietAntigravityLogger(), + ) + + joined := strings.Join(args, " ") + if !strings.Contains(joined, "--conversation b8b263a4-4b2f-4339-acc9-78b248e2b606") { + t.Fatalf("expected --conversation flag with id; got %v", args) + } +} + +func TestBuildAntigravityArgsFiltersBlockedCustomArgs(t *testing.T) { + t.Parallel() + + args := buildAntigravityArgs( + "go", + "/tmp/agy.log", + time.Minute, + ExecOptions{ + // Each blocked flag below must be stripped silently — the daemon + // owns these because they're required for non-interactive, + // resume-aware operation. + CustomArgs: []string{ + "-p", "hijacked-prompt", + "--continue", + "-c", + "--conversation", "bad-id", + "--dangerously-skip-permissions", + "--print-timeout", "1h", + "--log-file", "/elsewhere.log", + "--add-dir", "/extra", // user-added workspace dir should survive + }, + }, + quietAntigravityLogger(), + ) + + joined := strings.Join(args, " ") + // Prompt argument should appear exactly once — the daemon's, not the + // user's hijacked copy. + pCount := 0 + for _, a := range args { + if a == "-p" { + pCount++ + } + } + if pCount != 1 { + t.Errorf("expected exactly one -p flag, got args=%v", args) + } + if strings.Contains(joined, "hijacked-prompt") { + t.Errorf("custom -p value leaked through filter: %v", args) + } + if strings.Contains(joined, "bad-id") { + t.Errorf("custom --conversation value leaked through filter: %v", args) + } + if strings.Contains(joined, "/elsewhere.log") { + t.Errorf("custom --log-file value leaked through filter: %v", args) + } + if !strings.Contains(joined, "--add-dir /extra") { + t.Errorf("non-blocked --add-dir flag should pass through: %v", args) + } +} + +func TestAntigravityFormatTimeoutClampsSubSecond(t *testing.T) { + t.Parallel() + if got := antigravityFormatTimeout(0); got != "1s" { + t.Errorf("antigravityFormatTimeout(0) = %q, want 1s", got) + } + if got := antigravityFormatTimeout(20 * time.Minute); got != "20m0s" { + t.Errorf("antigravityFormatTimeout(20m) = %q, want 20m0s", got) + } +} + +func TestReadAntigravityConversationID(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + logPath := filepath.Join(dir, "agy.log") + + // Sample log content modelled on real agy glog output: the + // conversation= line is what printmode.go writes once per dispatch. + logBody := strings.Join([]string{ + `I0528 13:36:19.959748 73304 printmode.go:71] Print mode: starting (promptLength=18, model="", conversationID="")`, + `I0528 13:36:23.318877 73304 printmode.go:130] Print mode: conversation=b8b263a4-4b2f-4339-acc9-78b248e2b606, sending message`, + `I0528 13:36:23.318892 73304 server.go:1083] Sending user message to conversation b8b263a4-4b2f-4339-acc9-78b248e2b606 (items=1, media=0)`, + }, "\n") + if err := os.WriteFile(logPath, []byte(logBody), 0o644); err != nil { + t.Fatal(err) + } + + got := readAntigravityConversationID(logPath) + want := "b8b263a4-4b2f-4339-acc9-78b248e2b606" + if got != want { + t.Fatalf("readAntigravityConversationID = %q, want %q", got, want) + } +} + +func TestReadAntigravityConversationIDMissingFile(t *testing.T) { + t.Parallel() + if got := readAntigravityConversationID("/nonexistent/path"); got != "" { + t.Errorf("expected empty string for missing file, got %q", got) + } + if got := readAntigravityConversationID(""); got != "" { + t.Errorf("expected empty string for empty path, got %q", got) + } +} diff --git a/server/pkg/agent/models.go b/server/pkg/agent/models.go index 24d00d19b..28a5c4d8e 100644 --- a/server/pkg/agent/models.go +++ b/server/pkg/agent/models.go @@ -102,6 +102,13 @@ func ListModels(ctx context.Context, providerType, executablePath string) ([]Mod return models, nil case "gemini": return geminiStaticModels(), nil + case "antigravity": + // Antigravity CLI (`agy`) does not expose a `--model` flag today; + // model selection lives in the user's Antigravity settings and is + // communicated to the backend internally by the CLI itself. Return + // an empty catalog so the daemon's model_list endpoint succeeds + // without populating a misleading dropdown. + return []Model{}, nil case "cursor": return cachedDiscovery(providerType, func() ([]Model, error) { return discoverCursorModels(ctx, executablePath) @@ -140,14 +147,20 @@ func ListModels(ctx context.Context, providerType, executablePath string) ([]Mod } // ModelSelectionSupported reports whether setting `agent.model` has -// any effect for the given provider. Today every provider in the -// registry honours `opts.Model` end-to-end: Hermes routes it through -// the ACP `session/set_model` RPC before each prompt, which means -// the UI's dropdown choice is carried all the way down to the LLM -// call. The helper is retained so we can add a `return false` branch -// the next time a provider legitimately ignores model selection. +// any effect for the given provider. Most providers honour `opts.Model` +// end-to-end — Hermes routes it through the ACP `session/set_model` RPC +// before each prompt, Claude / Codex / Cursor / Gemini / Copilot / Kimi / +// Kiro / OpenCode / OpenClaw / Pi pass it via flag or session config. +// +// Antigravity is the lone exception: `agy` has no `--model` flag today, +// and the backend in antigravity.go deliberately drops opts.Model on the +// floor. Returning false here makes the UI render a disabled +// "Managed by runtime" picker instead of an empty dropdown plus a +// silently-ignored manual-entry field. func ModelSelectionSupported(providerType string) bool { - _ = providerType + if providerType == "antigravity" { + return false + } return true } diff --git a/server/pkg/agent/models_test.go b/server/pkg/agent/models_test.go index a5e53ca3d..12122bb79 100644 --- a/server/pkg/agent/models_test.go +++ b/server/pkg/agent/models_test.go @@ -562,6 +562,18 @@ func TestHermesModelSelectionSupported(t *testing.T) { } } +// TestAntigravityModelSelectionUnsupported pins that the antigravity +// provider reports model selection as unsupported: `agy` has no +// `--model` flag and antigravityBackend deliberately drops opts.Model on +// the floor, so the UI must render a disabled "Managed by runtime" +// picker rather than an empty dropdown that accepts a silently-ignored +// custom value. +func TestAntigravityModelSelectionUnsupported(t *testing.T) { + if ModelSelectionSupported("antigravity") { + t.Error("antigravity should not be model-selection-supported: agy has no --model flag") + } +} + func TestCachedDiscovery(t *testing.T) { calls := 0 fn := func() ([]Model, error) {