Skip to content

feat(agents): add Google Antigravity CLI (agy) support - #83

Open
darion-yaphet wants to merge 12 commits into
nexu-io:mainfrom
darion-yaphet:antigravity-cli
Open

feat(agents): add Google Antigravity CLI (agy) support#83
darion-yaphet wants to merge 12 commits into
nexu-io:mainfrom
darion-yaphet:antigravity-cli

Conversation

@darion-yaphet

Copy link
Copy Markdown

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

  • Detects agy on PATH via ANTIGRAVITY_BIN env override (same pattern as all other agents)
  • Invokes with -p --output-format stream-json --verbose --include-partial-messages --dangerously-skip-permissions, matching agy's Claude Code-compatible CLI shape
  • Parses NDJSON output using the existing Claude branch in parseLineWithState
  • Model picker includes Gemini 2.5 Pro/Flash/Flash-Lite plus third-party placeholders (OpenAI, Anthropic) — update model IDs once confirmed in Antigravity docs

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.

@lefarcen
lefarcen requested a review from mrcfps May 22, 2026 05:53
@lefarcen lefarcen added size/L Large change: 300-699 changed lines risk/medium Medium risk change type/feature Feature or new user-facing capability labels May 22, 2026

@mrcfps mrcfps left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread next/src/lib/agents/detect.ts Outdated
Comment thread next/src/lib/agents/__tests__/antigravity.test.ts Outdated

@lefarcen lefarcen left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 mrcfps left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread next/src/lib/agents/invoke.ts Outdated
// 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");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

🔁 Powered by Looper · runner=reviewer · agent=opencode · An autonomous AI dev team for your GitHub repos.

@mrcfps mrcfps left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

Comment thread next/src/lib/agents/invoke.ts Outdated
// 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");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

🔁 Powered by Looper · runner=reviewer · agent=opencode · An autonomous AI dev team for your GitHub repos.

@mrcfps mrcfps left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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 ❤️

🔁 Powered by Looper · runner=reviewer · agent=opencode · An autonomous AI dev team for your GitHub repos.

@darion-yaphet darion-yaphet changed the title [WIP] feat(agents): add Google Antigravity CLI (agy) support feat(agents): add Google Antigravity CLI (agy) support May 23, 2026
@darion-yaphet

Copy link
Copy Markdown
Author

@lefarcen @mrcfps Does anyone still care about this PR?

@lefarcen

lefarcen commented Jun 1, 2026

Copy link
Copy Markdown

Hey @darion-yaphet — yes, and I’m sorry this sat quiet after your fixes.

Current state: @mrcfps has approved the latest head (a18a37e), and I’m not asking for more code changes from you right now. I’ve resurfaced this for the merge decision so it does not keep sitting without a clear next step.

Thanks for sticking with the follow-ups here. ❤️

@PerishCode

Copy link
Copy Markdown
Contributor

@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.

@mrcfps mrcfps left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@lefarcen

lefarcen commented Aug 3, 2026

Copy link
Copy Markdown

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 argv prompt flow, plus updating the Antigravity design/implementation docs so they match the adapter that actually shipped in code. Once those are addressed, please push again and re-request review.

💡 To drive this PR to merge hands-free, paste this to your AI coding agent (Claude Code / Codex / opencode / Cursor …):
Take over nexu-io/html-anything#83 until it merges — read https://raw.githubusercontent.com/nexu-io/looper/main/skills/pr-takeover/SKILL.md and follow it.

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.
@lefarcen
lefarcen requested a review from mrcfps August 3, 2026 05:39

@mrcfps mrcfps left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

@lefarcen

lefarcen commented Aug 3, 2026

Copy link
Copy Markdown

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk/medium Medium risk change size/L Large change: 300-699 changed lines type/feature Feature or new user-facing capability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants