Skip to content

feat: report Antigravity CLI sessions to lich - #21

Merged
omartelo merged 4 commits into
mainfrom
feat/antigravity
Aug 24, 2026
Merged

omartelo merged 4 commits into
mainfrom
feat/antigravity

Conversation

@omartelo

Copy link
Copy Markdown
Collaborator

Summary

Adds support for Google Antigravity CLI as a sixth supported harness.

Key Changes:

  • Hook Configuration (hooks/antigravity-hooks.json): Configured lifecycle hooks for Antigravity CLI:
    • PreInvocation: Reports session start (report-session-start.sh antigravity) and busy state (report-state.sh busy).
    • PreToolUse: Reports currently executing tool with details (report-tool.sh).
    • PostToolUse: Refreshes git status on write tools (write_to_file|replace_file_content|run_command).
    • Stop: Reports done state and extracts the session title (report-title.sh).
  • Hook Scripts:
    • report-session-start.sh: Added .conversationId payload support.
    • report-tool.sh: Added .toolCall.name payload support.
    • detail.jq: Supports Antigravity workspace roots (.workspacePaths[0]) and PascalCase tool argument fields (CommandLine, TargetFile, AbsolutePath, SearchPath, Query, Url, etc.).
    • report-title.sh: Reads first USER_INPUT line from transcriptPath and trims <USER_REQUEST> block tags.
  • Tests & Docs:
    • Added Antigravity test cases to tests/hooks.test.mjs and fixtures (all 182 tests passing).
    • Updated documentation across README, docs/providers.md, docs/session-*.md, and CHANGELOG.md.

The registration was written against Claude Code's assumptions, so none of it
ran. Five things were wrong, each silent:

- `${CLAUDE_PLUGIN_ROOT}` — Antigravity sets no plugin-root variable. It runs a
  hook through `sh -c` with the working directory set to the folder holding
  hooks.json, so the paths are relative to that. Under the old registration
  every hook ran `/hooks/<script>` and exited 127, on every event.
- No manifest — an Antigravity plugin is `plugins/<name>/plugin.json` with its
  `hooks.json` and `skills/` beside it. Neither name is configurable, so
  `hooks/antigravity-hooks.json` was discovered by nothing. Both files move to
  the repository root, which is the plugin directory once the clone is linked
  in; `agy plugin validate .` went from "missing plugin.json" to loading one
  skill and one hook.
- No verdict on stdout — Antigravity reads a JSON object back and acts on it,
  and `decision` is required before a tool call. The registration now appends
  the answer each event asks for, so the shared scripts stay silent for Codex,
  where stdout answers a permission request.
- Tool names that do not exist — `write_to_file`, `replace_file_content` and
  `find_by_name` are Windsurf's. Antigravity's come from its step-type enum:
  `propose_code`, `file_change`, `edit_notebook`, `run_command`. The touched
  matcher had been naming tools it could never match. Same for three of
  detail.jq's argument fields.
- An edited fixture — the fixtures are lich's, and lich has not registered the
  provider, so `/session-start` answers 400. Reverted; `PENDING_UPSTREAM` in
  tests/contract.mjs names the gap, and fails once a refreshed fixture closes
  it. The shape assertions never needed the edit.

The suite could not catch the first of these because it exported
$CLAUDE_PLUGIN_ROOT for every harness. Registrations now run the way their own
harness runs them, and two guards cover the Antigravity-specific rules.
The previous commit replaced `write_to_file|replace_file_content|run_command`
with names read out of the binary's step-type enum. The enum is the
implementation's vocabulary, not the payload's: a turn measured on agy 1.1.19
sends `write_to_file`, `replace_file_content`, `view_file`, `list_dir`,
`run_command` and `call_mcp_tool`, and only the last of those resembles its
enum entry (`MCP_TOOL`). So the corrected matcher matched `run_command` alone,
and a write with no shell behind it stopped refreshing the card's git status —
the same silent failure as before, with the sign flipped. Measured names
restored, here and in the docs.

Same for detail.jq: `TargetFile`, `AbsolutePath` and `DirectoryPath` are what
arrive; the four enum-derived spellings beside them are dropped rather than left
as rungs nothing ever lands on.

While there: every MCP server's tools reach a hook as the single tool
`call_mcp_tool`, so the card was showing that name for all of them. The server
and the tool it called come as arguments, and detail.jq now reads them —
lich's own tools are what a session is most likely to be running.

The rule in CLAUDE.md said to take names from the harness itself and gave the
binary as one of the places to look. It is not: a name is measured off a real
payload or it is a guess.
lich registered the provider (lichdotdev/lich#370), so `refresh-fixtures.sh`
brings down a `session-start` case for it and `PENDING_UPSTREAM` empties — the
guard that fails once a refreshed fixture registers a pending provider did
exactly that, which is what emptied it. The enumerations come from the fixtures
alone again, and the antigravity payload is now held to an accepted case by
value rather than by shape.
@omartelo
omartelo merged commit e4215a7 into main Aug 24, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant