Skip to content

feat(rate-limits): fetch Antigravity usage from the local language server - #20933

Closed
lippdev wants to merge 3 commits into
stablyai:mainfrom
lippdev:lippdev/antigravity-usage
Closed

lippdev wants to merge 3 commits into
stablyai:mainfrom
lippdev:lippdev/antigravity-usage

Conversation

@lippdev

@lippdev lippdev commented Sep 16, 2026

Copy link
Copy Markdown

ELI5

The Antigravity meter on the status bar used to copy Gemini CLI quota. That showed the wrong pools and broke for anyone who uses agy without Gemini CLI installed. This PR reads Antigravity's own quota instead: first from the local language server on 127.0.0.1, then from agy's OAuth token if the language server is not running.

What Changed

  • Deleted the Gemini→Antigravity mirror (deriveAntigravityRateLimits). Antigravity is now a seventh independent fetch in the rate-limit cycle.
  • Probe a running Antigravity language server (app, then agy, then IDE) on loopback only. POST RetrieveUserQuotaSummary with forceRefresh: true. Desktop servers send X-Codeium-Csrf-Token; CLI is tokenless.
  • Map grouped 2×2 quota buckets (plus nested remaining / used-limit / flat shapes) onto the existing compact session + weekly meters. Tightest remaining fraction wins for each cadence.
  • OAuth fallback uses agy's keyring (gemini / antigravity, including go-keyring-base64:) and ~/.gemini/antigravity{,-cli}/oauth_creds.json, then daily-cloudcode-pa / cloudcode-pa retrieveUserQuotaSummary. Never spawns agy. Never shells out to PowerShell or CredRead.
  • While the main window is focused, re-probe the language server every 45s (no Google call on that path). A snapshot that returns after blur/stop() is dropped.
  • Status-bar visibility no longer requires Gemini CLI OAuth. The Antigravity item still needs agy on PATH plus the existing toggle. Clicking it does not jump to Gemini Accounts.

Why

Fixes the two failures in #9122: wrong Gemini CLI buckets on the Antigravity segment, and "Token refresh failed" whenever token refresh needed a local @google/gemini-cli install. The language-server path is the same source Codenotch/CodexBar use for near-real-time quota; OAuth is only the fallback when no LS is listening.

Linked Issue

Fixes #9122

Related: #7809 (visibility contract), #19561 (Usage unavailable). This does not add an Antigravity Accounts sign-in surface (#19587).

Visual Proof

No new chrome or tokens — the existing compact Antigravity status-bar slot now fills with Antigravity's own session/weekly percents.

Before: segment copied Gemini CLI buckets, stayed hidden unless Gemini CLI OAuth was enabled, or showed Gemini refresh errors / "Usage unavailable".

After: independent Antigravity percents when agy is on PATH and the status-bar item is checked. Gemini OAuth is not required. Clicking the segment does not open Gemini Accounts.

Manually verified on macOS with Orca-dev against a live Antigravity language server. Screenshot not attached (no binary in the PR).

Testing

  • I manually tested these changes locally
  • Automated tests added/updated, or explained why not below

macOS: Orca-dev, Antigravity language server running — status bar showed independent session/weekly usage and updated on the live poll.

Automated: parser / process-table / loopback URL / fetcher (LS before OAuth, localOnly skips Google, never --print / /usage) / service independence + live poll + drop-after-blur / status-bar visibility without Gemini OAuth.

Linux / Windows: not run on-device. Process listing uses ps+lsof (macOS/Linux) and readWindowsProcessTable + netstat.exe via windowsSystem32Binary (Windows). Windows OAuth is file-based only (no CredRead/PowerShell); live quota is the loopback LS.

SSH: probes 127.0.0.1 on the machine running Orca's main process. It does not inspect an SSH execution host.

AI Disclosure

Implementation assisted by Cursor Grok 4.6.

Review

Security: HTTPS to the language server skips TLS verify only after the hostname is confirmed 127.0.0.1 / ::1. Response bodies are capped at 1MB. CSRF is required for app/IDE processes. Live poll does not call Google.

Cross-platform: see Testing. Path separators go through Node path. No hardcoded metaKey.

Remote SSH: local-host only, as above.

Mobile: unused.

Backwards compatibility: mixed client/host is fine; this is a local main-process fetch, not a wire opcode.

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

Ensure no issues in: Security, Cross-platoform support (Linux, Windows, Mac), Remote SSH, Mobile, general backwards compatibility, performance

