From 26aacf9e3cb8cc6ecfe96e033f86fd9e0c2f5fe5 Mon Sep 17 00:00:00 2001 From: taurus5650 Date: Sat, 3 Oct 2026 17:39:17 +0800 Subject: [PATCH] feat: add Antigravity agent adapter --- README.md | 5 +- README.zh-CN.md | 5 +- cli/src/__tests__/agents-invoke.test.ts | 24 +++ cli/src/agents-detect.ts | 10 + cli/src/agents-invoke.ts | 96 ++++++++++ next/src/lib/agents/__tests__/argv.test.ts | 207 ++++++++++++++++++++- next/src/lib/agents/argv.ts | 98 +++++++++- next/src/lib/agents/detect.ts | 13 +- next/src/lib/agents/invoke.ts | 2 +- 9 files changed, 452 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f3b04319..37452012 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@

Live page: open-design.ai/html-anything/ — overview, surface modes, and showcase before you clone.

-> **Markdown is the draft. HTML is what humans read. Your local agent writes it.** The agentic HTML editor — in the agentic era, you don't hand-edit docs anymore, so the output format should be what the reader actually wants: HTML. Local-first, zero API key, reuses the CLI session you already have logged in — **9 coding-agent CLIs** auto-detected on your `PATH` (Claude Code · Cursor Agent · Codex · Gemini CLI · GitHub Copilot CLI · OpenCode · Qwen Coder · Aider · IBM Bob), driven by **75 composable skill templates** across **9 deliverable surfaces** (magazine articles · keynote decks · résumés · posters · Xiaohongshu cards · tweet cards · web prototypes · data reports · Hyperframes videos). One-click export to WeChat / X / Zhihu, or download `.html` / `.png`. +> **Markdown is the draft. HTML is what humans read. Your local agent writes it.** The agentic HTML editor — in the agentic era, you don't hand-edit docs anymore, so the output format should be what the reader actually wants: HTML. Local-first, zero API key, reuses the CLI session you already have logged in — **10 coding-agent CLIs** auto-detected on your `PATH` (Claude Code · Cursor Agent · Codex · Gemini CLI · GitHub Copilot CLI · OpenCode · Qwen Coder · Aider · IBM Bob · Antigravity), driven by **75 composable skill templates** across **9 deliverable surfaces** (magazine articles · keynote decks · résumés · posters · Xiaohongshu cards · tweet cards · web prototypes · data reports · Hyperframes videos). One-click export to WeChat / X / Zhihu, or download `.html` / `.png`.

HTML Anything — the agentic HTML editor, on your laptop @@ -12,7 +12,7 @@

