@estebanforge/pi-antigravity-bridge 1.3.3 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/README.md +22 -3
- package/docs/ACP-ADOPTION-PLAN.md +875 -0
- package/docs/ACP-PROTOCOL-REFERENCE.md +460 -0
- package/docs/ANTIGRAVITY-INTEGRATIONS.md +50 -551
- package/docs/ARCHITECTURE.md +50 -3
- package/docs/DEVELOPMENT.md +33 -0
- package/docs/PI-BRIDGE-GAPS.md +41 -140
- package/extensions/index.ts +126 -19
- package/package.json +8 -2
- package/src/acp/connection.ts +395 -0
- package/src/acp/driver.ts +719 -0
- package/src/acp/events.ts +250 -0
- package/src/acp/jsonrpc.ts +185 -0
- package/src/config.ts +33 -0
- package/src/diff-render.ts +15 -0
- package/src/driver-types.ts +144 -0
- package/src/driver.ts +49 -77
- package/src/mcp-server.ts +8 -1
- package/src/models.ts +9 -7
- package/src/provider.ts +140 -23
|
@@ -1,551 +1,50 @@
|
|
|
1
|
-
# Antigravity Editor Integrations: Reverse Engineered Internals
|
|
2
|
-
|
|
3
|
-
Date: 2026-08-21
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
##
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
Manifest resolution:
|
|
53
|
-
|
|
54
|
-
- If the base URL ends in `.json`, it is fetched directly as the manifest.
|
|
55
|
-
- Otherwise the service endpoint form is used: `{base}/manifests/{goos}_{goarch}.json`.
|
|
56
|
-
- A valid manifest requires a non-empty string `version` and at least one of `url`, `binaries`, or `platforms`.
|
|
57
|
-
- Platform binary lookup: `binaries["{platform}-{arch}"]` first, then a normalized fallback used by public manifests: `win32 -> windows`, `arm64/aarch64 -> arm`, so keys look like `darwin-arm`, `linux-x86_64`, `windows-x86_64`.
|
|
58
|
-
- Each binary entry can carry `url`, `sha256`, `sha512`; the downloader verifies hashes when present.
|
|
59
|
-
- Download uses `fetch` with redirect following and streams to disk with progress reporting; partial files are deleted on failure.
|
|
60
|
-
- Installed target: `~/.gemini/bin/agy` (`.exe` suffix on Windows), via `getInstalledTargetPath()`.
|
|
61
|
-
|
|
62
|
-
Version gating:
|
|
63
|
-
|
|
64
|
-
- `verifyBinaryVersion` runs `agy --version` (5 s timeout) and requires semver `gte` against a minimum version; strings containing `dev` or `HEAD` bypass the check.
|
|
65
|
-
- `getBinaryVersionString` (3 s timeout) extracts `\d+\.\d+\.\d+[^ \t\n\r]*` from combined stdout+stderr; used for the launch log line.
|
|
66
|
-
|
|
67
|
-
### 2.2 Hub process lifecycle
|
|
68
|
-
|
|
69
|
-
Launch sequence in `AntigravityServerManager.start()`:
|
|
70
|
-
|
|
71
|
-
1. Acquire binary path (auto-install if needed, with progress UI).
|
|
72
|
-
2. Allocate an ephemeral port: `net.createServer(); server.listen(0, '127.0.0.1')`, read the assigned port, close the server, reuse the port number. Classic TOCTOU port allocation; the hub prints the retry hint if it loses the race.
|
|
73
|
-
3. Build args: `['--hub', '--hub-port=${port}', '--app_data_dir=antigravity']`, then `--add-dir=${folder.fsPath}` for every workspace folder, then any user-configured `antigravity.serverArgs` (see 2.7; this key is read but never declared in the manifest).
|
|
74
|
-
4. Spawn with `cwd` = first workspace folder (fallback: extension path) and env:
|
|
75
|
-
|
|
76
|
-
```
|
|
77
|
-
{...process.env,
|
|
78
|
-
HOME: os.homedir(),
|
|
79
|
-
USERPROFILE: os.homedir(),
|
|
80
|
-
AGY_ENABLE_HUB: '1',
|
|
81
|
-
ANTIGRAVITY_VSCODE_HOST: '1',
|
|
82
|
-
ANTIGRAVITY_AUTH_SUCCESS_APP: vscode.env.uriScheme || 'vscode',
|
|
83
|
-
}
|
|
84
|
-
stdio: ['ignore', 'pipe', 'pipe']
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
5. Readiness poll: HTTP GET the root `http://127.0.0.1:${port}` every 250 ms for up to 15 s; any status code 200-499 counts as healthy (deliberately tolerant, so an auth-required 401 still means "server is up").
|
|
88
|
-
6. On exit: log `[LAUNCH ERROR] Server process exited unexpectedly with code ${code}, signal ${signal}`, clear `serverProcess`/`serverUrl` state.
|
|
89
|
-
7. `stop()` performs a graceful shutdown with a settle guard so double-stop is safe.
|
|
90
|
-
|
|
91
|
-
stdout is scanned line by line for the magic prefix `ANTIGRAVITY_OPEN_URL:`; the remainder of the line is parsed as a URI and opened with `vscode.env.openExternal`. That is how OAuth gets out of the hub process and into the browser (see 2.6). All stdout/stderr lines are echoed to the "Antigravity" output channel prefixed `[HUB STDOUT]` / `[HUB STDERR]`; lifecycle messages use `[LAUNCH]`, `[LAUNCH ERROR]`, `[INSTALL]`.
|
|
92
|
-
|
|
93
|
-
### 2.3 Webview embedding
|
|
94
|
-
|
|
95
|
-
The sidebar view `antigravity.panel` (activity bar container `antigravity-sidebar`) renders a minimal HTML shell. Key construction, from `renderIframe(webview, serverUrl, options)`:
|
|
96
|
-
|
|
97
|
-
- Base URL: `new URL(targetRoute || '', serverUrl)`; `targetRoute` allows deep-linking a specific app route.
|
|
98
|
-
- Query params set on it: `extensionView=true`, `extensionVariant=vs-code`, `useWebSocket=true`, `hostTheme=${theme}`, `enableMicrophone=false`, `workspaceUri=${first workspace folder uri}`, plus any caller-provided `extraParams`.
|
|
99
|
-
- The iframe element: `<iframe id="jetski-frame" src="${fullUrlString}" allow="clipboard-read; clipboard-write" sandbox="allow-scripts allow-same-origin allow-popups allow-forms allow-popups-to-escape-sandbox">` with an opacity transition reveal.
|
|
100
|
-
- CSP (meta tag): `default-src 'none'; frame-src ${serverUrl} https: http:; script-src ${webview.cspSource} 'unsafe-inline'; style-src ${webview.cspSource} 'unsafe-inline'; connect-src 'self' ${webview.cspSource} https: http://localhost:* http://127.0.0.1:* ws://localhost:* ws://127.0.0.1:*;`
|
|
101
|
-
- The shell also loads `bridge.js` as a webview URI (see 2.4) and applies VS Code editor font settings to the host document, forwarding later changes via `updateFontSettings` postMessages.
|
|
102
|
-
- Reveal loop: `fetch(fullUrlString)` retried up to 20 times at 1 s intervals; while retrying, the loader text reads `Connecting to Remote Antigravity tunnel (n)...` and after exhaustion `Could not connect to remote port. Please check VS Code Ports tab.` The tunnel wording implies the supported remote scenario is VS Code port forwarding of the hub port, not a second spawn on the remote host.
|
|
103
|
-
- A "compatibility modal" in the shell HTML supports an in-place "Update" action, used when the installed binary is older than the extension's minimum (ties into 2.1 version gating).
|
|
104
|
-
|
|
105
|
-
The critical design fact: `useWebSocket=true` tells the served webapp to talk to the hub over a WebSocket back to `ws://127.0.0.1:${port}` (hence the CSP `connect-src` entries). All agent control traffic, streaming, tool events, and conversation state ride that socket. The editor never sees that protocol.
|
|
106
|
-
|
|
107
|
-
### 2.4 The postMessage RPC bridge
|
|
108
|
-
|
|
109
|
-
`bridge.js` runs inside the webview shell (outside the iframe). Constants:
|
|
110
|
-
|
|
111
|
-
```
|
|
112
|
-
AGY_API_CHANNEL: "agy-ext-antigravity-api" // service: AntigravityApi, implemented by the webapp
|
|
113
|
-
EXTENSION_API_CHANNEL: "agy-ext-extension-api" // service: ExtensionApi, implemented by the editor
|
|
114
|
-
ANTIGRAVITY_IFRAME_SOURCE: "antigravity-iframe" // postMessage source tag from iframe
|
|
115
|
-
ANTIGRAVITY_EXTENSION_SOURCE: "antigravity-extension" // postMessage source tag from extension host
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Wiring (from `getExtensionApi` / `getAntigravity` / the V2 factory `Xe` in bridge.js):
|
|
119
|
-
|
|
120
|
-
- Inside the iframe, `getExtensionApi()` builds a client for `ExtensionApi` that serializes RPC frames and posts them with `window.parent.postMessage({...frame, source: "antigravity-iframe"}, "*")`, listening on `message` for replies.
|
|
121
|
-
- In the webview shell, the bridge receives iframe messages (validated by source tag and frame shape), forwards them to the extension host through the VS Code webview messaging API, and symmetrically delivers extension host messages down into the iframe. Interceptors can override any method on either service (used for the V2 event-emitter merge).
|
|
122
|
-
- RPC payloads are protobuf messages from `third_party/gemini_coder/proto/iframe_messages.proto` (proto namespace `gemini_coder.agent_ui_toolkit.iframe`), bundled as a protobuf-es generated module with an embedded `FileDescriptorProto`.
|
|
123
|
-
|
|
124
|
-
Event surface (the emitter names hard-coded in bridge.js): `onAntigravityReady`, `onDidChangeUrl`, `onDidSendChatMessage`, `onDidStartConversation`, `onDidChangeConversations`, `onKeyboardEvent`, `onMouseEvent`, `onWebviewFocused`.
|
|
125
|
-
|
|
126
|
-
### 2.5 iframe_messages.proto: complete service surface
|
|
127
|
-
|
|
128
|
-
I extracted the embedded FileDescriptorProto from bridge.js and decoded it with `protoc --decode=google.protobuf.FileDescriptorProto`. Services:
|
|
129
|
-
|
|
130
|
-
`AntigravityApi` (implemented by the Antigravity webapp; the editor calls these):
|
|
131
|
-
|
|
132
|
-
| Method | Request | Response |
|
|
133
|
-
|---|---|---|
|
|
134
|
-
| GetUrl | UrlQueryMessage | UrlResponseMessage |
|
|
135
|
-
| Navigate | UrlNavigateMessage | UrlNavigateResponse |
|
|
136
|
-
| SetEditorState | EditorStateMessage | EditorStateResponse |
|
|
137
|
-
| SetContextCategories | SetContextCategoriesRequest | SetContextCategoriesResponse |
|
|
138
|
-
| InsertSnippet | InsertSnippetRequest | InsertSnippetResponse |
|
|
139
|
-
| AddContext | AddContextRequest | AddContextResponse |
|
|
140
|
-
| SetTheme | ThemeChangeMessage | ThemeChangeResponse |
|
|
141
|
-
| AddSkills | AddSkillsMessage | AddSkillsResponse |
|
|
142
|
-
| SetComments | SetCommentsRequest | SetCommentsResponse |
|
|
143
|
-
| NewConversation | NewConversationRequest | NewConversationResponse |
|
|
144
|
-
| AddFileComment | AddFileCommentRequest | AddFileCommentResponse |
|
|
145
|
-
| DeleteFileComment | DeleteFileCommentRequest | DeleteFileCommentResponse |
|
|
146
|
-
| EditFileComment | EditFileCommentRequest | EditFileCommentResponse |
|
|
147
|
-
| TriggerSend | TriggerSendRequest | TriggerSendResponse |
|
|
148
|
-
| SendCommand | SendCommandRequest | SendCommandResponse |
|
|
149
|
-
| ListCommands | ListCommandsRequest | ListCommandsResponse |
|
|
150
|
-
| SetFileDiffs | FileDiffsChangedMessage | FileDiffsChangedResponse |
|
|
151
|
-
|
|
152
|
-
`ExtensionApi` (implemented by the editor; the webapp calls these):
|
|
153
|
-
|
|
154
|
-
| Method | Request | Response |
|
|
155
|
-
|---|---|---|
|
|
156
|
-
| OnDidChangeUrl | UrlChangeMessage | UrlChangeResponse |
|
|
157
|
-
| OnDidSendChatMessage | ChatMessageSentRequest | ChatMessageSentResponse |
|
|
158
|
-
| OnDidStartConversation | ConversationStartedRequest | ConversationStartedResponse |
|
|
159
|
-
| OnDidChangeConversations | ConversationsChangedMessage | ConversationsChangedResponse |
|
|
160
|
-
| OpenUrl | OpenUrlRequest | OpenUrlResponse |
|
|
161
|
-
| OpenFile | OpenFileRequest | OpenFileResponse |
|
|
162
|
-
| OpenArtifact | OpenArtifactRequest | OpenArtifactResponse |
|
|
163
|
-
| OpenSettings | OpenSettingsRequest | OpenSettingsResponse |
|
|
164
|
-
| OpenTerminal | OpenTerminalRequest | OpenTerminalResponse |
|
|
165
|
-
| OpenDiff | OpenDiffRequest | OpenDiffResponse |
|
|
166
|
-
| AddAgentEdit | AddAgentEditRequest | AddAgentEditResponse |
|
|
167
|
-
| CloseAllDiffZones | CloseAllDiffZonesRequest | CloseAllDiffZonesResponse |
|
|
168
|
-
| ResolveAllAgentEdits | ResolveAllAgentEditsRequest | ResolveAllAgentEditsResponse |
|
|
169
|
-
| RequestAgentEditsState | RequestAgentEditsStateRequest | RequestAgentEditsStateResponse |
|
|
170
|
-
| RequestDiffZonesState | RequestDiffZonesStateRequest | RequestDiffZonesStateResponse |
|
|
171
|
-
| StorageGetItems | StorageGetItemsRequest | StorageGetItemsResponse |
|
|
172
|
-
| StorageUpdateItems | StorageUpdateItemsRequest | StorageUpdateItemsResponse |
|
|
173
|
-
| ClipboardRead | ClipboardReadRequest | ClipboardReadResponse |
|
|
174
|
-
| OnKeyboardEvent | KeyboardEventRequest | KeyboardEventResponse |
|
|
175
|
-
| OnMouseEvent | MouseEventRequest | MouseEventResponse |
|
|
176
|
-
| OnWebviewFocused | WebviewFocusedRequest | WebviewFocusedResponse |
|
|
177
|
-
| GetBrowserNotificationPermissionState | NotificationPermissionQueryMessage | NotificationPermissionStateMessage |
|
|
178
|
-
| RequestBrowserNotificationPermission | NotificationPermissionRequestMessage | NotificationPermissionStateMessage |
|
|
179
|
-
| ShowBrowserNotification | NotificationRequestMessage | BrowserNotificationResponse |
|
|
180
|
-
| ShowNotification | ShowNotificationRequest | ShowNotificationResponse |
|
|
181
|
-
| ReportFeedbackMetadata | ReportFeedbackMetadataRequest | ReportFeedbackMetadataResponse |
|
|
182
|
-
| ProvideFeedback | ProvideFeedbackRequest | ProvideFeedbackResponse |
|
|
183
|
-
| ChangeWorkspace | ChangeWorkspaceRequest | ChangeWorkspaceResponse |
|
|
184
|
-
| ExecuteNotebookCells | ExecuteNotebookCellsRequest | ExecuteNotebookCellsResponse |
|
|
185
|
-
| GetEditorState | GetEditorStateRequest | GetEditorStateResponse |
|
|
186
|
-
| BroadcastComments | BroadcastCommentsRequest | BroadcastCommentsResponse |
|
|
187
|
-
| CanResolveConnection | CanResolveConnectionRequest | CanResolveConnectionResponse |
|
|
188
|
-
| ResolveConnection | ResolveConnectionRequest | ResolveConnectionResponse |
|
|
189
|
-
| OnAntigravityReady | OnAntigravityReadyRequest | OnAntigravityReadyResponse |
|
|
190
|
-
| LogTelemetry | LogTelemetryRequest | LogTelemetryResponse |
|
|
191
|
-
| GetContextCategories | GetContextCategoriesRequest | GetContextCategoriesResponse |
|
|
192
|
-
| QueryContextCategory | QueryContextCategoryRequest | QueryContextCategoryResponse |
|
|
193
|
-
|
|
194
|
-
Also present in the proto: a `ConnectionResolutionType` enum with at least `RESTART_LS` (ties `ResolveConnection` to restarting the language server).
|
|
195
|
-
|
|
196
|
-
Read the two tables together and the split is obvious: the editor is an IDE capability server (files, diffs, notifications, storage, clipboard, editor state) plus an event sink, and the webapp owns all agent state. `TriggerSend` is the closest thing to "programmatically send a chat message" from the editor side, and `SendCommand`/`ListCommands` expose a slash-command-like surface to the webapp.
|
|
197
|
-
|
|
198
|
-
### 2.6 Auth flow
|
|
199
|
-
|
|
200
|
-
1. Hub starts unauthenticated; when OAuth is needed it writes `ANTIGRAVITY_OPEN_URL:<url>` to stdout.
|
|
201
|
-
2. The extension opens that URL externally (`vscode.env.openExternal`).
|
|
202
|
-
3. `ANTIGRAVITY_AUTH_SUCCESS_APP` (set to the editor URI scheme, e.g. `vscode`) lets the OAuth landing page deep-link back into the editor after success.
|
|
203
|
-
4. Credentials land in `~/.gemini/oauth_creds.json` (mode 600 on this machine) alongside `google_accounts.json`; `~/.gemini/installation_id` and `state.json` hold install identity.
|
|
204
|
-
|
|
205
|
-
### 2.7 Extension manifest surface (package.json)
|
|
206
|
-
|
|
207
|
-
- Activation: `onStartupFinished` and `onCustomEditor:antigravity.artifactEditor`.
|
|
208
|
-
- Custom editors: `antigravity.artifactEditor` claims filename patterns `antigravity:/**/*.md` and `**/.gemini/*/brain/**/*.md` (agent artifacts and the agent "brain" directory get a rich viewer instead of plain markdown); `jetski.settingsEditor` claims `jetski-settings://**` (internal codename "jetski" = Antigravity).
|
|
209
|
-
- Views: single webview `antigravity.panel` in activity-bar container `antigravity-sidebar`.
|
|
210
|
-
- Commands: `showThirdPartyNotices`, `resetConversationState`, `insertSnippet` (also `insertTerminalSnippet`, bound not declared), `panel.focus`, `inlineDiff.acceptAll`, `inlineDiff.rejectAll`, `toggleInlineDiff`, `startNewConversation`, `toggleChatFocus`, `dynamic.acceptAgentStep`, `dynamic.rejectAgentStep`, `dynamic.interruptAgent`.
|
|
211
|
-
- Keybindings: `cmd+l` / `ctrl+l` context-dependent (editor selection to chat, terminal selection to chat, focus chat, close chat), `cmd+shift+l` new conversation, `alt+enter` accept agent step, `alt+shift+enter` reject, `escape` interrupt while agent running and panel focused.
|
|
212
|
-
- Declared configuration: `antigravity.enableTelemetry` (default true, telemetry goes to "Google Cloudmill"), `antigravity.enableInlineDiff` (default true, inline decorations vs side-by-side diff tab), `antigravity.channel` (default `production`, deprecationMessage "Internal channel setting").
|
|
213
|
-
- Undocumented configuration: `antigravity.serverArgs`, an array of extra CLI args appended to the hub spawn. Read by the server manager, never declared in `contributes.configuration`. Useful for enabling experimental hub flags without code changes.
|
|
214
|
-
- `buildInfo` in the manifest records the internal build system (SrcFS, depot path `//depot/...`).
|
|
215
|
-
|
|
216
|
-
## 3. Mechanism B: Agent Client Protocol (Zed and others)
|
|
217
|
-
|
|
218
|
-
### 3.1 Registry entry
|
|
219
|
-
|
|
220
|
-
Zed ships a local cache of the ACP agent registry at `~/Library/Application Support/Zed/external_agents/registry/registry.json` (upstream: `https://cdn.agentclientprotocol.com/registry/v1/latest/...`). The `antigravity-acp` entry, version 1.0.0, published with release artifacts dated 2026-08-18:
|
|
221
|
-
|
|
222
|
-
```json
|
|
223
|
-
{
|
|
224
|
-
"id": "antigravity-acp",
|
|
225
|
-
"name": "Google Antigravity",
|
|
226
|
-
"version": "1.0.0",
|
|
227
|
-
"description": "Google's AI coding agent",
|
|
228
|
-
"website": "https://antigravity.google/docs/ide/extensions",
|
|
229
|
-
"authors": ["Google LLC"],
|
|
230
|
-
"license": "proprietary",
|
|
231
|
-
"distribution": {
|
|
232
|
-
"binary": {
|
|
233
|
-
"darwin-aarch64": {
|
|
234
|
-
"archive": "https://dl.google.com/agy-extensions/releases/macos/agy-acp-server-agy_acp_server_20260818_01_RC01-darwin-arm64.zip",
|
|
235
|
-
"cmd": "./agy_acp_server.par"
|
|
236
|
-
},
|
|
237
|
-
"linux-x86_64": {
|
|
238
|
-
"archive": "https://dl.google.com/agy-extensions/releases/linux/agy-acp-server-agy_acp_server_20260818_01_RC01-linux-x86_64.zip",
|
|
239
|
-
"cmd": "./agy_acp_server.par",
|
|
240
|
-
"args": ["--uid="]
|
|
241
|
-
},
|
|
242
|
-
"linux-aarch64": {
|
|
243
|
-
"archive": "https://dl.google.com/agy-extensions/releases/linux/agy-acp-server-agy_acp_server_20260818_01_RC01-linux-arm64.zip",
|
|
244
|
-
"cmd": "./agy_acp_server.par",
|
|
245
|
-
"args": ["--uid="]
|
|
246
|
-
},
|
|
247
|
-
"windows-x86_64": {
|
|
248
|
-
"archive": "https://dl.google.com/agy-extensions/releases/windows/agy-acp-server-agy_acp_server_20260818_01_RC01-windows-x86_64.zip",
|
|
249
|
-
"cmd": "./agy_acp_server.exe"
|
|
250
|
-
},
|
|
251
|
-
"windows-aarch64": {
|
|
252
|
-
"archive": "https://dl.google.com/agy-extensions/releases/windows/agy-acp-server-agy_acp_server_20260818_01_RC01-windows-arm64.zip",
|
|
253
|
-
"cmd": "./agy_acp_server.exe"
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
Note the Linux-only `--uid=` argument: Zed appends the real uid at spawn time, presumably because a sandboxed environment can't provide one.
|
|
261
|
-
|
|
262
|
-
### 3.2 Payload
|
|
263
|
-
|
|
264
|
-
The darwin-arm64 zip is 299.9 MB compressed and contains two files:
|
|
265
|
-
|
|
266
|
-
- `agy_acp_server.par`, 792,105,680 bytes uncompressed: a Mach-O arm64 executable despite the `.par` extension. It is a self-contained Google-style Python archive (log lines reference `main.py:80`, `settings.py:300`). 755 MB on disk.
|
|
267
|
-
- `localharness_external`, 101,551,680 bytes: Mach-O arm64. Name suggests a bundled local test harness; purpose not verified.
|
|
268
|
-
|
|
269
|
-
`agy_acp_server.par` is codesigned `Developer ID Application: Google LLC (EQHXZ8M8AV)`, identifier `agy_acp_server`.
|
|
270
|
-
|
|
271
|
-
### 3.3 Verified handshake (live test)
|
|
272
|
-
|
|
273
|
-
I spawned `./agy_acp_server.par` and sent one JSON-RPC line over stdin:
|
|
274
|
-
|
|
275
|
-
```
|
|
276
|
-
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":1,"clientCapabilities":{"fs":{"readTextFile":true,"writeTextFile":true}}}}
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
Response on stdout:
|
|
280
|
-
|
|
281
|
-
```json
|
|
282
|
-
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentCapabilities":{"loadSession":true,"promptCapabilities":{"image":true,"audio":true,"embeddedContext":true},"mcpCapabilities":{"http":true,"sse":true},"sessionCapabilities":{"list":{},"resume":{}},"auth":{"logout":{}}},"authMethods":[{"description":"Log in with your Google account","id":"oauth-personal","name":"Log in with Google"},{"description":"Log in with your Gemini Enterprise account","id":"oauth-business","name":"Log in with Gemini Enterprise"},{"description":"Use an API key with Gemini Developer API","id":"gemini-api-key","name":"Gemini API key"},{"description":"Use Gemini Enterprise Agent Platform (formerly Vertex AI) with Application Default Credentials or an API key","id":"agent-platform","name":"Gemini Enterprise Agent Platform"}],"agentInfo":{"name":"antigravity-acp","title":"Google Antigravity","version":"agy_acp_server_20260818_01_RC01"}}}
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
Stderr during the run:
|
|
286
|
-
|
|
287
|
-
```
|
|
288
|
-
I0821 19:24:52.714127 ... main.py:80] Starting AGY ACP Server...
|
|
289
|
-
I0821 19:24:52.714230 ... main.py:81] Gemini home resolved to /Users/esteban/.gemini (default; $GEMINI_HOME is unset)
|
|
290
|
-
I0821 19:24:52.714300 ... settings.py:300] settings: path=/Users/esteban/.gemini/antigravity-acp/settings.json status=missing
|
|
291
|
-
I0821 19:24:52.748428 ... main.py:98] Shutting down AGY ACP Server...
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
Capability decoding:
|
|
295
|
-
|
|
296
|
-
- `loadSession: true`: clients can resume existing agent sessions.
|
|
297
|
-
- `promptCapabilities`: `image`, `audio`, `embeddedContext` all supported in prompts (multimodal input).
|
|
298
|
-
- `mcpCapabilities`: `http` and `sse` MCP servers supported.
|
|
299
|
-
- `sessionCapabilities`: `list` and `resume`, so session enumeration and resumption are first-class.
|
|
300
|
-
- `auth`: `logout` method exists; four auth methods: `oauth-personal` (Google account), `oauth-business` (Gemini Enterprise), `gemini-api-key` (Gemini Developer API key), `agent-platform` (Gemini Enterprise Agent Platform, formerly Vertex AI, via ADC or API key).
|
|
301
|
-
|
|
302
|
-
### 3.4 ACP method surface
|
|
303
|
-
|
|
304
|
-
Strings in the binary confirm the standard ACP v1 method set: `session/new` (54 hits), `session/prompt`, `session/load`, `session/update`, `session/set_mode`, `session/cancel`, plus `newSession` and `agentclientprotocol` (449 hits). This is the Zed ACP spec (agentclientprotocol.com), JSON-RPC 2.0 over stdio with `session/update` notifications for streaming agent progress.
|
|
305
|
-
|
|
306
|
-
What this means concretely: any client that implements ACP (Zed today, any custom harness tomorrow) gets a bidirectional structured channel to Antigravity. Create sessions, prompt with images, audio, embedded context, receive streaming updates, switch modes, cancel, list and resume sessions, manage MCP servers. No browser, no webapp, no SQLite scraping.
|
|
307
|
-
|
|
308
|
-
### 3.5 Runtime notes
|
|
309
|
-
|
|
310
|
-
- State lives under the shared Gemini home: `~/.gemini/antigravity-acp/settings.json`, conversations under `~/.gemini/antigravity-acp/conversations/` (created on first session).
|
|
311
|
-
- `$GEMINI_HOME` overrides the root, which is how you isolate a bridge instance from your desktop installs.
|
|
312
|
-
- The server is Python inside a frozen archive (pex-style; the .par carries readable source under `google3/`), so startup is fast enough for per-session spawns but not free; `sessionCapabilities.resume` plus a long-lived process is the sensible pattern for a bridge.
|
|
313
|
-
- The zip has no sha256 pins in the registry entry (unlike some other agents in the same registry; amp-acp pins sha256 per platform). Trust rests on dl.google.com transport plus the Apple code signature.
|
|
314
|
-
|
|
315
|
-
### 3.6 Additional live findings (second test round, 2026-08-21)
|
|
316
|
-
|
|
317
|
-
A second test round closed what the first round left open, and added some warnings.
|
|
318
|
-
|
|
319
|
-
- `session/new` requires authentication. Unauthenticated call returns a descriptive error: `Authentication required ... Either call the authenticate method (supports oauth-personal, gemini-api-key, agent-platform), or set auth.type in settings.json (~/.gemini/antigravity-acp/settings.json) to one of: oauth-personal, gemini-api-key (requires GEMINI_API_KEY env var), oauth-business (Gemini Enterprise; requires gcp.project/location), agent-platform (formerly 'vertex-ai', still accepted; requires GOOGLE_API_KEY, or a project and location from GOOGLE_CLOUD_PROJECT/...)`.
|
|
320
|
-
- Critical consequence: the ACP server ignores the desktop OAuth credentials at `~/.gemini/oauth_creds.json`. Its auth is separate, persisted under `~/.gemini/antigravity-acp/settings.json`.
|
|
321
|
-
- A stderr warning states: `Environment-based auth selection has been removed. AGY_ACP_ENABLE_OAUTH and a bare GEMINI_API_KEY no l[onger ...]`. Env-var auth was deliberately removed; settings.json or the `authenticate` method are the only paths.
|
|
322
|
-
- `session/list` works unauthenticated and returns `{"sessions":[]}` on a fresh install.
|
|
323
|
-
- Method naming: the wire method is `session/new` (snake); a `newSession` (camelCase) request yields `Method not found`. Internal handler is `new_session` in `google3/cloud/developer_experience/antigravity_extensions/acp_server/server.py` (~line 2727), auth gate `_assert_authenticated` (~line 2607), per traceback leaked on stderr.
|
|
324
|
-
- The server bundles the open-source ACP Python library from `google3/third_party/py/acp` (router/connection/task plumbing visible in tracebacks).
|
|
325
|
-
- `localharness_external` presence changes startup behavior; without it, stderr logs `Localharness not found.` (non-fatal). With it present, one run hung >30s at `initialize`.
|
|
326
|
-
- Startup reliability was nondeterministic across four runs: 2 of 4 hung at `initialize` indefinitely (with and without localharness), fixed only by killing and respawning. Treat this build (RC01) as experimental.
|
|
327
|
-
- Process shutdown: SIGTERM was ignored in tests; SIGKILL was required.
|
|
328
|
-
|
|
329
|
-
## 4. Hidden: `agy agentapi`
|
|
330
|
-
|
|
331
|
-
`agy agentapi --help` (works on 1.1.17) prints:
|
|
332
|
-
|
|
333
|
-
```
|
|
334
|
-
Usage: agentapi <command> [args]
|
|
335
|
-
|
|
336
|
-
Available Commands:
|
|
337
|
-
get-conversation-metadata <conversation_id>
|
|
338
|
-
new-conversation [--model=<flash_lite|flash|pro>] [--title=<title>] [--profile=<profile>] <prompt>
|
|
339
|
-
send-message [--title=<title>] <recipient_id> <content>
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
Behavior: `agentapi` is an HTTP client for a running Antigravity IDE instance. Without configuration it fails with `{"error": "ANTIGRAVITY_LS_ADDRESS is not set"}`. The `ANTIGRAVITY_LS_ADDRESS` environment variable must point at the live IDE language server (the `language_server` binary from `/Applications/Antigravity.app`). The `~/.gemini/antigravity-cli/bin/agentapi` shim exists so other tools (including agent-side scripts) can call it with a stable path.
|
|
343
|
-
|
|
344
|
-
This is the IDE-attach surface: create conversations in the running IDE, send messages into them, read metadata. Model selectors `flash_lite|flash|pro` confirm the current Antigravity model tiers. Recipient IDs for `send-message` presumably address agents or subagents inside a conversation (unverified beyond the help text).
|
|
345
|
-
|
|
346
|
-
## 5. Hub WebSocket protocol status
|
|
347
|
-
|
|
348
|
-
Known: the webapp served by `agy --hub` connects back over WebSocket (`useWebSocket=true`) and that socket carries the agent control plane. Unknown: the message schema, the auth handshake on the socket, and any compatibility guarantee. Nothing in the VSIX exposes it; the schema lives in the webapp bundle served by the hub and in the hub binary itself. Treat it as internal and unstable. Anyone needing programmatic control should use section 3 instead.
|
|
349
|
-
|
|
350
|
-
## 6. Security observations
|
|
351
|
-
|
|
352
|
-
- All hub and ACP traffic is loopback-only (`127.0.0.1`, ephemeral port, no TLS; the CSP allows plain `http://localhost:*` / `ws://localhost:*`).
|
|
353
|
-
- The webview CSP `connect-src ... https:` is broad: the embedded webapp may call any HTTPS origin from inside the editor.
|
|
354
|
-
- OAuth tokens sit in `~/.gemini/oauth_creds.json` with mode 600; multiple Google surfaces (CLI, IDE, extension, ACP server) share the Gemini home, so one compromise exposes all.
|
|
355
|
-
- The postMessage bridge uses `targetOrigin "*"` from inside the iframe; risk is contained by the sandbox attributes on the iframe (`allow-scripts allow-same-origin allow-popups allow-forms allow-popups-to-escape-sandbox`) but the pattern is permissive by design.
|
|
356
|
-
- Binaries carry Google LLC signatures (EQHXZ8M8AV) with the hardened runtime flag (0x10000).
|
|
357
|
-
- The extension auto-downloads and executes binaries from a Cloud Run URL gated only by that service's availability; channel `dogfood` switches to a public GCS bucket. No signature pinning observed in the downloader; hash verification exists only when the manifest supplies hashes.
|
|
358
|
-
|
|
359
|
-
## 7. Impact on this project (pi-antigravity-bridge)
|
|
360
|
-
|
|
361
|
-
This ranking predates the adversarial review; section 9 supersedes it where they disagree.
|
|
362
|
-
|
|
363
|
-
The bridge currently spawns `agy` print-mode and scrapes SQLite WAL step data with a hand-rolled protobuf decoder. The findings above give three surfaces, ranked:
|
|
364
|
-
|
|
365
|
-
1. **ACP server (recommended)**: `agy_acp_server` is the sanctioned programmatic entry point. JSON-RPC over stdio, streaming `session/update` notifications replace WAL polling, `loadSession`/`list`/`resume` replace the session watermark logic, multimodal prompts and MCP support come for free. Costs: 755 MB binary, download from `dl.google.com/agy-extensions/`, no sha256 pin in the registry. Auth flows through ACP `authenticate` with the four methods from 3.3. The existing provider architecture maps cleanly: pi request -> `session/new` + `session/prompt`, streaming events -> `session/update`, pi tool calls stay local to pi (the ACP agent runs its own tools, same model as today).
|
|
366
|
-
2. **`agy agentapi`**: for driving a live IDE Antigravity instance (workspace sessions the human can watch). Thin surface (three commands) but zero reverse engineering needed. Requires the IDE running and `ANTIGRAVITY_LS_ADDRESS` discovery.
|
|
367
|
-
3. **`agy --hub` + iframe protocol**: richest surface, but the WebSocket schema is internal and the editor bridge only makes sense inside a real webview host. Not worth it for a headless bridge. The one salvageable trick is `ANTIGRAVITY_OPEN_URL:` stdout handling as an auth pattern reference.
|
|
368
|
-
|
|
369
|
-
For session continuity work: ACP `sessionCapabilities.list/resume` removes the need for the bridge's own persisted session map on the agy side, though pi-session to ACP-session mapping would still be needed.
|
|
370
|
-
|
|
371
|
-
## 8. Reproducing these findings
|
|
372
|
-
|
|
373
|
-
```
|
|
374
|
-
# Extension bundle analysis (all offsets refer to extension.js unless noted)
|
|
375
|
-
rg -o 'ws://localhost[^"]{0,80}' extension.js # CSP entries
|
|
376
|
-
rg -o '.{150}http://127\.0\.0\.1.{150}' extension.js # backendUrl construction
|
|
377
|
-
python3 - <<'PY' # spawn spec + env around serverProcess
|
|
378
|
-
data=open('extension.js',encoding='utf-8',errors='replace').read()
|
|
379
|
-
i=data.find('this.serverProcess ='); print(data[i-3000:i+500])
|
|
380
|
-
PY
|
|
381
|
-
python3 - <<'PY' # extract embedded FileDescriptorProto from bridge.js
|
|
382
|
-
import re,base64
|
|
383
|
-
b=open('bridge.js',encoding='utf-8',errors='replace').read()
|
|
384
|
-
m=re.search(r'file_third_party_gemini_coder_proto_iframe_messages\s*=\s*[^;]*?([A-Za-z0-9+/=]{200,})',b)
|
|
385
|
-
open('/tmp/iframe_messages.fd','wb').write(base64.b64decode(m.group(1)))
|
|
386
|
-
PY
|
|
387
|
-
protoc --decode=google.protobuf.FileDescriptorProto google/protobuf/descriptor.proto < /tmp/iframe_messages.fd
|
|
388
|
-
|
|
389
|
-
# Binary identity
|
|
390
|
-
codesign -d -vv ~/.local/bin/agy
|
|
391
|
-
codesign -d -vv /Applications/Antigravity.app/Contents/Resources/bin/language_server
|
|
392
|
-
|
|
393
|
-
# ACP server live test
|
|
394
|
-
curl -sL -o agy-acp.zip 'https://dl.google.com/agy-extensions/releases/macos/agy-acp-server-agy_acp_server_20260818_01_RC01-darwin-arm64.zip'
|
|
395
|
-
unzip agy-acp.zip
|
|
396
|
-
( printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":1,"clientCapabilities":{"fs":{"readTextFile":true,"writeTextFile":true}}}}'; sleep 6 ) | ./agy_acp_server.par
|
|
397
|
-
|
|
398
|
-
# Hidden subcommands
|
|
399
|
-
agy agentapi --help
|
|
400
|
-
strings ~/.local/bin/agy | grep -E 'AGY_ENABLE_HUB|hub-port'
|
|
401
|
-
```
|
|
402
|
-
|
|
403
|
-
## Appendix: Gemini home layout observed on this machine
|
|
404
|
-
|
|
405
|
-
```
|
|
406
|
-
~/.gemini/
|
|
407
|
-
agy (not present yet on this host; VSIX installs here)
|
|
408
|
-
antigravity/ agent platform app data
|
|
409
|
-
antigravity-backup/
|
|
410
|
-
antigravity-cli/ bin/agentapi (shim), bin/webm_encoder, brain/, builtin/, cache/, log/, conversation_summaries.db
|
|
411
|
-
antigravity-ide/ IDE data
|
|
412
|
-
config/ includes memory.txtpb (agent memory)
|
|
413
|
-
history/ prompts/ skills/ tmp/
|
|
414
|
-
google_accounts.json oauth_creds.json projects.json settings.json state.json installation_id trustedFolders.json
|
|
415
|
-
GEMINI.md -> /Users/esteban/Dev/EstebanForge/AGENTS/AGENTS.md
|
|
416
|
-
```
|
|
417
|
-
|
|
418
|
-
## 9. Integration plan: ACP transport for this bridge
|
|
419
|
-
|
|
420
|
-
Status: proposed. Reviewed adversarially by an isolated reviewer with repo access on 2026-08-21; verdict was "proceed with changes". This section is the binding plan. Where it disagrees with section 7, this section wins.
|
|
421
|
-
|
|
422
|
-
### 9.1 Goal and framing
|
|
423
|
-
|
|
424
|
-
Replace the CLI print-mode transport (spawn `agy -p`, poll SQLite WAL, decode protobuf steps) with the official ACP server for pi turns, behind a flag, without weakening the working CLI path until Google ships a non-RC ACP build.
|
|
425
|
-
|
|
426
|
-
On framing: the obvious pitch, "replaces polling", is the wrong one. The poller is decent code (`PRAGMA data_version` gating, torn-read tolerance). The defects ACP actually fixes:
|
|
427
|
-
|
|
428
|
-
1. Conversation binding is unreliable on darwin: `procTreeOpenDbResolver` (src/discovery.ts) returns null on non-Linux, so a concurrent agy process can fail a turn with "could not be bound" (src/provider.ts).
|
|
429
|
-
2. Protobuf step field numbers are load-bearing and unversioned; every agy release can silently break the decoder (src/protobuf.ts, src/runner.ts).
|
|
430
|
-
3. No cancellation semantics beyond process kill.
|
|
431
|
-
4. No image input path today.
|
|
432
|
-
5. Blanket `--dangerously-skip-permissions` instead of per-action permission control.
|
|
433
|
-
|
|
434
|
-
### 9.2 Gating condition (Phase 0 exit criteria)
|
|
435
|
-
|
|
436
|
-
No bridge code gets written until Phase 0 answers all of these from primary sources. The .par contains readable Python source; read it, do not guess:
|
|
437
|
-
|
|
438
|
-
- [ ] Model selection: does `session/new` (or `session/set_mode`) accept a model parameter? Read `new_session` in `google3/cloud/developer_experience/antigravity_extensions/acp_server/server.py` (~line 2727) and the settings model.
|
|
439
|
-
- [ ] Model catalog: what models does the ACP server serve? Same set as `agy models` (incl. claude-*, gpt-oss-*) or a narrower Gemini-only set?
|
|
440
|
-
- [ ] Billing identity: does the Antigravity subscription cover ACP sessions under `oauth-personal`, or do some auth methods bill a different quota (Gemini API key, Vertex)? The four auth methods imply different billing paths.
|
|
441
|
-
- [ ] Streaming fidelity: capture real `session/update` frames from one authenticated prompt. Confirm tool_call/tool_call_update granularity, diff content blocks, usage/token counts, thinking blocks.
|
|
442
|
-
- [ ] Concurrency: do concurrent `session/prompt` calls on one connection serialize (head-of-line blocking)? Measure RSS of the server plus harness.
|
|
443
|
-
- [ ] Cancellation: does `session/cancel` actually cancel on the RC build? Measure initialize hang rate over 20 cold runs.
|
|
444
|
-
- [ ] Session resume + MCP: does `session/load` accept `mcpServers`? If not, resumed sessions would bind a dead bridge port (ports are ephemeral per process).
|
|
445
|
-
- [ ] MCP transport: is `mcpCapabilities.http` Streamable HTTP, and does it accept custom headers (the bridge's token header)?
|
|
446
|
-
|
|
447
|
-
Kill criteria: if model selection is impossible AND the catalog is narrower than `agy models`, ACP is a downgrade for this bridge regardless of protocol cleanliness. Stay on CLI and revisit when Google ships a non-RC build with session-scoped model selection.
|
|
448
|
-
|
|
449
|
-
### 9.3 Least-bad model-selection fallbacks (ranked, if no session param exists)
|
|
450
|
-
|
|
451
|
-
1. Per-session `settings.json` model override under a per-pi-process isolated `GEMINI_HOME`, written before `session/new`, serialized by a lock; model switch = new session. Acceptable only if the source shows the model is read per session, not at process start.
|
|
452
|
-
2. Honest degradation: keep CLI as default; under the flag collapse the catalog to a single entry (`antigravity/acp-default`) so the model picker never lies.
|
|
453
|
-
3. Per-model server processes: rejected (footprint multiplied by N, auth multiplied by N).
|
|
454
|
-
|
|
455
|
-
The default transport does not flip until model selection is session-scoped. Advertising `antigravity/*` slugs that ACP silently ignores is the one outcome worse than staying on the CLI.
|
|
456
|
-
|
|
457
|
-
### 9.4 Phase plan (revised after review)
|
|
458
|
-
|
|
459
|
-
**Phase 0, verify-before-build (no bridge code):**
|
|
460
|
-
|
|
461
|
-
- Unzip the `.par` (pex; readable source under `google3/`), read `new_session`, `set_mode`, settings, and auth code paths.
|
|
462
|
-
- Run one authenticated prompt (one-time `authenticate` with `oauth-personal`, browser roundtrip), capture `session/update` frames to a fixture file under `tests/fixtures/`.
|
|
463
|
-
- Run the kill-criteria checklist in 9.2. Record results in this doc.
|
|
464
|
-
- Decide the AskAntigravity question (see Phase 3) now, not later.
|
|
465
|
-
|
|
466
|
-
**Phase 1, transport behind a flag:**
|
|
467
|
-
|
|
468
|
-
- New `src/acp/client.ts`: process lifecycle, JSON-RPC stdio framing, initialize/authenticate handshake, first-turn initialize timeout with kill+retry (RC hang observed 2 of 4 runs).
|
|
469
|
-
- New `src/acp/acquire.ts`: download and cache the zip (~950 MB unpacked with `localharness_external`). Requirements: explicit user consent before first download, sha256 pinned by this repo (the registry and dl.google.com provide none), version pin with an update check, install under a bridge-owned directory. Never execute an unpinned download.
|
|
470
|
-
- Rewrite `src/runner.ts` internals to emit the same event stream from `session/update`; keep the public options/result shape.
|
|
471
|
-
- Widen the seam in the same phase, not later: extend the event union with `tool_update` (status, locations), `diff` (native old/new content; skip git reconstruction for these), `usage` (token counts, retire `zeroUsage()` fallback when present), and `mode`. Keeping only today's four event kinds would force Phase 4 rework.
|
|
472
|
-
- `src/sessions.ts`: store ACP session id; drop `lastStepIdx` (native resume); keep `lastMessageCount` (digest watermark, independent of steps); add a transport tag per record so a post-flip `get()` never feeds an agy conversation UUID to `session/load` (silent-wrong-resume risk). `narrowStoreMap` must validate the tag.
|
|
473
|
-
- `src/provider.ts`: unchanged apart from event-union expansion and digest watermark continuation.
|
|
474
|
-
- Cancellation: per-prompt deadline that (a) finalizes pi's stream, (b) marks the ACP session dead in the store, (c) kills and respawns the server (a watchdog kill nukes every concurrent session on that process; document this trade-off). `session/cancel` first, kill as backstop, not the reverse.
|
|
475
|
-
- Flag: `PI_AGY_TRANSPORT=acp` (env), CLI default unchanged.
|
|
476
|
-
|
|
477
|
-
**Phase 2, MCP discovery hop only (shrunk after review):**
|
|
478
|
-
|
|
479
|
-
- The pi dist patch (src/patcher.ts, docs/PI-INVOKETOOL-PATCH.md) does not retire. It supplies `pi.invokeTool` inside pi; `startMcpServer` hard-gates on it. ACP `mcpServers` replaces only the discovery hop: the `--add-dir` bridge config dir and `mcp_config.json` wiring. Delete that plumbing only.
|
|
480
|
-
- Register the existing bridge MCP server through `newSession mcpServers` (transport per the 9.2 checklist; fall back to keeping agy-side config if http-with-headers is unsupported).
|
|
481
|
-
- Permission handling: auto-approve only. Interactive forwarding of `session/request_permission` to pi's `ask_user_question` routes through `pi.invokeTool`, the very seam Phase 2 cannot remove; do not promise interactive permissions until pi exposes a provider UI channel.
|
|
482
|
-
|
|
483
|
-
**Phase 3, default flip and deletions (blocked on two decisions):**
|
|
484
|
-
|
|
485
|
-
- Blocked by: (a) AskAntigravity (src/ask-tool.ts) spawns `agy -p` directly and imports conversation-dir helpers from src/discovery.ts; it must either migrate to its own ACP session (second concurrent session on the shared server, see 9.2 concurrency) or the CLI path stays and deletions shrink accordingly; (b) model selection session-scoped (9.3).
|
|
486
|
-
- After unblocking: flip default to ACP, delete `src/poller.ts` and `src/protobuf.ts`, and delete `src/discovery.ts` only if ask-tool no longer imports it. Own zero or one legacy transport accordingly.
|
|
487
|
-
|
|
488
|
-
**Auth home (cross-phase requirement):**
|
|
489
|
-
|
|
490
|
-
- Add an explicit `/agy auth` command (extension entry, where `ctx.ui` exists) that runs the one-time `authenticate` flow before the first turn. `streamSimple` has no UI channel; the provider must fail with a clear "run /agy auth" error when unauthenticated.
|
|
491
|
-
- Headless/CI pi: only via `auth.type: gemini-api-key` in settings, which changes billing. Document this, do not hide it.
|
|
492
|
-
- Long-lived process: map auth-expiry errors to a reauth instruction; verify token refresh behavior in Phase 0.
|
|
493
|
-
- Isolate: run the ACP server under a bridge-owned `GEMINI_HOME` so bridge state never collides with desktop installs.
|
|
494
|
-
|
|
495
|
-
### 9.5 What does not change
|
|
496
|
-
|
|
497
|
-
- `src/models.ts` catalog discovery stays on `agy models` until 9.2 answers the catalog question.
|
|
498
|
-
- Turn digest (delta of pi-side context agy was not spawned for) stays; ACP sessions hold agy-side context exactly like conversations today.
|
|
499
|
-
- `src/diff-render.ts` stays (still needed for edit tools that do not carry native diff blocks).
|
|
500
|
-
- CLI print-mode transport stays as default and fallback until the Phase 3 conditions hold. This is time-boxed risk management on an RC build, not permanent compatibility cruft.
|
|
501
|
-
|
|
502
|
-
### 9.6 Known risks accepted
|
|
503
|
-
|
|
504
|
-
- RC build quality: initialize hangs (2 of 4 runs), SIGTERM ignored (SIGKILL needed), env auth removed mid-release cycle. Every watchdog and consent gate in this plan exists because of observed behavior, not speculation.
|
|
505
|
-
- 755 MB server + 100 MB harness footprint per pi process; measured, not assumed, in Phase 0.
|
|
506
|
-
- Zed's registry JSON is a local cache, not a contract; pin downloads to exact dl.google.com URLs with repo-computed hashes.
|
|
507
|
-
- No official stability guarantees on any of this until Google documents the ACP server beyond the Zed registry entry.
|
|
508
|
-
|
|
509
|
-
## 10. Trade-off analysis: ACP transport vs current CLI transport
|
|
510
|
-
|
|
511
|
-
Comparison of what the bridge gains and pays by moving pi turns from the CLI print-mode path (spawn `agy -p`, poll SQLite WAL, decode protobuf steps) to the ACP server. Every row is grounded in repo code or a live test; nothing speculative. The flag-and-fallback strategy in section 9 keeps this trade reversible at every phase.
|
|
512
|
-
|
|
513
|
-
### 10.1 What we win
|
|
514
|
-
|
|
515
|
-
| # | Gain under ACP | Cost today (CLI path) |
|
|
516
|
-
|---|---|---|
|
|
517
|
-
| 1 | Darwin conversation binding fixed: the session id comes from the protocol itself | `procTreeOpenDbResolver` (src/discovery.ts) returns null on non-Linux, so a concurrent agy process can fail a turn with "could not be bound" (src/provider.ts) |
|
|
518
|
-
| 2 | No dependence on unversioned protobuf internals: typed `session/update` frames under a versioned protocol | Decoder relies on magic step_type numbers (14/15/23 plus 9 tool types) and reverse-engineered field numbers; any agy release can silently break decoding (src/protobuf.ts, src/runner.ts) |
|
|
519
|
-
| 3 | Native diffs and usage: `tool_call_update` carries status, locations, old/new text, and likely token counts | Diffs are reconstructed from git after the fact (src/diff-render.ts); usage always falls back to `zeroUsage()` |
|
|
520
|
-
| 4 | Image and audio input (`promptCapabilities.image/audio/embeddedContext`) | Text-only prompts |
|
|
521
|
-
| 5 | Native session list and resume (`sessionCapabilities.list/resume`) | Bridge owns conversationId + step watermark + re-poll skip logic (src/sessions.ts) |
|
|
522
|
-
| 6 | Per-action permission requests (auto-approve mode at minimum) | Blanket `--dangerously-skip-permissions` |
|
|
523
|
-
| 7 | Streaming without the 250ms poll loop: no torn-read tolerance, no `data_version` coalescing | Modest cost only; the poller (src/poller.ts) is decent code |
|
|
524
|
-
| 8 | MCP registration through `newSession mcpServers`, no config-dir hack | `--add-dir` + per-conversation `mcp_config.json` wiring |
|
|
525
|
-
| 9 | Official protocol trajectory: public spec (agentclientprotocol.com), consumed by Zed, maintained by Google | The DB schema reverse engineering has no maintainer but this project |
|
|
526
|
-
|
|
527
|
-
### 10.2 What we pay
|
|
528
|
-
|
|
529
|
-
1. Footprint: 950 MB (755 MB server + 100 MB harness) versus the ~30 MB CLI; possibly per pi process.
|
|
530
|
-
2. Separate auth: one-time browser flow; headless pi needs `gemini-api-key`, a different billing path. The CLI today transparently reuses the desktop login.
|
|
531
|
-
3. RC stability tax: hangs at `initialize` (2 of 4 observed runs), SIGTERM ignored, env-based auth removed mid-cycle. Every watchdog in section 9 exists because of observed behavior, not speculation.
|
|
532
|
-
4. Process model inversion: one shared long-lived server versus free per-turn isolation; a hang-kill nukes every concurrent session on that process.
|
|
533
|
-
5. Download and pinning burden: no hashes published, RC-pinned URLs, Zed's registry is a cache not a contract; this repo owns consent, sha256 pinning, and updates.
|
|
534
|
-
6. Two transports to maintain during the transition, until the Phase 3 conditions hold.
|
|
535
|
-
7. Cancellation becomes negotiable: today abort is a guaranteed kill of a throwaway process; ACP relies on `session/cancel` on a build that ignored SIGTERM, so the deadline + mark-dead + respawn ladder from 9.4 is mandatory.
|
|
536
|
-
|
|
537
|
-
### 10.3 Incompatibilities (sharp edges)
|
|
538
|
-
|
|
539
|
-
1. Model selection: the gating risk. The CLI takes `--model`/`--effort` per turn; ACP v1 may pin the model per session. If so, mid-conversation model switches break (new session, history drop), and the effort tiers in src/models.ts have no known ACP analogue. Phase 0 (9.2) gates on this.
|
|
540
|
-
2. Catalog identity unverified: if the ACP server serves Gemini-only, the `antigravity/claude-*` and `gpt-oss-*` slugs die under the flag, and billing may move off the Antigravity subscription.
|
|
541
|
-
3. sessions.json schema: agy conversation UUIDs fed to `session/load` would fail or silently resume the wrong session. Requires the transport tag from 9.4; `lastStepIdx` becomes meaningless while `lastMessageCount` (digest watermark) stays.
|
|
542
|
-
4. Event union mismatch: ACP's richer blocks (tool_update, diff, usage, mode) do not fit today's four-kind `AgyEvent`; the seam must widen in Phase 1 or Phase 4 pays for it.
|
|
543
|
-
5. Conversation data location: ACP sessions live under `~/.gemini/antigravity-acp/conversations` (and under an isolated `GEMINI_HOME` if the bridge isolates). The `decode-db` script, the summaries DB, and any tooling that reads `antigravity-cli/conversations/*.db` goes blind to ACP sessions.
|
|
544
|
-
6. Permissions UX: interactive forwarding of `session/request_permission` to pi's question UI is unreachable from the provider path; auto-approve only until pi exposes a provider UI channel.
|
|
545
|
-
7. AskAntigravity divergence (src/ask-tool.ts): it spawns `agy -p` itself; either it becomes a second concurrent ACP session or it anchors the CLI path alive (Phase 3 decision).
|
|
546
|
-
|
|
547
|
-
### 10.4 Net assessment
|
|
548
|
-
|
|
549
|
-
The trade in one paragraph: we exchange self-maintained reverse engineering (fragile against agy updates, broken binding on macOS, text-only) for an official protocol with real capabilities, and we pay in footprint, auth bootstrap, and RC babysitting until Google stabilizes the ACP server. The flag plus CLI fallback keeps the trade reversible at every phase.
|
|
550
|
-
|
|
551
|
-
References: extension marketplace page (https://marketplace.visualstudio.com/items?itemName=Google.google-antigravity), Antigravity extensions doc (https://antigravity.google/docs/ide/extensions), ACP spec and registry (https://agentclientprotocol.com, https://cdn.agentclientprotocol.com/registry/v1/latest/).
|
|
1
|
+
# Antigravity Editor Integrations: Reverse Engineered Internals (historical)
|
|
2
|
+
|
|
3
|
+
Date of original research: 2026-08-21 (full 551-line analysis preserved in git
|
|
4
|
+
history; this stub summarizes what still matters). Sources: the official VSIX
|
|
5
|
+
`Google.google-antigravity_1.0.0`, Zed's external-agents registry cache, the
|
|
6
|
+
ACP release zip from `dl.google.com`, and the binaries on this machine — all
|
|
7
|
+
verified by direct inspection or live execution.
|
|
8
|
+
|
|
9
|
+
> **2026-09-04 status:** the ACP adoption this doc originally planned is
|
|
10
|
+
> SHIPPED (phases 0-3 of docs/ACP-ADOPTION-PLAN.md). Everything about the ACP
|
|
11
|
+
> server's protocol now lives in docs/ACP-PROTOCOL-REFERENCE.md (far more
|
|
12
|
+
> complete than section 3 below); the adoption decision and trade-offs live
|
|
13
|
+
> in ACP-ADOPTION-PLAN.md sections 1, 4, and 8.1 (superseding sections 9-10
|
|
14
|
+
> here). The VSIX internals below are retained as ecosystem reference only —
|
|
15
|
+
> this bridge uses Mechanism B and has no plans to touch Mechanism A.
|
|
16
|
+
|
|
17
|
+
## The three mechanisms (executive summary)
|
|
18
|
+
|
|
19
|
+
1. **Mechanism A, VS Code and JetBrains extension**: the extension spawns
|
|
20
|
+
`agy --hub` (hidden flag; `AGY_ENABLE_HUB=1`), a local HTTP server serving
|
|
21
|
+
the complete Antigravity web UI, embedded in an iframe. The webapp drives
|
|
22
|
+
the agent over an internal WebSocket protocol; IDE capabilities flow
|
|
23
|
+
through a protobuf RPC bridge over postMessage. Never documented by
|
|
24
|
+
Google.
|
|
25
|
+
2. **Mechanism B, Zed and any ACP client**: Google publishes
|
|
26
|
+
`agy_acp_server`, a dedicated binary speaking Agent Client Protocol v1,
|
|
27
|
+
JSON-RPC over stdio. The sanctioned programmatic surface — this is what
|
|
28
|
+
the bridge's ACP engine drives.
|
|
29
|
+
3. **Hidden, `agy agentapi`**: an undocumented subcommand that proxies
|
|
30
|
+
conversation control into a running Antigravity IDE language server over
|
|
31
|
+
HTTP via the `ANTIGRAVITY_LS_ADDRESS` environment variable. Documented
|
|
32
|
+
nowhere else; potential future integration surface, unexamined.
|
|
33
|
+
|
|
34
|
+
## Facts worth keeping
|
|
35
|
+
|
|
36
|
+
- Two distinct `agy` install locations exist: `~/.local/bin/agy` (the
|
|
37
|
+
self-updating CLI, what `agy install` configures) and `~/.gemini/bin/agy`
|
|
38
|
+
(where the VS Code extension's auto-installer pins its own copy). They are
|
|
39
|
+
independent and can be different versions.
|
|
40
|
+
- The VSIX auto-installer resolves binaries from
|
|
41
|
+
`https://antigravity-cli-auto-updater-974169037036.us-central1.run.app/manifests/{goos}_{goarch}.json`
|
|
42
|
+
(production) or a GCS dogfood bucket, verifies sha256/sha512 when present,
|
|
43
|
+
and installs to `~/.gemini/bin/agy`.
|
|
44
|
+
- The hub process signals OAuth via the stdout prefix
|
|
45
|
+
`ANTIGRAVITY_OPEN_URL:`; the extension opens the URI with
|
|
46
|
+
`vscode.env.openExternal`. (The bridge's ACP auth uses the same
|
|
47
|
+
loopback-redirect OAuth flow via the BROWSER-capture trick — see
|
|
48
|
+
docs/ACP-PROTOCOL-REFERENCE.md.)
|
|
49
|
+
- The ACP conversation store is per-session SQLite with opaque
|
|
50
|
+
`steps.step_payload` blobs. Never poll these DBs.
|