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
This commit is contained in:
Nikketryhard
2026-02-14 21:40:35 -06:00
parent b965be3f60
commit 940786c57f
4 changed files with 183 additions and 59 deletions

View File

@@ -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 <PORT>`: Override MITM proxy port (default: auto-assign)
- `--port <PORT>`: Override proxy listen port (default: 8741)

View File

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

View File

@@ -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": "<cascade-id>" }
```
**Result:** `"reactive component <cascade-id> 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)

View File

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