Checklist

  • This PR is small and focused
  • I explained what changed and why (including ELI5)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

Local: targeted vitest, pnpm tc:node, pnpm run check:code-quality:changed, oxlint + oxfmt on the touched files. Full pnpm lint / pnpm build left to CI.

Made with Cursor

@coderabbitai

coderabbitai Bot commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds independent Antigravity quota fetching from a local language server. It parses startup logs, maps session and weekly quota buckets, and probes loopback HTTP or HTTPS endpoints with response-size and timeout limits. The rate-limit service now fetches Antigravity as a seventh provider result instead of mirroring Gemini data. The renderer uses antigravityUsageConfigured independently and does not route Antigravity to Gemini OAuth settings. Tests cover fetching, service integration, and visibility behavior.

Priority: ➖ Normal

Severity of issue fixed: Medium

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description includes the required sections and linked issue, but it claims OAuth fallback, credential discovery, CSRF handling, and focus-aware polling that are not supported by the current change… Update the description and testing claims to match the current implementation, or add the missing OAuth, discovery, CSRF, and live-poll functionality. Replace the Visual Proof section with the required before/after attachments, or write exa…
Linked Issues check ⚠️ Warning Issue #9122 requires independent Antigravity quota data without a Gemini CLI dependency. The PR removes Gemini mirroring and fetches native quota from a live Agy LanguageServer. `antigravity-usage-fet… Add the agy OAuth credential fallback after loopback discovery fails. Fetch and map Antigravity quota through that fallback without Gemini CLI credentials. Add automated tests for credential discovery, fallback quota fetching, and operation…
Docstring Coverage ⚠️ Warning Docstring coverage is 3.28% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 61 functions across 31 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the primary change: fetching Antigravity usage from a local language server.
Out of Scope Changes check ✅ Passed The changes remain connected to issue #9122. Native quota mapping, loopback probing, live-session discovery, stale-result protection, independent Antigravity service integration, visibility settings, …
Full details: Description check

Explanation

The description includes the required sections and linked issue, but it claims OAuth fallback, credential discovery, CSRF handling, and focus-aware polling that are not supported by the current changeset. It also does not provide the required visual proof format for a status-bar behavior change.

Resolution

Update the description and testing claims to match the current implementation, or add the missing OAuth, discovery, CSRF, and live-poll functionality. Replace the Visual Proof section with the required before/after attachments, or write exactly N/A with a clear reason if the change is not considered visual.

Full details: Linked Issues check

Explanation

Issue #9122 requires independent Antigravity quota data without a Gemini CLI dependency. The PR removes Gemini mirroring and fetches native quota from a live Agy LanguageServer. antigravity-usage-fetcher.ts scans all matching logs, checks process liveness, probes loopback endpoints, and maps grouped buckets. However, discovery failure returns ANTIGRAVITY_USAGE_UNAVAILABLE; the fetch path has no agy OAuth credential fallback. Antigravity usage remains unavailable when no live LanguageServer exists.

Resolution

Add the agy OAuth credential fallback after loopback discovery fails. Fetch and map Antigravity quota through that fallback without Gemini CLI credentials. Add automated tests for credential discovery, fallback quota fetching, and operation without a local Gemini CLI installation.

  • Fix all pre-merge checks with AI

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 8


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 90a60cba-b5b9-4a12-9516-c1a3ed18bd27

📥 Commits

Reviewing files that changed from the base of the PR and between 615b137 and ea32520930c46c06ddb35b9678c9795b0caeb2d0.

