feat(antigravity): detect the agy CLI's language server#526
feat(antigravity): detect the agy CLI's language server#526vinitkumargoel wants to merge 2 commits into
Conversation
The Antigravity plugin only resolved credentials from the IDE — its state.vscdb and the flagged language_server_macos process — so anyone running the `agy` CLI instead of the IDE always hit "Start Antigravity and try again." The agy CLI embeds the same exa.language_server_pb.LanguageServerService but exposes it on a local port with no CSRF token and without the IDE's --ide_name / --csrf_token flags. Teach discovery to handle that: - host_api ls.discover: treat empty markers as "no marker filter" and an empty csrfFlag as "no CSRF required", and iterate matching processes, picking the first with listening ports instead of bailing on the first name match. Existing plugins still pass non-empty markers + csrf, so their behavior is unchanged. - antigravity plugin: add discoverAgyCli/probeAgyCli (processName "agy", empty markers/csrf), refactor probeLs into a shared probeViaDiscovery, and try the CLI as a fallback after the IDE path. Verified end-to-end: with the IDE closed and the CLI running, the plugin returns live model pools and plan from GetUserStatus.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
✅ Files skipped from review due to trivial changes (2)
📝 WalkthroughWalkthroughThe PR adds support for discovering Antigravity language servers embedded in the local ChangesAntigravity CLI Fallback Discovery
🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src-tauri/src/plugin_engine/host_api.rs (1)
1360-1362:⚠️ Potential issue | 🟠 Major | ⚡ Quick winUse executable-name matching instead of substring matching for process selection.
Line 1360 currently matches any command containing the string, so short names like
agycan hit unrelated processes and return the wrong PID.Suggested fix
- if !command_lower.contains(&process_name_lower) { + let argv0 = command.split_whitespace().next().unwrap_or_default(); + let exe_name = std::path::Path::new(argv0) + .file_name() + .and_then(|n| n.to_str()) + .map(|s| s.to_lowercase()) + .unwrap_or_default(); + if exe_name != process_name_lower { continue; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src-tauri/src/plugin_engine/host_api.rs` around lines 1360 - 1362, The current substring check using command_lower.contains(&process_name_lower) can match unrelated commands; change it to compare the executable basename against process_name_lower instead: extract the command's executable name (e.g., by splitting on whitespace and taking the first token or using a Path basename) and lower-case that token, then replace the contains check with an equality check (executable_name_lower == process_name_lower) so only exact executable-name matches (using the variables command_lower/process_name_lower) return the PID.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@plugins/antigravity/plugin.test.js`:
- Around line 1544-1569: The test "prefers the IDE language server over the agy
CLI when both are present" should explicitly assert the CLI discovery path was
not invoked; after calling plugin.probe(ctx) and before finishing the test, add
an expectation that the language-server discovery function (ls.discover) was
never called with processName: "agy" (e.g.,
expect(ls.discover).not.toHaveBeenCalledWith(expect.objectContaining({
processName: "agy" }))). This change ensures the mocked CLI discovery path was
skipped; reference the existing test and helpers (mockDiscoverByProcess,
loadPlugin, plugin.probe) to locate where to insert the assertion.
In `@src-tauri/src/plugin_engine/host_api.rs`:
- Around line 1370-1382: The closure computing has_marker currently
short-circuits when ide_name exists but doesn't match, skipping app_data_dir
checks; update the closure used for markers_lower.iter().any(|m| { ... }) so it
independently tests both exact IDE flag match (ide_name == m) and exact app data
dir flag match (app_data == m) instead of returning early on the ide_name
branch, and then still fall back to the path substring check using
command_lower.contains(&format!("/{}/", m)); ensure the symbols mentioned
(has_marker, markers_lower, ide_name, app_data, command_lower) are used so both
exact-flag comparisons are evaluated before the path substring fallback.
---
Outside diff comments:
In `@src-tauri/src/plugin_engine/host_api.rs`:
- Around line 1360-1362: The current substring check using
command_lower.contains(&process_name_lower) can match unrelated commands; change
it to compare the executable basename against process_name_lower instead:
extract the command's executable name (e.g., by splitting on whitespace and
taking the first token or using a Path basename) and lower-case that token, then
replace the contains check with an equality check (executable_name_lower ==
process_name_lower) so only exact executable-name matches (using the variables
command_lower/process_name_lower) return the PID.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 5b1c2522-ef74-4263-a0c6-e6008846334e
📒 Files selected for processing (3)
plugins/antigravity/plugin.jsplugins/antigravity/plugin.test.jssrc-tauri/src/plugin_engine/host_api.rs
| it("prefers the IDE language server over the agy CLI when both are present", async () => { | ||
| const ctx = makeCtx() | ||
| mockDiscoverByProcess(ctx, { | ||
| ide: makeDiscovery({ csrf: "ide-csrf" }), | ||
| agy: makeDiscovery({ csrf: "", ports: [58892] }), | ||
| }) | ||
| // IDE yields only a Flash model; if the CLI path were used we'd see the | ||
| // full default set instead. | ||
| userStatusHttp( | ||
| ctx, | ||
| makeUserStatusResponse({ | ||
| configs: [ | ||
| { | ||
| label: "Gemini 3 Flash", | ||
| modelOrAlias: { model: "M18" }, | ||
| quotaInfo: { remainingFraction: 0.5, resetTime: "2026-02-08T09:10:56Z" }, | ||
| }, | ||
| ], | ||
| }) | ||
| ) | ||
|
|
||
| const plugin = await loadPlugin() | ||
| const result = plugin.probe(ctx) | ||
|
|
||
| expect(result.lines.map((l) => l.label)).toEqual(["Gemini Flash"]) | ||
| }) |
There was a problem hiding this comment.
The IDE-priority test does not currently prove that CLI was skipped.
This test can still pass if the CLI path is used, because the mocked LS HTTP response is shared. Add a direct assertion that ls.discover was never called with processName: "agy".
Suggested test hardening
const plugin = await loadPlugin()
const result = plugin.probe(ctx)
expect(result.lines.map((l) => l.label)).toEqual(["Gemini Flash"])
+ const agyCalls = ctx.host.ls.discover.mock.calls.filter(
+ (c) => c[0] && c[0].processName === "agy"
+ )
+ expect(agyCalls.length).toBe(0)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@plugins/antigravity/plugin.test.js` around lines 1544 - 1569, The test
"prefers the IDE language server over the agy CLI when both are present" should
explicitly assert the CLI discovery path was not invoked; after calling
plugin.probe(ctx) and before finishing the test, add an expectation that the
language-server discovery function (ls.discover) was never called with
processName: "agy" (e.g.,
expect(ls.discover).not.toHaveBeenCalledWith(expect.objectContaining({
processName: "agy" }))). This change ensures the mocked CLI discovery path was
skipped; reference the existing test and helpers (mockDiscoverByProcess,
loadPlugin, plugin.probe) to locate where to insert the assertion.
| let has_marker = markers_lower.is_empty() | ||
| || markers_lower.iter().any(|m| { | ||
| // Prefer exact flag match; skip path fallback when | ||
| // a distinguishing flag exists. | ||
| if let Some(ref name) = ide_name { | ||
| return *name == *m; | ||
| } | ||
| if let Some(ref dir) = app_data { | ||
| return *dir == *m; | ||
| } | ||
| // Fallback: path substring | ||
| command_lower.contains(&format!("/{}/", m)) | ||
| }); |
There was a problem hiding this comment.
Do not short-circuit --app_data_dir matching when --ide_name exists.
Current logic returns on ide_name comparison first, so app_data_dir is skipped whenever ide_name is present but different.
Suggested fix
- let has_marker = markers_lower.is_empty()
- || markers_lower.iter().any(|m| {
- // Prefer exact flag match; skip path fallback when
- // a distinguishing flag exists.
- if let Some(ref name) = ide_name {
- return *name == *m;
- }
- if let Some(ref dir) = app_data {
- return *dir == *m;
- }
- // Fallback: path substring
- command_lower.contains(&format!("/{}/", m))
- });
+ let has_marker = markers_lower.is_empty()
+ || markers_lower.iter().any(|m| {
+ if ide_name.as_ref().is_some_and(|name| name == m) {
+ return true;
+ }
+ if app_data.as_ref().is_some_and(|dir| dir == m) {
+ return true;
+ }
+ if ide_name.is_some() || app_data.is_some() {
+ return false;
+ }
+ command_lower.contains(&format!("/{}/", m))
+ });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src-tauri/src/plugin_engine/host_api.rs` around lines 1370 - 1382, The
closure computing has_marker currently short-circuits when ide_name exists but
doesn't match, skipping app_data_dir checks; update the closure used for
markers_lower.iter().any(|m| { ... }) so it independently tests both exact IDE
flag match (ide_name == m) and exact app data dir flag match (app_data == m)
instead of returning early on the ide_name branch, and then still fall back to
the path substring check using command_lower.contains(&format!("/{}/", m));
ensure the symbols mentioned (has_marker, markers_lower, ide_name, app_data,
command_lower) are used so both exact-flag comparisons are evaluated before the
path substring fallback.
There was a problem hiding this comment.
1 issue found across 5 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/lib/settings.ts">
<violation number="1" location="src/lib/settings.ts:49">
P2: Removing `60` from `AUTO_UPDATE_INTERVALS` silently resets existing 60-minute settings to 15 minutes, causing 4× more frequent refreshes without user knowledge.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
| export const DEFAULT_START_ON_LOGIN = false; | ||
|
|
||
| const AUTO_UPDATE_INTERVALS: AutoUpdateIntervalMinutes[] = [5, 15, 30, 60]; | ||
| const AUTO_UPDATE_INTERVALS: AutoUpdateIntervalMinutes[] = [1, 5, 15, 30]; |
There was a problem hiding this comment.
P2: Removing 60 from AUTO_UPDATE_INTERVALS silently resets existing 60-minute settings to 15 minutes, causing 4× more frequent refreshes without user knowledge.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/lib/settings.ts, line 49:
<comment>Removing `60` from `AUTO_UPDATE_INTERVALS` silently resets existing 60-minute settings to 15 minutes, causing 4× more frequent refreshes without user knowledge.</comment>
<file context>
@@ -46,7 +46,7 @@ export const DEFAULT_MENUBAR_ICON_STYLE: MenubarIconStyle = "provider";
export const DEFAULT_START_ON_LOGIN = false;
-const AUTO_UPDATE_INTERVALS: AutoUpdateIntervalMinutes[] = [5, 15, 30, 60];
+const AUTO_UPDATE_INTERVALS: AutoUpdateIntervalMinutes[] = [1, 5, 15, 30];
const THEME_MODES: ThemeMode[] = ["system", "light", "dark"];
const DISPLAY_MODES: DisplayMode[] = ["used", "left"];
</file context>
Summary
The Antigravity provider only worked when the Antigravity IDE was installed/running — it resolved credentials from the IDE's
state.vscdband discovered the IDE's flaggedlanguage_server_macosprocess. When you use theagyCLI instead of the IDE, neither source exists, so the probe always falls through tothrow "Start Antigravity and try again.".This teaches the plugin to also detect the
agyCLI's embedded language server.Why it works
The
agyCLI runs as a singleagyprocess and embeds the sameexa.language_server_pb.LanguageServerService, exposing it on a local port — but with two differences from the IDE:--csrf_tokenon the LS process args; the CLI doesn't), and--ide_name/ path markers to match on.GetUserStatuson the CLI's port returns the samecascadeModelConfigData.clientModelConfigs+userTierthe plugin already parses, so once discovery finds the port the rest of the pipeline is unchanged.Changes
src-tauri/.../plugin_engine/host_api.rs(ls.discover):markers⇒ no marker filtering.csrfFlag⇒ no CSRF required (emit an empty token instead of bailing).agyresolves robustly).markers+csrfFlag, so their discovery behavior is unchanged.plugins/antigravity/plugin.js:discoverAgyCli/probeAgyCli(processName: "agy", empty markers/csrf).probeLsinto a sharedprobeViaDiscovery.Test plan
bun run test— full suite green (1116 passed), including 4 new antigravity CLI tests: CLI provides data, correct discovery opts (empty markers/csrf), IDE-takes-precedence, and still-throws-when-nothing-available.cargo test(src-tauri) — passes; Rust compiles clean.agyCLI running, the provider now resolves plan "Google AI Pro" and the Gemini Pro / Gemini Flash / Claude quota pools fromGetUserStatus— where it previously showed "Start Antigravity and try again."No UI changes (logic only), so no screenshots. One concern per the contributing guide: this is solely about Antigravity CLI detection.
Summary by cubic
Detects the
agyCLI’s embedded language server as a fallback when the IDE isn’t running, and adds a 1-minute auto-refresh interval for faster updates.New Features
agylanguage server (no CSRF token, no--ide_namemarkers).Refactors
ls.discover: emptymarkersdisables marker filtering; emptycsrfFlagmeans “no CSRF required.”probeViaDiscovery.Written for commit bbcb0be. Summary will update on new commits.
Summary by CodeRabbit
New Features
Bug Fixes
Tests
Documentation