From 940786c57f33d5c776c7119215413ae226affa9c Mon Sep 17 00:00:00 2001 From: Nikketryhard Date: Sat, 14 Feb 2026 21:40:35 -0600 Subject: [PATCH] docs: update standalone LS, MITM, and panel stream investigation - Add panel-stream-investigation.md documenting dead end - Update KNOWN_ISSUES: move polling and panel stream to resolved - Update GEMINI.md with standalone LS section and new MITM setup - Fix standalone-ls-todo to reflect default mode --- GEMINI.md | 93 ++++++++++++++++++------------ KNOWN_ISSUES.md | 52 ++++++++++------- docs/panel-stream-investigation.md | 93 ++++++++++++++++++++++++++++++ docs/standalone-ls-todo.md | 4 +- 4 files changed, 183 insertions(+), 59 deletions(-) create mode 100644 docs/panel-stream-investigation.md diff --git a/GEMINI.md b/GEMINI.md index 3411da2..8fc23da 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -8,11 +8,17 @@ OpenAI-compatible proxy that intercepts and relays requests to Google's Antigrav # Build cargo build --release -# Run (language server must be running) +# First-time setup (creates user + iptables for MITM) +sudo ./scripts/mitm-redirect.sh install + +# Run (spawns standalone LS automatically) RUST_LOG=info ./target/release/antigravity-proxy # Custom port RUST_LOG=info ./target/release/antigravity-proxy --port 9000 + +# Attach to existing LS instead of spawning standalone +RUST_LOG=info ./target/release/antigravity-proxy --no-standalone ``` Default port: **8741** @@ -115,62 +121,75 @@ Version strings (Antigravity, Chrome, Electron, Client) are **auto-detected** at Falls back to hardcoded values if the app isn't installed. No manual updates needed when Antigravity updates. +## Standalone LS + +By default, the proxy spawns its own Language Server instance for full isolation: + +1. Discovers the main LS config (`extension_server_port`, `csrf_token`) from the running Antigravity app +2. Spawns a standalone LS binary on a random port +3. Builds init metadata protobuf (model config, `detect_and_use_proxy=ENABLED`) +4. If MITM is active, spawns as `antigravity-ls` user for UID-scoped traffic interception +5. Kills the child on proxy shutdown + +Disable with `--no-standalone` to attach to the real LS instead. + +**Module:** `src/standalone.rs` + ## Stealth Features - **TLS fingerprint**: BoringSSL with Chrome JA3/JA4 + H2 fingerprint via `wreq` (version auto-detected) - **Protobuf**: Hand-rolled encoder producing byte-exact match to real webview traffic - **Warmup**: Mimics real webview startup RPC calls - **Heartbeat**: Periodic keep-alive matching real webview lifecycle -- **Jitter**: Randomized polling intervals to avoid automation fingerprint -- **Session reuse**: Cascades are reused for multi-turn, matching real webview behavior -- **MITM proxy**: TLS-intercepting proxy for real token usage capture (opt-in) +- **Reactive streaming**: `StreamCascadeReactiveUpdates` for real-time state diffs (polling fallback) +- **Jitter**: Randomized intervals to avoid automation fingerprint +- **Session reuse**: Cascades reused for multi-turn, matching real webview behavior +- **MITM proxy**: TLS-intercepting proxy for real token usage capture ## MITM Proxy -Built-in MITM proxy intercepts LS ↔ Google/Anthropic traffic to capture **real** token usage (input, output, cache read, cache creation). Disabled with `--no-mitm`. +Built-in MITM proxy intercepts LS ↔ Google API traffic to capture **real** token usage (input, output, thinking tokens). Enabled by default with the standalone LS. Disable with `--no-mitm`. + +### How It Works + +``` +Client → Proxy (8741) → Standalone LS (as antigravity-ls user) + ↓ (port 443 traffic) + iptables REDIRECT (UID-scoped) + ↓ + MITM Proxy (8742) + ↓ (TLS decrypt + parse SSE) + Google API (daily-cloudcode-pa.googleapis.com) +``` ### Setup ```bash -# 1. Start proxy (generates CA cert automatically) +# One-time setup (creates user + iptables rule) +sudo ./scripts/mitm-redirect.sh install + +# Run proxy (standalone LS + MITM are both on by default) RUST_LOG=info ./target/release/antigravity-proxy -# 2. Patch extension to enable proxy detection (required!) -# The LS has a protobuf field `detect_and_use_proxy` that defaults to UNSPECIFIED, -# which means it ignores HTTPS_PROXY for LLM API calls. This patch sets it to ENABLED (1). -# Must be re-applied after every Antigravity update. -sudo sed -i -E 's/detectAndUseProxy=[^,;)]+/detectAndUseProxy=1/g' \ - /usr/share/antigravity/resources/app/extensions/antigravity/dist/extension.js - -# 3. Install wrapper (patches LS binary to route through MITM) -./scripts/mitm-wrapper.sh install - -# 4. Restart Antigravity — done! - -# Check status -./scripts/mitm-wrapper.sh status - -# Uninstall -./scripts/mitm-wrapper.sh uninstall -``` - -### Extension Patch Details - -The LS uses `daily-cloudcode-pa.googleapis.com/v1internal:streamGenerateContent?alt=sse` for LLM API calls (regular HTTPS+SSE, NOT gRPC). The LS binary checks a protobuf field (`detect_and_use_proxy`, field 34 on init metadata) to decide whether to honor `HTTPS_PROXY`. The extension defaults this to `UNSPECIFIED` (ignore proxy). The sed patch above changes it to `ENABLED` (value `1`), allowing the MITM wrapper's env vars to take effect. - -**Verify patch:** `grep -o 'detectAndUseProxy=[^;]*' /usr/share/antigravity/resources/app/extensions/antigravity/dist/extension.js` should show `detectAndUseProxy=1`. - -**Model IDs** (for standalone LS testing): See `docs/ls-binary-analysis.md` for the full proto enum mapping. - -### Usage Stats - -```bash +# Check intercepted usage curl -s http://localhost:8741/v1/usage | jq . + +# Cleanup +sudo ./scripts/mitm-redirect.sh uninstall ``` -Returns aggregate token counts from all intercepted API calls. +### Details + +- **UID-scoped iptables**: Only the standalone LS's traffic is intercepted (no side effects) +- **Combined CA bundle**: System CAs + MITM CA → `/tmp/antigravity-mitm-combined-ca.pem` +- **Google SSE parsing**: Extracts `promptTokenCount`, `candidatesTokenCount`, `thoughtsTokenCount` +- **Init metadata**: Protobuf field 34 `detect_and_use_proxy` set to ENABLED (1) +- See `docs/mitm-interception-status.md` for full technical details +- See `docs/ls-binary-analysis.md` for proto enum mappings and model IDs ### CLI Flags - `--no-mitm`: Disable MITM proxy entirely +- `--no-standalone`: Attach to existing LS instead of spawning standalone - `--mitm-port `: Override MITM proxy port (default: auto-assign) +- `--port `: Override proxy listen port (default: 8741) diff --git a/KNOWN_ISSUES.md b/KNOWN_ISSUES.md index 2f02dad..08f59b9 100644 --- a/KNOWN_ISSUES.md +++ b/KNOWN_ISSUES.md @@ -1,7 +1,7 @@ # Known Issues & Future Work -All critical blockers have been resolved. MITM interception is fully working -in standalone mode with UID-scoped iptables redirection. +All critical blockers have been resolved. Standalone LS with MITM interception +is fully working. Reactive streaming is implemented with polling fallback. --- @@ -11,8 +11,8 @@ in standalone mode with UID-scoped iptables redirection. **Status: SOLVED (2026-02-14)** -Previously the #1 blocker. The standalone LS (`--standalone` flag) now routes -all LLM API traffic through the MITM proxy with full decryption. +Previously the #1 blocker. The standalone LS (`--standalone` flag, now default) +routes all LLM API traffic through the MITM proxy with full decryption. **Solution:** @@ -28,6 +28,23 @@ all LLM API traffic through the MITM proxy with full decryption. **Verified:** `/v1/usage` returns per-model token usage from intercepted traffic. +### ~~Polling-Based Cascade Updates~~ + +**Status: SOLVED (2026-02-14)** + +`StreamCascadeReactiveUpdates` is now used for real-time cascade state +notifications. Falls back to timer-based polling if the streaming RPC is +unavailable. Reactive diffs also carry progressive response text and thinking +content (see `docs/panel-stream-investigation.md`). + +### ~~StreamCascadePanelReactiveUpdates — Dead End~~ + +**Status: INVESTIGATED & CLOSED (2026-02-14)** + +`CascadePanelState` only contains `plan_status` and `user_settings` — not +thinking text. The panel reactive component uses a workspace-scoped ID, not +cascade IDs. See `docs/panel-stream-investigation.md`. + --- ## 🟡 Medium (Architecture / Future Work) @@ -56,29 +73,17 @@ prompts, modifying model selection). --- -### 3. Polling-Based Cascade Updates vs Streaming RPC - -**File:** `src/api/polling.rs` - -We poll `GetCascadeTrajectorySteps` on a timer. The LS has a -`StreamCascadeReactiveUpdates` streaming gRPC method that pushes updates -in real-time. Polling works but adds latency. - -**Status:** Functional but suboptimal. - ---- - ## 🟢 Low -### 4. MITM Integration Tests +### 3. MITM Integration Tests Unit tests cover protobuf decoding and intercept parsing (18 tests pass). Integration tests for the full MITM pipeline (TLS interception, response parsing, usage recording) would be valuable now that interception works. -### 5. MITM for Main Antigravity Session +### 4. MITM for Main Antigravity Session -The current MITM only works for the standalone LS (`--standalone` mode). +The current MITM only works for the standalone LS (default mode). Intercepting the main Antigravity session's LS is harder because: - The main LS is managed by the Antigravity app, not by us @@ -86,4 +91,11 @@ Intercepting the main Antigravity session's LS is harder because: - The `mitm-wrapper.sh` approach sets env vars but the LLM client ignores `HTTPS_PROXY` unless `detect_and_use_proxy` is ENABLED via init metadata -**Workaround:** Use `--standalone` mode for all proxy traffic. +**Workaround:** Use standalone mode (default) for all proxy traffic. + +### 5. Progressive Thinking Streaming + +For extended-thinking models (Opus), thinking text may arrive progressively +across multiple reactive diffs. Currently thinking is captured atomically via +polling. Progressive streaming would require parsing reactive diff field numbers +to extract incremental thinking deltas. See `docs/panel-stream-investigation.md`. diff --git a/docs/panel-stream-investigation.md b/docs/panel-stream-investigation.md new file mode 100644 index 0000000..3275c61 --- /dev/null +++ b/docs/panel-stream-investigation.md @@ -0,0 +1,93 @@ +# Panel Stream Investigation — Dead End + +## Summary + +Investigated `StreamCascadePanelReactiveUpdates` RPC as a potential source for +progressive thinking text. **Result: dead end.** The panel state only contains +UI metadata (`plan_status`, `user_settings`), not thinking content. + +## What We Tried + +### 1. Subscribe with Cascade ID + +Attempted to subscribe to `StreamCascadePanelReactiveUpdates` using the cascade +ID as the reactive component identifier: + +```json +{ "protocolVersion": 1, "id": "" } +``` + +**Result:** `"reactive component not found"` + +### 2. Retry with Delays + +Added retry logic (3 attempts, 500ms/1s/1.5s delays) to handle the possibility +that the panel state is created asynchronously after cascade start. + +**Result:** Same error on all attempts. The panel state uses a different +identifier than the cascade ID. + +### 3. InitializeCascadePanelState Analysis + +Examined the RPC that creates panel state: + +```js +await this.client.initializeCascadePanelState({ metadata: e, userStatus: t }); +``` + +Takes workspace metadata + user status, not cascade ID. Panel state is +workspace-scoped, not cascade-scoped. + +## CascadePanelState Proto Definition + +``` +exa.cortex_pb.CascadePanelState: + field 1: plan_status (PlanStatus) + field 2: user_settings (UserSettings) +``` + +Only 2 fields — neither contains thinking text. + +## Where Thinking Text Actually Lives + +Thinking text flows through **`StreamCascadeReactiveUpdates`** (the cascade +reactive diffs that we already subscribe to): + +``` +CascadeState (jetski_cortex_pb) + └─ field 2: trajectory (gemini_coder.Trajectory) + └─ field 2: steps[] (gemini_coder.Step) + └─ field 20: planner_response (CortexStepPlannerResponse) + ├─ field 1: response (string — streams progressively) + ├─ field 3: thinking (string — raw thinking text) + ├─ field 8: modified_response (string) + └─ field 11: thinking_duration (Duration) +``` + +### Observed Behavior (gemini-3-flash) + +- Thinking text arrives as a **single atomic diff** (341 chars, one shot) +- Response text streams progressively across many diffs (26 → 1796 chars) +- Total diffs per request: ~20 + +### Current Proxy Approach + +The proxy already captures thinking text correctly through polling +`GetCascadeTrajectory` + `extract_thinking_content()`. No reactive diff +parsing needed for current functionality. + +### Future: Progressive Thinking for Extended-Thinking Models + +For Opus models with extended thinking, the thinking text _might_ arrive +progressively across multiple reactive diffs. If needed: + +1. Parse reactive diff JSON for field 3 changes within field 20 +2. Diff the thinking text between updates for incremental deltas +3. Emit `response.reasoning_summary_text.delta` events as thinking grows + +## Cleanup + +- Removed `stream_cascade_panel_updates()` from `backend.rs` +- Removed panel stream subscription + retry code from `responses.rs` +- `StreamCascadeReactiveUpdates` (cascade diffs) is still used for + real-time notification of state changes (with polling as fallback) diff --git a/docs/standalone-ls-todo.md b/docs/standalone-ls-todo.md index 7e6bfeb..4fe789a 100644 --- a/docs/standalone-ls-todo.md +++ b/docs/standalone-ls-todo.md @@ -2,7 +2,7 @@ ## Status: ✅ FULLY IMPLEMENTED (incl. MITM interception) -The standalone LS is fully working via `--standalone` flag on the proxy. +The standalone LS is the default mode. Disable with `--no-standalone`. All cascade types (sync, streaming, multi-turn) and all endpoints work. MITM interception captures real token usage from Google's API. @@ -38,7 +38,7 @@ When `scripts/mitm-redirect.sh install` has been run: sudo ./scripts/mitm-redirect.sh install # Run -RUST_LOG=info ./target/release/antigravity-proxy --standalone +RUST_LOG=info ./target/release/antigravity-proxy # Check intercepted usage curl -s http://localhost:8741/v1/usage | jq .