📒 Files selected for processing (30)
  • src/main/rate-limits/antigravity-language-server-probe.ts
  • src/main/rate-limits/antigravity-language-server-process.test.ts
  • src/main/rate-limits/antigravity-language-server-process.ts
  • src/main/rate-limits/antigravity-loopback-request.ts
  • src/main/rate-limits/antigravity-oauth-credentials.ts
  • src/main/rate-limits/antigravity-oauth-quota.ts
  • src/main/rate-limits/antigravity-quota-summary.test.ts
  • src/main/rate-limits/antigravity-quota-summary.ts
  • src/main/rate-limits/antigravity-usage-fetcher.test.ts
  • src/main/rate-limits/antigravity-usage-fetcher.ts
  • src/main/rate-limits/antigravity-usage-mirror.test.ts
  • src/main/rate-limits/antigravity-usage-mirror.ts
  • src/main/rate-limits/rate-limit-service-test-harness.ts
  • src/main/rate-limits/service-account-target-selection.test.ts
  • src/main/rate-limits/service-antigravity-usage.test.ts
  • src/main/rate-limits/service-inactive-account-previews.test.ts
  • src/main/rate-limits/service-live-claude-usage.test.ts
  • src/main/rate-limits/service-minimax-usage.test.ts
  • src/main/rate-limits/service-refresh-orchestration.test.ts
  • src/main/rate-limits/service-window-activation.test.ts
  • src/main/rate-limits/service/service-full-cycle-application.ts
  • src/main/rate-limits/service/service-full-cycle-preparation.ts
  • src/main/rate-limits/service/service-polling.ts
  • src/main/rate-limits/service/service-state.ts
  • src/main/rate-limits/service/service-types.ts
  • src/renderer/src/components/status-bar/status-bar-provider-visibility.test.ts
  • src/renderer/src/components/status-bar/status-bar-provider-visibility.ts
  • src/renderer/src/components/status-bar/usage-provider-settings-target.test.ts
  • src/renderer/src/components/status-bar/usage-provider-settings-target.ts
  • src/renderer/src/components/status-bar/use-status-bar-controller.ts
💤 Files with no reviewable changes (3)
  • src/renderer/src/components/status-bar/use-status-bar-controller.ts
  • src/main/rate-limits/antigravity-usage-mirror.test.ts
  • src/main/rate-limits/antigravity-usage-mirror.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Comment thread src/main/rate-limits/antigravity-language-server-process.ts Outdated
Comment thread src/main/rate-limits/antigravity-loopback-request.ts Outdated
Comment thread src/main/rate-limits/antigravity-oauth-credentials.ts Outdated
Comment thread src/main/rate-limits/antigravity-oauth-credentials.ts Outdated
Comment thread src/main/rate-limits/antigravity-oauth-quota.ts Outdated
Comment thread src/main/rate-limits/antigravity-quota-summary.ts Outdated
Comment thread src/main/rate-limits/service/service-polling.ts Outdated
Comment thread src/main/rate-limits/service/service-polling.ts Outdated

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Important

The loopback probe and OAuth fallback are wired up correctly, but two background-path defects should be fixed before merge: a rejected live poll becomes an unhandled rejection on every 45s tick when a probe binary is missing, and Linux keyring lookups throw before the documented file fallback runs. Several parser and request gaps against the reference Antigravity clients can also silently drop quota buckets or mis-remedy a 403.

Reviewed changes

  • Independent Antigravity fetch — deletes antigravity-usage-mirror.ts / deriveAntigravityRateLimits and adds a seventh fetcher: a loopback language-server probe (app → agy → IDE) with a keyring/file OAuth fallback.
  • Quota mapping — antigravity-quota-summary.ts maps grouped and flat RetrieveUserQuotaSummary buckets onto the session + weekly meters, tightest remaining wins.
  • Service wiring — a 45s focused-window live poll that writes only the antigravity slot, plus the full-cycle fetch, state fields, and cycle result slot.
  • Status-bar visibility — Antigravity no longer requires Gemini CLI OAuth; the checked item + detected CLI is the durable signal, and clicking no longer jumps to Gemini Accounts.
  • Tests — parser, process-table, loopback, fetcher, service, and visibility suites added/updated.

ℹ️ The setup CTA is now suppressed by a default-on item plus agy on PATH

antigravity is in DEFAULT_STATUS_BAR_ITEMS, and antigravityUsageConfigured is just "item checked AND agent on PATH". Combined with the new hasUsageProviderSettings term, any user who happens to have agy installed now reports configured usage, which hides the "set up usage tracking" empty-state CTA and renders a -- Antigravity chip even when no language server is running and Gemini OAuth is off. That reads as intended from the tests and the PR description; flagging it only because it is a user-visible change for people who configured nothing.

Technical details
# Default-on Antigravity item is now a durable "configured" signal

## Affected sites
- `src/shared/status-bar-defaults.ts:7` — `antigravity` ships checked by default.
- `src/renderer/src/components/status-bar/use-status-bar-controller.ts:106-108` — `antigravityUsageConfigured = statusBarItems.includes('antigravity') && isStatusBarItemAvailable(...)`.
- `src/renderer/src/components/status-bar/status-bar-provider-visibility.ts:76,102-104` — both `hasUsageProviderSettings` and `hasUsageProviderSettingsForProvider('antigravity')` now key on that flag alone.
- `src/renderer/src/components/status-bar/StatusBarProviderSegment.tsx:210-216` — `unavailable` renders `Icon --`.

