feat(agents): add Google Antigravity CLI (agy) support - #83
feat(agents): add Google Antigravity CLI (agy) support#83darion-yaphet wants to merge 12 commits into
Conversation
mrcfps
left a comment
There was a problem hiding this comment.
Thanks for shipping the agy integration and the convert error-state fix — I left two follow-ups to make the new adapter safer to ship.
🔁 Powered by Looper · runner=reviewer · agent=opencode · An autonomous AI dev team for your GitHub repos.
lefarcen
left a comment
There was a problem hiding this comment.
Hey @darion-yaphet! 👋 The use-convert.ts bug fix is genuinely well-targeted — tracking hadError in the SSE loop and guarding commitBaseFor behind it is exactly the right shape here. The silent-failure pattern (stream closes cleanly, UI resets as if nothing happened, error lands only in the log tab) is the worst kind to debug, and this plugs it properly.
The adapter shape also looks correct: routing agy through claude || antigravity in parseLineWithState, using the Claude Code-compatible argv (-p, --output-format stream-json, --verbose, --include-partial-messages, --dangerously-skip-permissions), and following the same AgentDef pattern as the other agents (envOverride, fallbackModels, etc.) — all consistent with the existing codebase conventions.
Looper's two inline notes already cover what needs to land before this merges: the unverified openai/gpt-5 / anthropic/claude-sonnet-4-6 model IDs in the live picker (users picking those will hit a runtime failure), and the missing buildArgv("antigravity") argv assertion so a future refactor can't silently reroute through the wrong branch.
One small nit from me: the UnsupportedAgentProtocolError constructor message still lists the pre-antigravity agents (claude / codex / cursor-agent / gemini / copilot / opencode / qwen / qoder / deepseek / aider) — worth adding antigravity there so the error string stays accurate.
Happy to do a full pass once the placeholder model IDs are resolved and the WIP tag comes off. ❤️
mrcfps
left a comment
There was a problem hiding this comment.
Thanks for the follow-up here — the agy adapter is much closer, but I found two correctness issues in the current head that still look merge-blocking.
🔁 Powered by Looper · runner=reviewer · agent=opencode · An autonomous AI dev team for your GitHub repos.| // record the just-finished (content, html) as the new diff-edit baseline | ||
| // so the user's next edit goes through diff mode instead of full regen | ||
| useStore.getState().commitBaseFor(taskId); | ||
| useStore.getState().setStatusFor(taskId, hadError ? "error" : "done"); |
There was a problem hiding this comment.
This still only flips hadError on an explicit SSE error event, but invokeAgent() always emits a done event from child.on("close") regardless of the exit code. So an agent that prints an auth/config failure to stderr and exits with code !== 0 will still leave hadError === false, and this branch will mark the run as done and commit the diff baseline even though the convert failed. That means the silent-error bug called out in the PR description is still reproducible for the common non-zero-exit path. Please treat a non-zero done.code as failure here (or emit an error event before done in invoke.ts) so unsuccessful agent exits always land in error status and skip commitBaseFor.
mrcfps
left a comment
There was a problem hiding this comment.
@darion-yaphet Thanks for pushing the follow-up fixes here — I re-checked the latest head and there are still two correctness issues in the changed paths that look merge-blocking.
🔁 Powered by Looper · runner=reviewer · agent=opencode · An autonomous AI dev team for your GitHub repos.| // record the just-finished (content, html) as the new diff-edit baseline | ||
| // so the user's next edit goes through diff mode instead of full regen | ||
| useStore.getState().commitBaseFor(taskId); | ||
| useStore.getState().setStatusFor(taskId, hadError ? "error" : "done"); |
There was a problem hiding this comment.
hadError only flips on an explicit SSE error event, but invokeAgent() still emits done for every child exit and never turns code !== 0 into an error event. That means an auth/config failure that writes to stderr and exits non-zero still leaves hadError === false, so this block marks the run as done and commits the diff baseline even though the convert failed. This is the same silent-failure path the PR description is trying to close. Please also treat a non-zero done.code as failure here (or emit an error before done in invoke.ts) so unsuccessful agent exits always end in error and skip commitBaseFor.
mrcfps
left a comment
There was a problem hiding this comment.
@darion-yaphet Re-checked the latest head and the two earlier blockers look resolved: useConvert() now treats non-zero/null agent exits as failures before committing the diff baseline, and the duplicate Antigravity close-flush is gone. I also spot-checked the current protocol: "argv" / buildArgv("antigravity") wiring plus the updated adapter tests, and I do not see any new actionable issues in the changed ranges. I could not run the local pnpm checks in this prepared worktree because node_modules is not installed here, but the code changes themselves look ready to me. Thanks for working through the follow-ups on this one ❤️
|
Hey @darion-yaphet — yes, and I’m sorry this sat quiet after your fixes. Current state: @mrcfps has approved the latest head ( Thanks for sticking with the follow-ups here. ❤️ |
|
@darion-yaphet I'm holding off on generating review comments for #83 because this pull request has merge conflicts right now. Please resolve the conflicts with main and push the updated branch. Once that's done, request or wait for the review to run again and I'll take another look. 🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos. |
When an agent binary is not installed (or any other agent-side error
occurs), the SSE stream emits an 'error' event then closes normally.
Previously the post-stream handler unconditionally called
setStatusFor('done'), masking the error — users saw the generate button
reset with no visible feedback, while the error was silently written
only to the log tab.
Track whether an error event was received (hadError) and propagate it
to the final status. Also guard commitBaseFor so a failed convert does
not update the diff-edit baseline with empty HTML.
agy --help reveals it uses -p/--print and --dangerously-skip-permissions, identical to Claude Code. The previous implementation incorrectly used Gemini-style flags (--output-format stream-json --yolo) and the Gemini parser branch. Both produced no output since agy does not recognise those flags. Switch buildArgv to Claude Code flags and move parser to the claude branch so stream-json NDJSON events are correctly parsed.
agy --help exposes no --model flag; the TUI confirms only Gemini 3.5 Flash is in use. The previous openai/gpt-5 and anthropic/claude-sonnet-4-6 entries were unverified placeholders that would cause every convert run to fail if selected. Strip to DEFAULT_MODEL only until Antigravity docs confirm accepted --model strings.
agy --print <prompt> takes the prompt as a positional argument and emits plain UTF-8 text, not Claude Code's stream-json format. The old argv passed -p --output-format stream-json --verbose --include-partial-messages, which caused agy to treat --output-format as the argument to -p and exit immediately (exit=0) with no output, leaving the UI with an empty result. - detect.ts: set protocol "argv" so invoke.ts appends the prompt after argv - argv.ts buildArgv: replace Claude Code flags with --dangerously-skip-permissions --print - argv.ts parser: move antigravity to the plain-text branch (aider/deepseek style) - invoke.ts: flush remaining stdout buffer as a delta on close for antigravity - test: rewrite tests to cover correct argv shape and plain-text parsing
…ed list The error string enumerated supported agents but was missing antigravity, which was added in the previous commit. Keeping this list in sync avoids misleading users who encounter the error into thinking antigravity is unsupported.
hadError was only set on an explicit SSE error event, so an agent that
printed an auth/config failure to stderr and exited with code != 0 still
reached setStatusFor("done") and commitBaseFor — silently recording a
failed (empty) HTML as the diff-edit baseline.
Treat any done event whose code is not 0 (including null, i.e. signal
termination) as a failure so unsuccessful agent exits always land in
error status and skip the baseline commit.
a18a37e to
397b147
Compare
mrcfps
left a comment
There was a problem hiding this comment.
@darion-yaphet Thanks for continuing to work through the Antigravity integration and convert error handling. The exit-status fix is now correct and the local checks pass, but I found one Windows command-injection path that should be fixed before merge.
Documentation follow-up — Google Antigravity CLI Integration Design, Decision and Architecture sections, plus the companion Implementation Plan: these documents still prescribe Gemini JSON streaming, standard-input prompt delivery, no process-runner change, and multiple model choices. The final code instead sends a positional prompt, consumes plain text, changes the close handler, and leaves model selection to the command-line tool. Since the design is labeled Approved, please revise both documents so their decision, architecture, command, parser, tests, risks, and file summaries match the final adapter, or mark them superseded and link to an accurate replacement.
🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.| vendor: "Google", | ||
| // agy --print <prompt> takes the prompt as a required argument, not via | ||
| // stdin. Use "argv" protocol so invoke.ts appends opts.prompt after argv. | ||
| protocol: "argv", |
There was a problem hiding this comment.
Please keep the prompt out of a shell-parsed argv on Windows. This new argv protocol makes invokeAgent append the entire user prompt to the process arguments, while that same invocation path sets shell: true on win32. Node does not escape those arguments before handing the concatenated command to the shell (Node 24 now emits DEP0190 for this exact pattern), so document content containing cmd metacharacters such as &, |, redirection operators, or environment expansions can be interpreted as commands instead of arriving literally at agy. That turns an imported or pasted prompt into local command execution for Windows users. Launch the official agy executable with shell: false on Windows (resolving the native executable rather than a command shim), or use a supported non-shell prompt transport such as stdin. Please also add a Windows-focused regression case proving shell metacharacters arrive verbatim and cannot start a second command.
🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.|
Hey @darion-yaphet — the latest blocker is now pretty focused. @mrcfps's current review is the one to work from on this head: the remaining must-fix items are the Windows command-injection path in the 💡 To drive this PR to merge hands-free, paste this to your AI coding agent (Claude Code / Codex / opencode / Cursor …): |
Antigravity sends prompts through argv, which is unsafe when the shared Windows runner launches a command shell. Command-line prompt protocols now require a direct native executable, while declared model IDs are validated before argv construction so stdin adapters keep their existing shim compatibility without accepting arbitrary shell syntax. The Antigravity design and implementation records now describe the adapter that shipped. Constraint: agy --print requires a positional prompt, while Windows .cmd/.bat shims require a shell to launch. Rejected: Escape free-form prompts for cmd.exe | quoting and expansion rules are fragile and do not provide a durable safety boundary. Confidence: high Scope-risk: moderate Reversibility: clean Directive: Any protocol that carries untrusted data in argv must use a shell-free launch path or validate the value against a closed set. Tested: Node 24 Vitest (190 tests), Next and E2E typechecks, workspace guard, Windows spawn-spec smoke test. Not-tested: Execution against a real Windows native executable and command shim.
mrcfps
left a comment
There was a problem hiding this comment.
@darion-yaphet Thanks for the careful follow-up on this integration. I re-checked all eight changed files on the current head: the Antigravity adapter now uses the verified positional-prompt/plain-text contract, non-zero and null exits remain errors before the diff baseline is committed, and the latest Windows path keeps prompt text out of shell parsing while rejecting unsafe command shims. The updated design and implementation records match that shipped behavior. The repository guard, app and e2e typechecks, all 190 unit tests, and the production build pass locally. The earlier blocker is resolved, and this looks ready to merge—thank you for sticking with the review feedback. ❤️
🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.|
Hey @darion-yaphet — good news: @mrcfps has re-checked the current head and cleared the earlier blocker. From the review side, I’m not asking for any more code changes from you right now. Thanks for sticking with the iteration here. |
Description:
Adds agy (Google Antigravity CLI) as a supported agent in html-anything, alongside a bug fix for silent error handling in the convert pipeline.
Agent integration
Bug fix — silent convert errors
Previously, when an agent binary was not installed (or any other agent-side error occurred), the SSE stream emitted an error event and closed normally. The
post-stream handler unconditionally called setStatusFor("done"), masking the error — users saw the generate button reset with no feedback while the error was silently
written only to the log tab.
Fix: track hadError in the SSE loop and propagate it to the final status. Also guard commitBaseFor so a failed convert does not overwrite the diff-edit baseline with
empty HTML.
Test coverage
6 new unit tests: stream_event delta, assistant body fallback, dedup, non-JSON noise, AgentDef id/bin/model integrity.