License - Agents + Agents Skills Export Quickstart @@ -203,6 +203,7 @@ On startup we scan `PATH` (including `~/.local/bin`, `~/.bun/bin`, `/opt/homebre | **Qwen Coder** | `qwen` | `qwen --yolo -` | | **Aider** | `aider` | `aider --no-pretty --no-stream --yes-always --message-file -` | | **IBM Bob** | `bob` | `bob --output-format stream-json --hide-intermediary-output` | +| **Antigravity** | `agy` | `agy --output-format stream-json --dangerously-skip-permissions --disable-slash-commands --effort medium -p ` | > The detection strategy and per-CLI adapter shape are borrowed directly from [`nexu-io/open-design`](https://github.com/nexu-io/open-design) and [`multica-ai/multica`](https://github.com/multica-ai/multica): one privileged process spawns CLIs, JSON-line is the wire protocol, every CLI gets a thin adapter in [`next/src/lib/agents/argv.ts`](next/src/lib/agents/argv.ts). diff --git a/README.zh-CN.md b/README.zh-CN.md index 0f72f0a9..84bb1067 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -4,7 +4,7 @@

项目主页: open-design.ai/html-anything/ —— 不用 clone 也能先看看 HTML Anything 长什么样、能干啥。

-> **Markdown 是草稿, HTML 才是给人读的成品 —— 让本地 agent 直接写 HTML。** Agent 时代的 HTML 编辑器 —— 既然你已经不亲手改文档、全都让 Claude 改了, 那 agent 的输出就该是读者真正想看的 HTML, 而不是中间态的 markdown。本地优先、零 API Key、复用你已经登录好的 CLI session —— **9 个 coding-agent CLI** 在 `PATH` 上自动识别(Claude Code · Cursor Agent · Codex · Gemini CLI · GitHub Copilot CLI · OpenCode · Qwen Coder · Aider · IBM Bob),驱动 **75 套 skill 模板** 和 **9 类可交付场景**(杂志文章 · Keynote PPT · 简历 · 海报 · 小红书 · 推特卡 · Web 原型 · 数据报告 · Hyperframes 视频)。一键复制到公众号 / 推特 / 知乎,或者下载 `.html` / `.png`。 +> **Markdown 是草稿, HTML 才是给人读的成品 —— 让本地 agent 直接写 HTML。** Agent 时代的 HTML 编辑器 —— 既然你已经不亲手改文档、全都让 Claude 改了, 那 agent 的输出就该是读者真正想看的 HTML, 而不是中间态的 markdown。本地优先、零 API Key、复用你已经登录好的 CLI session —— **10 个 coding-agent CLI** 在 `PATH` 上自动识别(Claude Code · Cursor Agent · Codex · Gemini CLI · GitHub Copilot CLI · OpenCode · Qwen Coder · Aider · IBM Bob · Antigravity),驱动 **75 套 skill 模板** 和 **9 类可交付场景**(杂志文章 · Keynote PPT · 简历 · 海报 · 小红书 · 推特卡 · Web 原型 · 数据报告 · Hyperframes 视频)。一键复制到公众号 / 推特 / 知乎,或者下载 `.html` / `.png`。

HTML Anything — agent 时代的 HTML 编辑器,在你的笔记本上 @@ -12,7 +12,7 @@

License - Agents + Agents Skills Export Quickstart @@ -203,6 +203,7 @@ pnpm -F @html-anything/e2e test | **Qwen Coder** | `qwen` | `qwen --yolo -` | | **Aider** | `aider` | `aider --no-pretty --no-stream --yes-always --message-file -` | | **IBM Bob** | `bob` | `bob --output-format stream-json --hide-intermediary-output` | +| **Antigravity** | `agy` | `agy --output-format stream-json --dangerously-skip-permissions --disable-slash-commands --effort medium -p ` | > 这一层的设计直接借鉴了 [`nexu-io/open-design`](https://github.com/nexu-io/open-design) 和 [`multica-ai/multica`](https://github.com/multica-ai/multica) 的 agent 检测策略:唯一被 spawn 子进程的进程是 server route,业务进程不直接 spawn;CLI 的 stdin / stdout 用 JSON-line 协议复用,每个 CLI 一个轻 adapter,统一在 [`next/src/lib/agents/argv.ts`](next/src/lib/agents/argv.ts)。 diff --git a/cli/src/__tests__/agents-invoke.test.ts b/cli/src/__tests__/agents-invoke.test.ts index 3cfad7d2..13198a14 100644 --- a/cli/src/__tests__/agents-invoke.test.ts +++ b/cli/src/__tests__/agents-invoke.test.ts @@ -527,4 +527,28 @@ describe("invokeAgent", () => { }); }); + + describe("antigravity", () => { + it("passes the prompt right after -p and parses agy step_update/result lines", async () => { + const lines = [ + JSON.stringify({ event: "init", conversation_id: "conv-1", init: { cwd: "/tmp" } }), + JSON.stringify({ event: "step_update", step_update: { text_delta: "

hi

" } }), + JSON.stringify({ event: "result", result: { status: "SUCCESS", response: "

hi

" } }), + ].join("\n") + "\n"; + + const events = await driveInvoke( + { agent: "antigravity", prompt: "make html", binOverride: BIN_OVERRIDE }, + lines, + 0, + ); + + const argv = mockSpawn.mock.calls.at(-1)![1] as string[]; + expect(argv.slice(-2)).toEqual(["-p", "make html"]); + expect(argv).toContain("--disable-slash-commands"); + + // result.response must not duplicate the streamed delta + const deltas = events.filter((e) => e.type === "delta"); + expect(deltas).toEqual([{ type: "delta", text: "

hi

" }]); + }); + }); }); diff --git a/cli/src/agents-detect.ts b/cli/src/agents-detect.ts index f4813b43..0efcc21c 100644 --- a/cli/src/agents-detect.ts +++ b/cli/src/agents-detect.ts @@ -89,6 +89,16 @@ export const AGENTS: AgentDef[] = [ { id: "gpt-5", label: "gpt-5" }, ], }, + { + id: "antigravity", + label: "Antigravity", + bin: "agy", + envOverride: "ANTIGRAVITY_BIN", + vendor: "Google", + protocol: "argv", + // Model ids come from the user's agy config; no curated list yet. + fallbackModels: [DEFAULT_MODEL], + }, { id: "gemini", label: "Gemini CLI", diff --git a/cli/src/agents-invoke.ts b/cli/src/agents-invoke.ts index 2416e634..f8994631 100644 --- a/cli/src/agents-invoke.ts +++ b/cli/src/agents-invoke.ts @@ -120,6 +120,20 @@ function buildArgv(agent: string, opts: AgentArgvOpts = {}): string[] { "--trust", ...(model ? ["--model", model] : []), ]; + case "antigravity": + // `-p` must stay last: invoke appends the prompt right after it + // (`protocol: "argv"`). Effort is pinned so runs don't depend on the + // user's local agy default. + return [ + "--output-format", + "stream-json", + "--dangerously-skip-permissions", + "--disable-slash-commands", + "--effort", + "medium", + ...(model ? ["--model", model] : []), + "-p", + ]; case "gemini": return [ "--output-format", @@ -209,6 +223,7 @@ function rescueHtmlFromToolUse( name !== "createfile" && name !== "writefile" && name !== "write_file" && + name !== "write_to_file" && name !== "filewrite" ) continue; @@ -229,6 +244,20 @@ function rescueHtmlFromToolUse( return parts.join(""); } +/** agy reports `cache_read_tokens`; the UI reads Claude-style usage keys. */ +function agyUsage(u: Record): AgentParse { + return { + kind: "meta", + key: "usage", + value: { + input_tokens: u.input_tokens, + output_tokens: u.output_tokens, + cache_read_input_tokens: u.cache_read_tokens ?? 0, + cache_creation_input_tokens: 0, + }, + }; +} + function parseLineWithState(agent: string, line: string, state: ParseState): AgentParse[] { const trimmed = line.trim(); if (!trimmed) return []; @@ -246,6 +275,73 @@ function parseLineWithState(agent: string, line: string, state: ParseState): Age if (!parsed || typeof parsed !== "object") return []; const obj = parsed as Record; const out: AgentParse[] = []; + if (agent === "antigravity") { + // agy stream-json: one envelope per line, keyed by `event` + // (init / step_update / result). + if (obj.event === "init") { + if (typeof obj.conversation_id === "string") { + out.push({ kind: "meta", key: "session", value: obj.conversation_id }); + } + const initObj = obj.init as Record | undefined; + if (typeof initObj?.cwd === "string") { + out.push({ kind: "meta", key: "cwd", value: initObj.cwd }); + } + } + + const step = obj.step_update as Record | undefined; + if (step && typeof step === "object") { + // agy writes files via `write_to_file { TargetFile, CodeContent }`. + // Map it onto the shared tool_use shape so the rescue rules live in + // one place. + const toolInfo = step.tool_info as + | { name?: string; parameters?: Record } + | undefined; + const params = toolInfo?.parameters; + if (params && (toolInfo?.name ?? step.tool_name) === "write_to_file") { + const toolHtml = rescueHtmlFromToolUse([ + { + type: "tool_use", + name: "write_to_file", + input: { file_path: params.TargetFile, content: params.CodeContent }, + }, + ]); + if (toolHtml) { + out.push({ kind: "html", text: toolHtml }); + state.sawStreamEventText = true; + } + } + if (typeof step.text_delta === "string" && step.text_delta) { + state.sawStreamEventText = true; + out.push({ kind: "delta", text: step.text_delta }); + } + if (typeof step.thinking_delta === "string" && step.thinking_delta) { + out.push({ kind: "meta", key: "thinking", value: step.thinking_delta }); + } + if (step.usage && typeof step.usage === "object") { + out.push(agyUsage(step.usage as Record)); + } + } + + const result = obj.result as Record | undefined; + if (result && typeof result === "object") { + if (!state.sawStreamEventText && typeof result.response === "string" && result.response) { + out.push({ kind: "delta", text: result.response }); + } + if (result.usage && typeof result.usage === "object") { + out.push(agyUsage(result.usage as Record)); + } + if (typeof result.duration_seconds === "number") { + out.push({ + kind: "meta", + key: "duration_ms", + value: Math.round(result.duration_seconds * 1000), + }); + } + if (typeof result.status === "string") { + out.push({ kind: "meta", key: "result", value: result.status }); + } + } + } if (agent === "claude") { if (obj.type === "system" && obj.subtype === "init") { diff --git a/next/src/lib/agents/__tests__/argv.test.ts b/next/src/lib/agents/__tests__/argv.test.ts index 449277d9..6e9c5e63 100644 --- a/next/src/lib/agents/__tests__/argv.test.ts +++ b/next/src/lib/agents/__tests__/argv.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { parseLine, makeParser } from "../argv"; +import { buildArgv, parseLine, makeParser } from "../argv"; describe("parseLine opencode", () => { it("extracts text from nested part payload", () => { @@ -219,3 +219,208 @@ describe("parseLine bob", () => { }); }); +describe("parseLine antigravity", () => { + it("extracts session and cwd from init event", () => { + const line = JSON.stringify({ + event: "init", + conversation_id: "conv-123", + init: { + cwd: "/Users/test/project", + tools: ["write_to_file"], + }, + }); + + expect(parseLine("antigravity", line)).toEqual([ + { kind: "meta", key: "session", value: "conv-123" }, + { kind: "meta", key: "cwd", value: "/Users/test/project" }, + ]); + }); + + it("extracts streamed text delta from step_update event", () => { + const line = JSON.stringify({ + event: "step_update", + step_update: { + conversation_id: "conv-123", + step_index: 1, + state: "ACTIVE", + step_type: "agent_response", + text_delta: "
Hello world
", + }, + }); + + expect(parseLine("antigravity", line)).toEqual([ + { kind: "delta", text: "
Hello world
" }, + ]); + }); + + it("extracts usage and text delta from step_update done event", () => { + const line = JSON.stringify({ + event: "step_update", + step_update: { + conversation_id: "conv-123", + step_index: 1, + state: "DONE", + step_type: "agent_response", + text_delta: "\n", + duration_seconds: 2.5, + usage: { + input_tokens: 1000, + output_tokens: 200, + thinking_tokens: 50, + cache_read_tokens: 100, + total_tokens: 1200, + }, + }, + }); + + expect(parseLine("antigravity", line)).toEqual([ + { kind: "delta", text: "\n" }, + { + kind: "meta", + key: "usage", + value: { + input_tokens: 1000, + output_tokens: 200, + cache_read_input_tokens: 100, + cache_creation_input_tokens: 0, + }, + }, + ]); + }); + + it("rescues HTML from write_to_file tool step_update", () => { + const line = JSON.stringify({ + event: "step_update", + step_update: { + conversation_id: "conv-123", + step_index: 2, + state: "ACTIVE", + step_type: "tool", + tool_name: "write_to_file", + tool_info: { + name: "write_to_file", + parameters: { + TargetFile: "index.html", + CodeContent: "resuced", + }, + }, + }, + }); + + expect(parseLine("antigravity", line)).toEqual([ + { + kind: "html", + text: "resuced", + }, + ]); + }); + + it("handles result event with response fallback when no prior delta was seen", () => { + const line = JSON.stringify({ + event: "result", + result: { + conversation_id: "conv-123", + status: "SUCCESS", + response: "Complete", + duration_seconds: 3.2, + usage: { + input_tokens: 500, + output_tokens: 100, + cache_read_tokens: 0, + }, + }, + }); + + const parser = makeParser("antigravity"); + expect(parser(line)).toEqual([ + { kind: "delta", text: "Complete" }, + { + kind: "meta", + key: "usage", + value: { + input_tokens: 500, + output_tokens: 100, + cache_read_input_tokens: 0, + cache_creation_input_tokens: 0, + }, + }, + { kind: "meta", key: "duration_ms", value: 3200 }, + { kind: "meta", key: "result", value: "SUCCESS" }, + ]); + }); + + it("does not duplicate response from result event if text deltas were already streamed", () => { + const parser = makeParser("antigravity"); + const deltaLine = JSON.stringify({ + event: "step_update", + step_update: { + step_type: "agent_response", + text_delta: "Hello", + }, + }); + const resultLine = JSON.stringify({ + event: "result", + result: { + status: "SUCCESS", + response: "Hello", + duration_seconds: 1.0, + }, + }); + + expect(parser(deltaLine)).toEqual([{ kind: "delta", text: "Hello" }]); + expect(parser(resultLine)).toEqual([ + { kind: "meta", key: "duration_ms", value: 1000 }, + { kind: "meta", key: "result", value: "SUCCESS" }, + ]); + }); +}); + +describe("buildArgv antigravity", () => { + it("keeps -p last and always pins effort", () => { + expect(buildArgv("antigravity")).toEqual([ + "--output-format", + "stream-json", + "--dangerously-skip-permissions", + "--disable-slash-commands", + "--effort", + "medium", + "-p", + ]); + }); + + it("adds --model before -p when a model is picked", () => { + const argv = buildArgv("antigravity", { model: "some-model" }); + expect(argv.slice(-3)).toEqual(["--model", "some-model", "-p"]); + }); +}); + +describe("parseLine antigravity edge cases", () => { + it("emits thinking_delta as thinking meta", () => { + const line = JSON.stringify({ + event: "step_update", + step_update: { step_type: "agent_response", thinking_delta: "planning" }, + }); + expect(parseLine("antigravity", line)).toEqual([ + { kind: "meta", key: "thinking", value: "planning" }, + ]); + }); + + it("does not rescue write_to_file content for non-HTML targets", () => { + const line = JSON.stringify({ + event: "step_update", + step_update: { + step_type: "tool", + tool_info: { + name: "write_to_file", + parameters: { TargetFile: "notes.md", CodeContent: "# notes" }, + }, + }, + }); + expect(parseLine("antigravity", line)).toEqual([]); + }); + + it("ignores Claude-shaped lines it does not understand", () => { + const line = JSON.stringify({ type: "assistant", text: "

not agy

", content: "x" }); + expect(parseLine("antigravity", line)).toEqual([]); + }); +}); diff --git a/next/src/lib/agents/argv.ts b/next/src/lib/agents/argv.ts index f92b6b59..fe2046b9 100644 --- a/next/src/lib/agents/argv.ts +++ b/next/src/lib/agents/argv.ts @@ -15,7 +15,7 @@ export class UnsupportedAgentProtocolError extends Error { constructor(public readonly agent: string, public readonly protocol: string) { super( `${agent} uses the ${protocol} protocol, which is not yet wired up in this build. ` + - `Pick one of: claude / codex / cursor-agent / gemini / copilot / opencode / qwen / qoder / codewhale / deepseek-tui / aider.`, + `Pick one of: claude / codex / cursor-agent / gemini / antigravity / copilot / opencode / qwen / qoder / codewhale / deepseek-tui / aider.`, ); } } @@ -69,6 +69,20 @@ export function buildArgv(agent: string, _opts: AgentArgvOpts = {}): string[] { "--trust", ...(model ? ["--model", model] : []), ]; + case "antigravity": + // `-p` must stay last: invoke appends the prompt right after it + // (`protocol: "argv"`). Effort is pinned so runs don't depend on the + // user's local agy default. + return [ + "--output-format", + "stream-json", + "--dangerously-skip-permissions", + "--disable-slash-commands", + "--effort", + "medium", + ...(model ? ["--model", model] : []), + "-p", + ]; case "gemini": return [ "--output-format", @@ -217,6 +231,7 @@ function rescueHtmlFromToolUse( name !== "createfile" && name !== "writefile" && name !== "write_file" && + name !== "write_to_file" && name !== "filewrite" ) continue; @@ -239,6 +254,20 @@ function rescueHtmlFromToolUse( return parts.join(""); } +/** agy reports `cache_read_tokens`; the UI reads Claude-style usage keys. */ +function agyUsage(u: Record): AgentParse { + return { + kind: "meta", + key: "usage", + value: { + input_tokens: u.input_tokens, + output_tokens: u.output_tokens, + cache_read_input_tokens: u.cache_read_tokens ?? 0, + cache_creation_input_tokens: 0, + }, + }; +} + function parseLineWithState(agent: string, line: string, state: ParseState): AgentParse[] { const trimmed = line.trim(); if (!trimmed) return []; @@ -258,6 +287,73 @@ function parseLineWithState(agent: string, line: string, state: ParseState): Age if (!parsed || typeof parsed !== "object") return []; const obj = parsed as Record; const out: AgentParse[] = []; + if (agent === "antigravity") { + // agy stream-json: one envelope per line, keyed by `event` + // (init / step_update / result). + if (obj.event === "init") { + if (typeof obj.conversation_id === "string") { + out.push({ kind: "meta", key: "session", value: obj.conversation_id }); + } + const initObj = obj.init as Record | undefined; + if (typeof initObj?.cwd === "string") { + out.push({ kind: "meta", key: "cwd", value: initObj.cwd }); + } + } + + const step = obj.step_update as Record | undefined; + if (step && typeof step === "object") { + // agy writes files via `write_to_file { TargetFile, CodeContent }`. + // Map it onto the shared tool_use shape so the rescue rules live in + // one place. + const toolInfo = step.tool_info as + | { name?: string; parameters?: Record } + | undefined; + const params = toolInfo?.parameters; + if (params && (toolInfo?.name ?? step.tool_name) === "write_to_file") { + const toolHtml = rescueHtmlFromToolUse([ + { + type: "tool_use", + name: "write_to_file", + input: { file_path: params.TargetFile, content: params.CodeContent }, + }, + ]); + if (toolHtml) { + out.push({ kind: "html", text: toolHtml }); + state.sawStreamEventText = true; + } + } + if (typeof step.text_delta === "string" && step.text_delta) { + state.sawStreamEventText = true; + out.push({ kind: "delta", text: step.text_delta }); + } + if (typeof step.thinking_delta === "string" && step.thinking_delta) { + out.push({ kind: "meta", key: "thinking", value: step.thinking_delta }); + } + if (step.usage && typeof step.usage === "object") { + out.push(agyUsage(step.usage as Record)); + } + } + + const result = obj.result as Record | undefined; + if (result && typeof result === "object") { + if (!state.sawStreamEventText && typeof result.response === "string" && result.response) { + out.push({ kind: "delta", text: result.response }); + } + if (result.usage && typeof result.usage === "object") { + out.push(agyUsage(result.usage as Record)); + } + if (typeof result.duration_seconds === "number") { + out.push({ + kind: "meta", + key: "duration_ms", + value: Math.round(result.duration_seconds * 1000), + }); + } + if (typeof result.status === "string") { + out.push({ kind: "meta", key: "result", value: result.status }); + } + } + } if (agent === "claude") { // Init / system metadata diff --git a/next/src/lib/agents/detect.ts b/next/src/lib/agents/detect.ts index f98123e5..d70eac26 100644 --- a/next/src/lib/agents/detect.ts +++ b/next/src/lib/agents/detect.ts @@ -6,7 +6,8 @@ import path, { delimiter, join } from "node:path"; * Per-agent invocation protocol. Determines what `invokeAgent` does with the * prompt and how it parses output: * - "stdin" : pipe prompt → child stdin, parse stdout via parseLine - * - "argv" : pass prompt as positional argv (deepseek-tui), parse stdout as plain + * - "argv" : pass prompt as trailing argv (deepseek-tui: plain stdout; + * antigravity: stream-json, parsed via parseLine) * - "argv-message" : prompt goes via `--message ` (openclaw); stdout is * a single multi-line JSON document (not ndjson), parsed * after the child closes. @@ -117,6 +118,16 @@ export const AGENTS: AgentDef[] = [ { id: "gpt-5", label: "gpt-5" }, ], }, + { + id: "antigravity", + label: "Antigravity", + bin: "agy", + envOverride: "ANTIGRAVITY_BIN", + vendor: "Google", + protocol: "argv", + // Model ids come from the user's agy config; no curated list yet. + fallbackModels: [DEFAULT_MODEL], + }, { id: "gemini", label: "Gemini CLI", diff --git a/next/src/lib/agents/invoke.ts b/next/src/lib/agents/invoke.ts index 22d11d91..300c6801 100644 --- a/next/src/lib/agents/invoke.ts +++ b/next/src/lib/agents/invoke.ts @@ -150,7 +150,7 @@ export function invokeAgent(opts: InvokeOpts): ReadableStream { safeClose(); return; } - // `protocol: "argv"` adapters (deepseek-tui today) take the prompt as a + // `protocol: "argv"` adapters (deepseek-tui, antigravity) take the prompt as a // trailing positional arg rather than reading from stdin. if (promptViaArgv) argv = [...argv, opts.prompt]; // `protocol: "argv-message"` (openclaw today) wants the prompt under