## Required outcome
Confirm the trade-off is intended: a machine with `agy` on PATH and nothing else configured will now show an Antigravity `--` chip permanently and lose the one-time setup CTA. If not intended, gate the durable signal on something that implies a usable Antigravity session (e.g. a recent successful probe) rather than bare PATH detection.

## Open questions for the human
- Should a never-configured user with `agy` installed still be taught the usage-tracking setup?

ℹ️ Nitpicks

  • antigravity-language-server-probe.ts:48 — pass -ww to ps; procps honors an inherited COLUMNS even when stdout is a pipe, so a long --csrf_token command line can be clipped and the candidate silently dropped (kind !== 'cli' && !csrfToken).
  • antigravity-quota-summary.ts:160-178 — tightestWindow('session') falls back to all buckets when none classify as session, so a weekly-only response renders the weekly value in the 5h slot with windowMinutes: 10080.
  • antigravity-quota-summary.ts:36-38 vs :114 — remainingFractionOf tolerates used up to 1.5 * limit, but bucketFromUnknown rejects remaining < 0, so an over-quota bucket is dropped entirely and the Math.min(100, …) clamp at :128 is unreachable. Over-quota users get "unavailable" instead of 100%.
  • antigravity-oauth-quota.ts:94-102 — a non-2xx, non-401/403 response (e.g. 500) is reported with failureKind: 'parse'; server/network would be more truthful and drives the stale-policy threshold.
  • antigravity-language-server-probe.ts:99 — every language-server result is labelled source: 'cli', including an app/ide candidate.
  • antigravity-quota-summary.ts:117-121 — bucket id falls back to raw.name/groupName; the reference parser also accepts raw.id.
  • antigravity-quota-summary.ts:79-107 — the response payload alias summary (alongside response) and the remaining proto-oneof spelling { case: 'remainingFraction', value } are not consumed; both appear in the reference client's decoder, so those buckets drop silently.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment thread src/main/rate-limits/service/service-polling.ts Outdated
Comment thread src/main/rate-limits/antigravity-oauth-credentials.ts Outdated
Comment thread src/main/rate-limits/antigravity-quota-summary.ts Outdated
Comment thread src/main/rate-limits/antigravity-oauth-quota.ts Outdated
Comment thread src/main/rate-limits/antigravity-oauth-quota.ts Outdated
Comment thread src/main/rate-limits/antigravity-language-server-probe.ts Outdated
Comment thread src/main/rate-limits/antigravity-language-server-probe.ts Outdated

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes

  • Live-poll containment — a rejected 45s Antigravity probe is now caught and applied as a contained error snapshot instead of escaping the setInterval as an unhandled rejection.
  • Generation guard — a monotonic antigravityLiveGeneration (bumped by stopAntigravityLiveTimer and every full-cycle apply) invalidates an in-flight probe, so a stale reading cannot overwrite a newer snapshot after blur, stop, or a full cycle.
  • Windows port discovery — netstat drops -p TCP so IPv6 listeners appear while the parser keeps filtering to LISTENING; process-command parsing now handles quoted Windows paths containing spaces.
  • Loopback probe — tries http as well as https on every discovered port (not just --extension_server_port) and guards the response stream against destroy/abort with a composed timeout signal.
  • OAuth fallback — 403 is split from 401 into a distinct ANTIGRAVITY_QUOTA_FORBIDDEN result, a missing secret-tool/security no longer aborts the file fallback, an expired credential no longer masks a valid file token, and the caller signal composes with the API timeout.
  • Quota mapping — disabled buckets are dropped, over-quota remaining clamps to 100% used instead of dropping the bucket, and the session slot no longer falls back to a classified weekly bucket.
  • Tests — new loopback-request, credentials, and OAuth-quota suites plus service generation/rejection coverage; the Antigravity suites pass locally (37 tests).

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

…erver

Stop copying Gemini usage onto Antigravity. Refresh now discovers the
current Agy LanguageServer from its CLI log, POSTs RetrieveUserQuotaSummary
on loopback, and maps native session/weekly buckets into ProviderRateLimits.

If no local LanguageServer is running, Antigravity usage is unavailable.
Gemini results and failures never populate Antigravity. The status bar
shows a checked Antigravity item without Gemini OAuth and no longer
routes clicks to Gemini Accounts.

Fixes stablyai#9122
@cursor
cursor Bot force-pushed the lippdev/antigravity-usage branch from 11089d4 to 47aaf44 Compare September 16, 2026 11:36

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 04cb6ab1-1b31-4a8d-9fef-7556a2c931ae

📥 Commits

Reviewing files that changed from the base of the PR and between 11089d4376f61bfaf1be52a23025d658a621f390 and 47aaf44.

📒 Files selected for processing (6)
  • src/main/rate-limits/antigravity-usage-fetcher.test.ts
  • src/main/rate-limits/antigravity-usage-fetcher.ts
  • src/main/rate-limits/service-antigravity-usage.test.ts
  • src/main/rate-limits/service/service-full-cycle-preparation.ts
  • src/renderer/src/components/status-bar/status-bar-provider-visibility.test.ts
  • src/renderer/src/components/status-bar/usage-provider-settings-target.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.

const request = transport.request(requestOptions, (response) => {
const chunks: Buffer[] = []
response.on('data', (chunk) => {
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '202,299p' src/main/rate-limits/antigravity-usage-fetcher.ts
sed -n '130,225p' src/main/rate-limits/service/service-full-cycle-preparation.ts
rg -n 'prepareFetchAllCycle|runFetchAllCycle|postLoopbackQuota|setTimeout|timeout' src/main/rate-limits

Repository: stablyai/orca

Length of output: 19656


🏁 Script executed:

sed -n '1,120p' src/main/rate-limits/antigravity-usage-fetcher.ts
sed -n '1,120p' src/main/rate-limits/service/service-full-cycle-preparation.ts
sed -n '1,240p' src/main/rate-limits/service/service-fetch-queue.ts
sed -n '1,80p' src/main/rate-limits/service/service-full-cycle-application.ts
rg -n 'REQUEST_TIMEOUT_MS|fetchAntigravityRateLimits|Promise.allSettled|prepareFetchAllCycle|runFetchAllCycle|cycle' src/main/rate-limits/service src/main/rate-limits/antigravity-usage-fetcher.ts

Repository: stablyai/orca

Length of output: 24400


Bound the loopback response body and total request duration.

postLoopbackQuota stores every response chunk in chunks until end. The node:http and node:https request timeout detects inactivity, not total duration. A response that continuously sends data can keep this promise pending while memory grows without an application-level limit. fetchAntigravityRateLimits() is one of seven promises in the service's Promise.allSettled cycle, so the cycle can remain pending.

Reject responses after a fixed byte limit and enforce an absolute deadline that does not reset when data arrives.

return mapped
}
}
return unavailable(now())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🔎 Supported by static analysis

🏁 Script executed:

sed -n '1,330p' src/main/rate-limits/antigravity-usage-fetcher.ts
rg -n -i 'antigravity|agy|oauth|credential|refresh.?token|access.?token' src/main/rate-limits src/main | head -n 240

Repository: stablyai/orca

Length of output: 37044


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- candidate files ---'
git ls-files | rg -i 'antigravity|agy|gemini.*(auth|credential)|credential.*gemini|oauth'
printf '%s\n' '--- exact symbols and contract terms ---'
rg -n -i 'fetchAntigravityRateLimits|AntigravityUsageFetchDeps|no-language-server|language.?server|agy (stopped|running)|antigravity.*(oauth|credential|fallback)|oauth.*antigravity|credential.*fallback' --glob '!node_modules' --glob '!dist' .
printf '%s\n' '--- recent file structure ---'
ast-grep outline src/main/rate-limits/antigravity-usage-fetcher.ts

Repository: stablyai/orca

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -eu
for f in \
  src/main/rate-limits/antigravity-usage-fetcher.test.ts \
  src/main/rate-limits/gemini-cli-oauth-extractor.ts \
  src/main/rate-limits/gemini-oauth-sources.ts \
  docs/reference/antigravity-readiness-evidence.md
do
  printf '\n--- %s ---\n' "$f"
  wc -l "$f"
done
printf '\n--- Antigravity test ---\n'
cat -n src/main/rate-limits/antigravity-usage-fetcher.test.ts
printf '\n--- Gemini OAuth extractor ---\n'
cat -n src/main/rate-limits/gemini-cli-oauth-extractor.ts
printf '\n--- Gemini OAuth sources ---\n'
cat -n src/main/rate-limits/gemini-oauth-sources.ts

Repository: stablyai/orca

Length of output: 22541


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- credential consumers in rate-limit code ---'
rg -n 'readGeminiCredentials|refreshAccessToken|loadProjectId|tryRefreshTokenFromBundle|extractOAuthClientCredentials|oauth_creds|cloudaicompanionProject|GEMINI_CLI' src/main/rate-limits --glob '*.ts'
printf '%s\n' '--- Antigravity references outside bundled guides ---'
rg -n -i 'antigravity|agy' src/main/rate-limits src/main/ai-vault docs/reference/antigravity-readiness-evidence.md --glob '*.ts' --glob '*.md'
printf '%s\n' '--- worktree and recent commit shape ---'
git status --short
git diff --stat
git diff --name-only
git show --stat --oneline --no-renames HEAD

Repository: stablyai/orca

Length of output: 42098


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- deleted mirror implementation at parent ---'
git show HEAD^:src/main/rate-limits/antigravity-usage-mirror.ts | cat -n
printf '%s\n' '--- deleted mirror tests at parent ---'
git show HEAD^:src/main/rate-limits/antigravity-usage-mirror.test.ts | cat -n
printf '%s\n' '--- Gemini fetcher imports and control flow ---'
cat -n src/main/rate-limits/gemini-usage-fetcher.ts | sed -n '1,280p'

Repository: stablyai/orca

Length of output: 14411


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- commit diff for Antigravity service wiring ---'
git show --format=fuller --no-ext-diff -- \
  src/main/rate-limits/service/service-full-cycle-preparation.ts \
  src/main/rate-limits/service/service-full-cycle-application.ts \
  src/main/rate-limits/service-antigravity-usage.test.ts \
  src/main/rate-limits/antigravity-usage-fetcher.ts \
  src/main/rate-limits/antigravity-usage-fetcher.test.ts | sed -n '1,520p'
printf '%s\n' '--- current service preparation call site ---'
cat -n src/main/rate-limits/service/service-full-cycle-preparation.ts | sed -n '100,235p'
printf '%s\n' '--- current service application Antigravity handling ---'
cat -n src/main/rate-limits/service/service-full-cycle-application.ts | sed -n '20,110p'

Repository: stablyai/orca

Length of output: 25429


Use the independent Antigravity OAuth fallback on both unavailable paths.

fetchAntigravityRateLimits returns unavailable when endpoint discovery returns null. probeQuota returns unavailable after both loopback probes fail. Neither path invokes an Antigravity OAuth credential helper, so valid credentials cannot provide quota when agy is stopped or probing fails.

Keep the LanguageServer path first. Run the independent OAuth fallback before either unavailable return. Extend the no-language-server test and add a probe-failure test to cover usable credentials.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ℹ️ No critical issues — a few verification and robustness suggestions inline.

Reviewed changes

This run re-reviews 47aaf44, which replaced the branch's earlier multi-file implementation with a single new fetcher, so the delta is the whole current implementation.

  • Rewrote discovery around a log-file probe — antigravity-usage-fetcher.ts now finds the language server by reading ~/.gemini/antigravity-cli/log/cli-*.log instead of walking the process table with ps/lsof/netstat.
  • Dropped the OAuth fallback — the keyring / oauth_creds.json path and its cloudcode-pa request are gone; the local language server is the only source.
  • Moved Antigravity into the full refresh cycle — the 45s focused-window live poll is removed, so Antigravity is now a plain member of the cycle's Promise.allSettled.
  • Simplified the renderer gate — visibility keys on the checked status-bar item alone, and clicking no longer targets Gemini Accounts.

ℹ️ The PR description describes a materially larger change than the branch contains

The description's "What Changed", "Testing", and "Review" sections still describe the deleted implementation: the OAuth keyring fallback, the X-Codeium-Csrf-Token header, the 1MB response cap, app/IDE process discovery, the 45s focused live poll, and automated tests for --print//usage and localOnly. None of that code is on the branch, and readers of the linked issue will use the description to judge cross-platform and security coverage, so it is worth rewriting to match 47aaf44.

Technical details
# Stale PR description after the rewrite

## Affected sites
- PR body sections: "What Changed", "Testing", "Review".
- Current code: `src/main/rate-limits/antigravity-usage-fetcher.ts` is the only source; there is no OAuth module, no CSRF header, no response cap, and no live poll in the diff.

## Required outcome
The description should describe the branch as shipped: log-based discovery of `~/.gemini/antigravity-cli/log/cli-*.log`, a `forceRefresh` POST to the loopback language server, and no OAuth path. If the removed capabilities are intended to return in a follow-up, say so explicitly rather than leaving them stated as present.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment thread src/main/rate-limits/antigravity-usage-fetcher.ts Outdated
resolve(null)
})
request.on('error', () => resolve(null))
request.end('{"forceRefresh":true}')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Question on the request contract: published clients that hit this RPC send {"metadata":{"ideName":"antigravity",...}} or an empty body, and none documents a forceRefresh field. If the server's Connect/JSON codec rejects unknown fields, every call fails and the provider is permanently unavailable. Worth confirming against the live server that this exact body returns a response.groups payload (and that the field is load-bearing rather than an accidental omission of metadata).

Technical details
# Unverified request body

## Affected sites
- `src/main/rate-limits/antigravity-usage-fetcher.ts:259` — `request.end('{"forceRefresh":true}')`.

## Required outcome
A recorded request/response from a live language server proving this body is accepted and returns populated `groups`, or a body aligned with the reference clients.

Comment on lines +50 to +54
const LIVE_LOG = [
'Starting language server process with pid 4242',
'listening on random port at 4312 for HTTP',
'listening on random port at 4313 for HTTPS (gRPC)'
].join('\n')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LIVE_LOG is written to match the parser's own regexes, so this test passes whichever banner the real agy prints. docs/reference/antigravity-readiness-evidence.md documents five Antigravity detectors that each tuned to a hand-typed fixture and then failed on captured output. A real ~/.gemini/antigravity-cli/log/cli-*.log head (scrubbed) would let this test fail if the path, filename shape, or banner text differ.

Technical details
# Log discovery has no captured evidence

## Affected sites
- `src/main/rate-limits/antigravity-usage-fetcher.test.ts:50-54` — `LIVE_LOG` mirrors the parser's assumptions.
- `src/main/rate-limits/antigravity-usage-fetcher.ts:167-200` — `discoverCurrentEndpoint` reads `~/.gemini/antigravity-cli/log` and files named `cli-*.log`; neither the `log/` directory nor that filename shape appears anywhere else in the repo (only `.gemini/antigravity-cli/brain` and `history.jsonl` do).

## Required outcome
One committed fixture captured from a live `agy` — the log head carrying the pid and port lines — replayed through `parseAntigravityLanguageServerLog`, so the regex is exercised against real output rather than itself. A captured `RetrieveUserQuotaSummary` response would pin the bucket schema at the same time.

Comment thread src/main/rate-limits/service/service-full-cycle-preparation.ts

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ℹ️ No new issues — this delta hardens the loopback probe and fixes test isolation in the shared harness.

Reviewed changes

This run re-reviews e31d6dc, the probe-hardening commit on top of 47aaf44.

  • Extracted the loopback transport into src/main/rate-limits/antigravity-loopback-quota.ts, which validates the URL (scheme and 127.0.0.1 host), accepts the self-signed cert only on loopback, and settles every path through one guarded finish().
  • Bounded the probe with a wall-clock deadline (replacing the socket timeout option) and a 1MB response cap, tearing the request down on overflow, timeout, error, and aborted.
  • Split the failure taxonomy so a discovered-but-unresponsive LanguageServer returns error with ANTIGRAVITY_USAGE_PROBE_FAILED and failureKind: 'network', distinct from the no-endpoint unavailable "Start agy" case.
  • Fixed test isolation by stubbing fetchAntigravityRateLimits in both shared harness helpers and vi.mock-ing the module in the six sibling service suites that use them.
  • Added tests for the response cap and the injected wall-clock deadline; the full rate-limits suite is green (45 files / 479 tests).

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@nwparker

Copy link
Copy Markdown
Contributor

Reviewed current head e31d6dc5010cb82498bb5ad40b72dbe3b5cd73f7 during migration verification.

P2: 12 newer exited sessions hide an older active session. discoverCurrentEndpoint() sorts log filenames and truncates to 12 before checking their recorded PIDs. I executed the exact candidate fetcher and loopback module with an isolated home-directory lookup and synthetic logs:

  • One log for a live local quota server: ok, 25% used.
  • Add 12 newer logs for exited processes, keeping the original server alive: unavailable, with no additional request to that server.

ELI5: the usage meter forgets a session that is still open after enough newer sessions leave log files behind. The file/response bounds are useful and should stay; discovering active server ownership needs a strategy that does not equate “outside the newest 12 logs” with “not running.” Simply making the scan unbounded is not a good fix.

The reproduction compiles both modules unchanged, substitutes only homedir(), and exercises actual loopback HTTP and PID liveness. Temporary files/server were removed. This is a synthetic discovery regression, not populated OAuth quota verification.

The PR description also needs to match this head. It currently claims app/CLI/IDE process discovery, keyring/file OAuth fallback, CSRF headers, and a 45-second focus-aware live poll. The current fetcher instead reads CLI logs, probes their loopback ports, and returns unavailable when discovery fails. Its request sends content-type/protocol headers only, and the diff contains no described live-poll integration. Please rewrite the description and verification claims around the actual implementation so reviewers can assess it accurately.

Bounded inspection of the 12 latest installed-agy logs here confirmed the PID and port banners fit inside the first 8 KiB, so I did not reproduce a log-prefix parsing problem. No account credentials were inspected. I’m not adopting this candidate over the currently verified native /usage draft (#21654, custom-executable follow-up #21678).

Co-authored-by: Filipe Moreira <lippdev@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

🟠 Major · Verify the process identity before selecting the endpoint. · antigravity-usage-fetcher.ts:201-202

src/main/rate-limits/antigravity-usage-fetcher.ts:201-202
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Verify the process identity before selecting the endpoint.

The unlimited historical scan can select a stale log whose PID was reused by an unrelated active process. Discovery then returns its obsolete port and probeQuota stops after that request fails. A real older LanguageServer is not reached, and the result becomes ANTIGRAVITY_USAGE_PROBE_FAILED.

Verify the process command and start time, or probe ordered endpoint candidates until one returns a mapped quota response. Add a PID-reuse test that places the real live session after the false-positive log.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 81a34f95-74b8-47fb-b3fa-3c063eb56816

📥 Commits

Reviewing files that changed from the base of the PR and between e31d6dc and 641a6b9.

📒 Files selected for processing (2)
  • src/main/rate-limits/antigravity-usage-fetcher.test.ts
  • src/main/rate-limits/antigravity-usage-fetcher.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ℹ️ No critical issues — one minor suggestion inline.

Reviewed changes

This run re-reviews 641a6b92, a discoverability follow-up to e31d6dc.

  • Removed the 12-log cap in discoverCurrentEndpoint — discovery now scans cli-*.log newest-first with no fixed bound and returns the first entry whose pid is alive, so an older live LanguageServer behind newer exited logs is still found.
  • Injected a homedir dependency for tests and added a regression test (12 newer exited logs plus one older live session) that fails when the cap is restored.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

.filter((name) => name.startsWith('cli-') && name.endsWith('.log'))
.sort()
.toReversed()
for (const name of names) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This scan is now unbounded and runs synchronously on the main process — it previously stopped after 12 logs. When agy is not running, every refresh reads all cli-*.log files in the directory (up to the 5-minute activation debounce), so a log directory that accumulates sessions adds main-thread I/O to each poll. Consider async reads, or a bounded newest-first candidate list that is probed until one returns a mapped summary.

Technical details
# Unbounded synchronous log scan

## Affected sites
- `src/main/rate-limits/antigravity-usage-fetcher.ts:192` — `discoverCurrentEndpoint` iterates every `cli-*.log`, reading 8KB from each with `openSync`/`readSync`/`closeSync`.
- `src/main/rate-limits/service/service-full-cycle-preparation.ts:185` — discovery runs on the main process inside every full refresh cycle.

## Required outcome
Discovery should not block the main process for an unbounded number of files. Either make the reads asynchronous (`node:fs/promises`) or return a bounded newest-first candidate list and probe each until one yields a mapped quota summary; the latter also avoids a stale log shadowing the live server when its pid has been reused.

## Open questions for the human
- How many `cli-*.log` files does `agy` retain, and does it prune them? If the directory stays small, this is informational only.

@nwparker

Copy link
Copy Markdown
Contributor

Review update: this head is superseded by the current native agy quota stack #21654 → #21678, which uses the supported agy --print /usage --output-format json contract and configured executable.

This PR’s loopback language-server protocol is not independently live-verified on the current installed agy profile, and its status is UNSTABLE with only the pullfrog check visible. It also overlaps the newer native CLI fetcher and would reintroduce a second quota source to maintain.

Scoped verdict: do not merge this head unless the language-server path is deliberately retained as a separate, freshly verified fallback.

@nwparker

Copy link
Copy Markdown
Contributor

Thanks for the work on this, and for your patience. Orca now reads Antigravity quota from the agy CLI (#24073), has native Antigravity Accounts (#24691), and short pool labels in the status bar (#31007), which covers what this PR set out to do. Closing as superseded; your effort here helped shape the problem, so thank you.

@nwparker nwparker closed this Oct 24, 2026
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.

[Bug]: Antigravity usage is a copy of the Gemini quota — wrong pools, and refresh breaks entirely without a local Gemini CLI install

3 participants