@estebanforge/pi-antigravity-bridge 1.0.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 ADDED
@@ -0,0 +1,125 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [1.0.0] - 2026-07-29
6
+
7
+ First release. A streaming Gemini model provider for pi, built on Google's
8
+ `agy` CLI, plus an MCP tool bridge that lets agy use pi's installed tools
9
+ (memory, codegraph, Slack, Asana, web, peer delegation, etc.) instead of its
10
+ own. Registers `antigravity/*` models in pi's `/model` picker and streams
11
+ responses by polling the SQLite database agy writes and decoding its
12
+ protobuf step payloads. No generated protobuf code, no native SQLite
13
+ dependency.
14
+
15
+ ### Added
16
+
17
+ #### Streaming provider
18
+
19
+ - **Gemini provider for pi.** `antigravity/gemini-*` models appear in pi's
20
+ `/model` picker, discovered live from `agy models` (with a fallback catalog
21
+ when discovery fails). Picking one routes each turn through the provider.
22
+ - **Real streaming.** A concurrent poll loop (250ms, `PRAGMA data_version`
23
+ coalescing) reads agy's conversation DB while agy is still running, so text
24
+ and tool activity arrive during the turn, not replayed at exit. Three
25
+ trailing polls catch the final flush; abort skips them for prompt cancel.
26
+ - **Hand-rolled protobuf decoder** for agy's `step_payload` blobs: agent text
27
+ at field 20.1, tool calls at field 5.4 (name@2/9, input@3), title at 30.4.
28
+ Field numbers verified against real agy 1.1.7 databases and cross-checked
29
+ against the shindgew/agy-acp and shubzkothekar/antigravity-acp decoders.
30
+ Unknown fields are skipped per protobuf wire rules.
31
+ - **Multi-turn conversations.** A pi session is bound to an agy conversation
32
+ id (persisted at `~/.pi/agent/antigravity-bridge/sessions.json`) and
33
+ resumed via `--conversation <id>`. agy keeps its own history; only the latest
34
+ user message is sent each turn. Atomic, dirty-key-merged writes survive
35
+ concurrent pi processes.
36
+ - **`/agy` slash command** with status, an interactive picker (mode,
37
+ permissions), and direct subcommands. Settings persist to
38
+ `~/.pi/agent/antigravity-bridge/config.json`.
39
+ - **Configurable execution mode** (`accept-edits` default, or `plan`) and
40
+ permissions, overridable by `AGY_MODE` /
41
+ `AGY_SKIP_PERMISSIONS` env vars.
42
+ - **`--dangerously-skip-permissions` passed by default.** Technically
43
+ required: `accept-edits` auto-approves file edits but not shell commands,
44
+ so a `run_command` would otherwise hang on an unanswerable `y/n` prompt in
45
+ non-interactive `-p` mode (upstream google-antigravity/antigravity-cli#318).
46
+ Consistent with pi's own no-confirmation-gate design.
47
+ - **Conversation-id discovery** by snapshot/diff of agy's conversations dir
48
+ (agy `-p` never prints the id). Refuses to bind on ambiguity.
49
+ - **Tool-activity visibility.** agy's closed tool loop surfaces in pi's
50
+ thinking panel as `[agy tool: <name>]`. agy edits/commands land on disk;
51
+ pi's tools never fire (architectural wall, documented).
52
+ - **Cross-turn context continuity.** agy keeps its own history, but it now
53
+ also receives pi-side context it wasn't spawned for (compaction summaries,
54
+ turns from other providers or pi's own tools) as a brief digest with the
55
+ prompt each turn, so multi-turn work and provider switches stay coherent.
56
+ - **Edit diffs in the thinking stream.** When agy writes a file, pi's thinking
57
+ panel shows a line-numbered diff of the change as it lands. Works across
58
+ nested repos, submodules, and multi-repo workspaces; degrades cleanly for
59
+ binary, off-repo, or unchanged files.
60
+ - **Tests.** Unit tests for the protobuf decoder; a
61
+ deterministic fake-agy test that asserts events stream during the run and
62
+ that abort returns promptly (guards the "provider did not actually stream"
63
+ regression class).
64
+
65
+ #### MCP tool bridge (agy -> pi tools)
66
+
67
+ - **`AskAntigravity` tool** is now provided by this extension (ported from
68
+ `pi-ask-antigravity` v1.1.0). The bridge ships BOTH the streaming
69
+ antigravity provider AND the one-shot delegation tool - the same combined
70
+ shape as `pi-claude-bridge`. Model aliases (flash/pro/gemini, tier/version
71
+ qualifiers), one-shot vs continued-conversation modes, and the `mode`/
72
+ `digest` params are all preserved.
73
+ - **Cross-extension clash avoidance.** When both this bridge and
74
+ `pi-ask-antigravity` are installed, the bridge wins and
75
+ `pi-ask-antigravity` silently registers nothing (it detects the bridge via
76
+ package resolution, order-independent). The `AskAntigravity` tool is never
77
+ duplicated.
78
+ - **`/agy` gains `model` and `thinking` subcommands + picker rows** for the
79
+ tool's defaults (alias flash/pro/gemini; tier low/medium/high). Persisted
80
+ alongside the provider settings in `config.json`.
81
+ - **MCP tool bridge.** agy runs as pi's Gemini provider; the bridge exposes
82
+ pi's installed tools to agy over a Streamable HTTP MCP server so agy can
83
+ call them (memory, codegraph, Slack, Asana, web, peer delegation, etc.)
84
+ instead of doing the work itself. Per-pid config directory at
85
+ `~/.pi/agent/antigravity-bridge/agy-mcp-<pid>/`, written into agy's
86
+ `--add-dir` path so agy reads `.agents/mcp_config.json` from there.
87
+ Global agy config is never touched.
88
+ - **Capability gate.** The bridge checks for `pi.invokeTool` at startup and
89
+ silently no-ops the MCP server if the patch is absent (clean pi reinstall
90
+ drops the patch; bridge still runs as a provider).
91
+ - **Tool filtering.** Builtin tools that agy already has natively (read,
92
+ write, edit, bash, ls, grep, find) are filtered out so agy does not double
93
+ up on its own equivalents; `AskAntigravity` is filtered to avoid recursion.
94
+ Every other registered tool is exposed.
95
+ - **Security.** Shared-secret `x-bridge-token` header, request body size cap,
96
+ per-call `AbortController` so agy can cancel in-flight tool calls, full
97
+ request handler `try/catch`, rawHeaders rewrite for Hono's protocol clamp.
98
+
99
+ #### Documentation
100
+
101
+ - `docs/ARCHITECTURE.md` - bridge design and per-pid config layout.
102
+ - `docs/DEVELOPMENT.md` - how to run tests, rebuild, and iterate.
103
+ - `docs/PI-INVOKETOOL-PATCH.md` - the local patch to pi's dist that the
104
+ bridge depends on.
105
+ - `docs/PI-BRIDGE-GAPS.md` - capability gaps, as actionable tasks (G1-G10).
106
+ G1 (conversation history) and G8 (edit diffs) are closed via no-patch,
107
+ provider/decode-side work; the rest (streaming progress, UI primitives,
108
+ MCP-server double-exposure, etc.) remain open, triaged by effort and payoff.
109
+
110
+ ### Fixed
111
+
112
+ - **Protocol-version clamp for the MCP bridge.** agy negotiates a protocol
113
+ version newer than the SDK this bridge ships: `@modelcontextprotocol/sdk`
114
+ 1.29.0 tops out at `2025-11-25`, but agy sends `2026-07-28`. `initialize` is
115
+ exempt from the transport's header check and the SDK downgrades its body
116
+ version itself, yet every follow-up (`tools/list`, `tools/call`,
117
+ `notifications/initialized`) is validated against the `MCP-Protocol-Version`
118
+ header and rejected with `400 Bad Request: Unsupported protocol version`,
119
+ surfaced as a `transport-error` on every turn. The bridge now rewrites any
120
+ unsupported header value to `LATEST_PROTOCOL_VERSION` before the transport
121
+ sees it. The server is stateless (a fresh transport per request), so it
122
+ cannot track the negotiated version across requests; clamping to LATEST is
123
+ the correct downgrade. Hono's Node->Web conversion reads `req.rawHeaders`,
124
+ not the parsed `req.headers` object, so the value is rewritten in the raw
125
+ array (and mirrored on `req.headers` for other readers).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 EstebanForge
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,153 @@
1
+ # pi-antigravity-bridge
2
+
3
+ A Gemini model provider **and** the `AskAntigravity` delegation tool for [pi](https://github.com/earendil-works/pi-coding-agent), both built on Google's `agy` CLI. It registers `antigravity/gemini-*` models in pi's `/model` picker (streaming), and provides the `AskAntigravity` tool for one-shot delegation - the same combined shape as `pi-claude-bridge`.
4
+
5
+ <img width="3024" height="1774" alt="image" src="https://github.com/user-attachments/assets/9fc2f368-7292-4dde-851b-db2bd579263c" align="center" />
6
+
7
+ If you also have [`@estebanforge/pi-ask-antigravity`](https://github.com/EstebanForge/pi-ask-antigravity) installed, this bridge takes over: pi-ask-antigravity detects the bridge and registers nothing, so the `AskAntigravity` tool is never duplicated.
8
+
9
+ ## What it does
10
+
11
+ You pick a Gemini model in pi's `/model` picker. pi routes each turn through this provider. The provider spawns `agy -p` in your workspace, polls the SQLite database agy writes as it works, decodes the protobuf step payloads, and streams the agent text back into pi token by token.
12
+
13
+ Multi-turn works. The provider binds a pi session to an agy conversation id (persisted under `~/.pi/agent/antigravity-bridge/sessions.json`) and resumes it on the next turn via `--conversation <id>`. agy keeps its own history, so only the latest user message is sent each turn.
14
+
15
+ ## What it cannot do
16
+
17
+ agy runs its own closed tool loop (`read_file`, `write_file`, `edit_file`, `run_command`) against `--add-dir`. By default pi's own `read`/`write`/`edit`/`bash` do not fire for agy's file and shell work, and that is fine: agy has capable native equivalents. What used to be a hard wall for everything else is now bridgeable; see [MCP tool bridge](#mcp-tool-bridge-agy-uses-pis-tools) below.
18
+
19
+ Residual limits (with or without the bridge):
20
+
21
+ - agy's own edits still land directly on disk; pi's inline diff review does not engage for them.
22
+ - agy commands run without per-action approval, same as every other tool in pi. See [Permissions](#permissions) below.
23
+ - No token usage or cost accounting. agy does not expose token counts, so usage reports as zero.
24
+
25
+ ## MCP tool bridge (agy uses pi's tools)
26
+
27
+ While agy is the active model it normally cannot see pi's universe of extensions: agentmemory, codegraph, web search, slack/asana, the `Ask*` delegations, and any other installed pi tool. This extension optionally bridges that gap.
28
+
29
+ When the capability is present the bridge starts a localhost MCP server inside pi's process. `tools/list` returns pi's registered tools (built-in file/shell tools and `AskAntigravity` are filtered out), and `tools/call` executes them via `pi.invokeTool()`. agy discovers the server through a per-invocation config: the bridge writes `.agents/mcp_config.json` into a bridge-controlled dir (`~/.pi/agent/antigravity-bridge/agy-mcp-<pid>/`) and the provider passes that dir as an extra `--add-dir` when it spawns agy. The user's global agy config (`~/.gemini/config/mcp_config.json`) is never touched, so standalone agy outside pi is unaffected.
30
+
31
+ **Capability requirement.** `pi.invokeTool()` is not (yet) part of upstream pi. On first load with the patch missing, the extension **asks you once** whether to apply it (see [Install](#install)); once applied it activates after a full `pi` restart. Without it the bridge detects the missing capability at load time, skips the MCP server, and everything else works unchanged. See [docs/PI-INVOKETOOL-PATCH.md](docs/PI-INVOKETOOL-PATCH.md) for what the patch is, and `/agy patch status|apply|restore` to inspect, force, or undo it.
32
+
33
+ **Recursion safety.** Only the provider's agy receives the extra `--add-dir`. The `AskAntigravity` tool spawns its own agy with just the workspace, so that inner agy starts plain (no pi tools) and cannot re-enter. `AskAntigravity` is also filtered from the exposed tool list. Standalone agy is unaffected because nothing is written to its global config.
34
+
35
+ **Cost / fan-out.** Every registered pi tool except builtins (and `AskAntigravity`) is exposed, including other delegation tools like `AskClaude`/`AskCodex`. agy can therefore chain into other models via the bridge, which is a new cost/time fan-out vector that did not exist before this feature.
36
+
37
+ **Security.** The MCP server binds to `127.0.0.1` only and requires a per-session shared-secret header (`x-bridge-token`) that agy sends from the bridge config; browsers cannot set custom headers on a simple cross-origin POST, so this blocks web CSRF against the loopback server. Request bodies are size-capped. This is intended for single-user developer machines: any local process running as the same user can read the token from the per-pid config and call the exposed tools, so do not run it on a shared host where you do not trust other same-user processes.
38
+
39
+ ## Install
40
+
41
+ > ⚠️ **Heads-up: this extension patches your `pi` install.** When it loads and
42
+ > the running pi lacks `pi.invokeTool()`, it **asks you once** whether to edit
43
+ > files inside your globally-installed `@earendil-works/pi-coding-agent/dist/`
44
+ > (adding one method) to enable the MCP tool bridge. This is, for now, the only
45
+ > way agy can use Pi internal tooling.
46
+ >
47
+ > - **Yes** → applies the patch (reversible via `/agy patch restore`) and tells
48
+ > you to restart pi. The bridge starts on the next launch.
49
+ >
50
+ > - **No** → it remembers your choice and stays silent; it won't ask again until
51
+ > you run `/agy patch apply`. The provider and AskAntigravity tool keep working;
52
+ > only the MCP tool bridge stays off.
53
+ >
54
+ > - The apply is **idempotent & safe** (only what's missing; aborts cleanly if a
55
+ > pi update moved the code), **backed up** (under
56
+ > `~/.pi/agent/antigravity-bridge/pi-patch-backup/`), and **self-healing** (a
57
+ > `pi` reinstall/update wipes `dist/`; re-applied on the next start).
58
+ >
59
+ > The patch only takes effect after a **full `pi` restart** (quit + relaunch) —
60
+ > `/reload` is **not** enough, because pi caches its compiled core for the
61
+ > process.
62
+ >
63
+ > Details for the patch can be found in [docs/PI-INVOKETOOL-PATCH.md](docs/PI-INVOKETOOL-PATCH.md).
64
+
65
+ Install with pi's package manager:
66
+
67
+ ```bash
68
+ pi install npm:@estebanforge/pi-antigravity-bridge
69
+ ```
70
+
71
+ Requires the **`agy` CLI** installed and authenticated. If you don't have it, follow Google's [official install guide](https://antigravity.google/docs/cli/install) for your platform, then run `agy` once to complete Google OAuth. The extension resolves `agy` on `$PATH`, or via the `AGY_BIN` environment variable.
72
+
73
+ Also requires Node 22.5 or newer (uses the built-in `node:sqlite`).
74
+
75
+ ## Usage
76
+
77
+ Pick a model and talk to pi as usual:
78
+
79
+ ```
80
+ /model
81
+ ```
82
+
83
+ Look for the models namespaced as: antigravity
84
+
85
+ Or specify a model directly:
86
+
87
+ ```
88
+ /model antigravity/gemini-3-6-flash-medium
89
+ ```
90
+
91
+ Model ids are slugified from the `agy models` output (`Gemini 3.6 Flash (Medium)` becomes `gemini-3-6-flash-medium`). Discovery runs once at extension load. Run `/reload` after an `agy update` to refresh the list.
92
+
93
+ If `agy models` fails at load (binary missing, auth not done, network stall), a fallback catalog still populates the picker so you get a clear runtime error instead of an empty list.
94
+
95
+ ### The /agy command
96
+
97
+ `/agy` configures the provider at runtime. Settings persist to `~/.pi/agent/antigravity-bridge/config.json` and take effect on the next turn.
98
+
99
+ ```
100
+ /agy status, or open the mode/permissions/model/thinking picker (TUI)
101
+ /agy status print current mode, permissions, model + session counts
102
+ /agy mode plan review-only: agy plans but writes nothing
103
+ /agy mode accept-edits agy applies edits directly (default)
104
+ /agy permissions on|off auto-approve / prompt for tool calls (see warning)
105
+ /agy model flash|pro|gemini default model alias for the AskAntigravity tool
106
+ /agy thinking low|medium|high default thinking tier for the AskAntigravity tool
107
+ /agy patch status|apply|restore inspect / force / undo the pi.invokeTool patch
108
+ /agy clear drop all session bindings (force fresh conversations)
109
+ ```
110
+
111
+ ### Permissions
112
+
113
+ pi itself has no built-in approval gate. Unlike codex, claude, or agy running interactively, pi does not prompt you to confirm each tool action before it runs. That is the host environment this extension lives in.
114
+
115
+ Because agy in `-p` (print) mode cannot answer an interactive `y/n` prompt, this extension passes `--dangerously-skip-permissions` by default. It is technically necessary: `accept-edits` auto-approves file edits but not shell commands, so a `run_command` would otherwise hang forever waiting for a prompt nothing can answer (upstream [google-antigravity/antigravity-cli#318](https://github.com/google-antigravity/antigravity-cli/issues/318)). The net effect is that agy executes commands the same way pi already executes your other tools: without per-action review.
116
+
117
+ If you want agy to execute nothing, use `/agy mode plan`. Do not combine `--sandbox` with skip-permissions ([#36](https://github.com/google-antigravity/antigravity-cli/issues/36)).
118
+
119
+ ### Run pi inside a sandbox
120
+
121
+ For isolation when running any agent that executes commands without a confirmation gate, run pi inside [**construct-cli**](https://github.com/EstebanForge/construct-cli) - EstebanForge's sandbox for AI agents. Isolated container, no path escape, ephemeral filesystem, `strict` / `offline` network modes, secret redaction. The blast radius of a bad command stays in the container, not your host. Install and usage instructions are in that repo.
122
+
123
+ ### Environment variables
124
+
125
+ | Variable | Purpose |
126
+ | --- | --- |
127
+ | `AGY_BIN` | Path to the agy binary. Defaults to `agy` on PATH. |
128
+ | `AGY_EXTRA_ARGS` | Extra args appended to every invocation. Whitespace-split. |
129
+ | `AGY_CONVERSATIONS_DIR` | Override the conversations DB directory. |
130
+ | `AGY_MODE` | Override execution mode: `plan` (review-only) or `accept-edits` (default). Wins over the config file. |
131
+ | `AGY_SKIP_PERMISSIONS` | `1`/`true` (default) to pass `--dangerously-skip-permissions` so commands don't hang on an unanswerable prompt in `-p` mode. `0`/`false` to prompt (hangs any `run_command` non-interactively). Wins over the config file. |
132
+ | `AGY_DEFAULT_MODEL` | Default model alias for the `AskAntigravity` tool (`flash`/`pro`/`gemini`, or a tier/version qualifier). Wins over the config file. |
133
+ | `AGY_DEFAULT_THINKING` | Default thinking tier for the `AskAntigravity` tool: `low`/`medium`/`high`. Anything else falls back to `medium`. Wins over the config file. |
134
+
135
+ ## Development
136
+
137
+ Build, test, and debug instructions live in [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md). For the internal architecture (decode pipeline, polling, conversation discovery) see [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
138
+
139
+ ## Terms of Service notice
140
+
141
+ Google's [Antigravity ToS](https://antigravity.google/terms) (Section 6) prohibits accessing the service "in connection with products not provided by us", and names as its example using tools like Hermes/OpenClaw with Antigravity OAuth. That targets reusing your credentials in a non-Google harness that calls Google's backend directly.
142
+
143
+ This extension does not do that. It spawns the official, unmodified `agy` binary as a subprocess; `agy` performs its own OAuth and makes its own calls to Google. This code never sees, extracts, or reuses your token, and never contacts Antigravity's backend. It only reads the local SQLite file `agy` writes. From Google's server-side view there is no signal that distinguishes "agy launched by pi" from "agy launched by a terminal, an IDE task runner, or cron": same signed binary, same authenticated calls.
144
+
145
+ Google's reported enforcement to date (the February 2026 suspensions) targeted token-reuse tools, not spawning the official CLI.
146
+
147
+ pi-antigravity-bridge practical risk is low, near zero. But not zero: the "in connection with" wording is broad, and Google can suspend accounts at its discretion regardless of whether a breach is provable. Grey area. Safe for now. You should read "news" about this online from time to time.
148
+
149
+ This is engineering analysis, not legal advice. Use against your own Antigravity account at your own risk; I am not responsible for any consequence to your account.
150
+
151
+ ## License
152
+
153
+ MIT.
@@ -0,0 +1,48 @@
1
+ # Architecture
2
+
3
+ How the provider works internally. For build/test/debug workflow see [DEVELOPMENT.md](./DEVELOPMENT.md).
4
+
5
+ ## Module map
6
+
7
+ ```
8
+ extensions/index.ts registerProvider + model discovery + /agy command (entry)
9
+ src/provider.ts streamSimple: pi Context -> agy turn -> pi event stream
10
+ src/runner.ts spawn agy -p, concurrent poll loop, abort/timeout, emit events
11
+ src/poller.ts read-only node:sqlite handle over one conversation DB
12
+ src/protobuf.ts hand-rolled varint walker + extractors (field 20.1 = text)
13
+ src/discovery.ts snapshot/diff to bind the conversation id agy never prints
14
+ src/models.ts agy models -> pi Model projection
15
+ src/sessions.ts atomic JSON store: pi session -> agy conversation + last step
16
+ src/config.ts persisted runtime config (mode, permissions, model/thinking defaults)
17
+ src/ask-tool.ts the AskAntigravity one-shot delegation tool (model/thinking defaults)
18
+ src/mcp-server.ts MCP tool bridge: exposes pi's tools to agy over Streamable HTTP
19
+ src/patcher.ts Auto-applies the pi.invokeTool local patch to enable the MCP tool bridge
20
+ src/diff-render.ts render agy's file edits as git diffs in pi's thinking stream
21
+ ```
22
+
23
+ No generated protobuf code, no native SQLite dependency.
24
+
25
+ ## The decode pipeline
26
+
27
+ agy writes each step to a SQLite row with a protobuf blob in `step_payload`. The text we want lives at field 20, submessage field 1. Tool calls live at field 5, submessage field 4, with the name at field 2 or 9 and the raw input JSON at field 3. These field numbers are reverse-engineered facts (cross-checked against the shindgew/agy-acp and shubzkothekar/antigravity-acp decoders, then verified against real databases on agy 1.1.7). They are load-bearing. Unknown fields are skipped per protobuf wire rules, so a future agy that adds fields will not break decoding.
28
+
29
+ ### Step types
30
+
31
+ | step_type | meaning |
32
+ | --- | --- |
33
+ | 15 | agent text (payload field 20 -> field 1) |
34
+ | 14 | thinking |
35
+ | 23 | title update (payload field 30 -> field 4) |
36
+ | 5, 7, 8, 9, 17, 21, 33, 101, 132, 138 | tool run (payload field 5 -> field 4 -> name@2/9, input@3) |
37
+
38
+ Status 3 = complete; anything else = in-flight.
39
+
40
+ ## Polling
41
+
42
+ The runner spawns `agy -p`, then polls its conversation DB on a 250ms interval concurrent with the running process (this is what makes the provider actually stream, not replay at exit). Each tick issues a single `PRAGMA data_version` check; while agy is thinking and has not committed, that check is false and no row SELECT runs at all (neither the new-row read nor the in-place re-read of the step agy is currently extending). Only when a commit lands do both reads fire in one pass. Three trailing polls at 100ms after agy exits catch the last flush; on abort these are skipped so cancellation is prompt.
43
+
44
+ ## Conversation id discovery
45
+
46
+ agy `-p` does not print the conversation id. On a fresh run the runner snapshots the `*.db` stems in the conversations dir before spawn, then diffs after. Exactly one new file = ours; zero new = refuse to bind (surfaced as an error rather than a guess).
47
+
48
+ When **more than one** new file appears (a concurrent agy or subagent started in parallel), the runner passes its spawned pid to `newConversationId`, which scans the process tree's open file descriptors (`/proc/<pid>/fd` on Linux) to find the single candidate `.db` our own agy is writing to. This is the authoritative disambiguator: mtime cannot separate two *active* concurrent runs, and the user-message payload (`step_type 98`) is undocumented and deeply nested, so content-matching would risk a silent misbind. When the pid is unavailable, the platform is not Linux, the process has already exited (the AskAntigravity tool binds post-exit), or the scan itself is ambiguous, discovery fails safe to null rather than guessing.
@@ -0,0 +1,64 @@
1
+ # Development & Debugging
2
+
3
+ How to build, test, and debug this extension outside pi.
4
+
5
+ ## Build, test, typecheck
6
+
7
+ ```bash
8
+ npm install
9
+ npm test # unit tests (protobuf decoder, runner streaming/abort, mcp bridge)
10
+ npm run build # tsc --noEmit type check
11
+ ```
12
+
13
+ The integration scripts below spawn a real `agy` process and need a logged-in account. The unit tests (`npm test`) need neither.
14
+
15
+ ## Standalone scripts
16
+
17
+ These exercise the pipeline without pi. Useful for isolating where a bug lives (decoder? poller? provider? pi loader?).
18
+
19
+ ```bash
20
+ # Decode any conversation DB and print agent text + tool calls.
21
+ # Accepts a UUID (resolved against ~/.gemini/antigravity-cli/conversations/)
22
+ # or an absolute path. Fastest way to check the protobuf decoder against
23
+ # real data. No agy spawn, no network.
24
+ npm run decode-db -- <uuid-or-path>
25
+
26
+ # Spawn agy and stream decoded events to stdout with timestamps. Proves the
27
+ # concurrent poll loop actually streams (events arrive during the run, not
28
+ # only at exit). Use this to reproduce a hang or a missing-event bug.
29
+ npm run run-agy -- "Say hello"
30
+ npm run run-agy -- --model "Gemini 3.6 Flash (Medium)" --mode plan "Review src/protobuf.ts"
31
+ npm run run-agy -- --conversation <uuid> "follow up" # resume a turn
32
+
33
+ # Drive the provider's streamSimple directly (no pi TUI) and assert the
34
+ # full event lifecycle: start -> text_start -> text_delta -> text_end ->
35
+ # done. The closest thing to a pi turn without pi.
36
+ npx tsx scripts/test-provider.ts
37
+
38
+ # Load the extension through a mock ExtensionAPI and assert registerProvider
39
+ # + registerCommand (/agy) fire with the right shape. No agy spawn.
40
+ npx tsx scripts/test-extension.ts
41
+
42
+ # Load the extension through pi's REAL loader and confirm the antigravity/*
43
+ # models register. This is the in-pi smoke test.
44
+ npm run smoke:pi
45
+ ```
46
+
47
+ ## Debugging a hang or "stuck" turn
48
+
49
+ Most "stuck" reports trace to one of:
50
+
51
+ 1. **agy blocked on a permission prompt.** `accept-edits` auto-approves file edits but NOT shell commands. Any `run_command` prompts `y/n`, which hangs forever in non-interactive `-p` mode. The provider passes `--dangerously-skip-permissions` by default to avoid this. If you turned it off (`/agy permissions off`), that is why. See the README Permissions section.
52
+ 2. **agy never started.** Check `AGY_BIN` is on PATH (or set explicitly). The runner swallows spawn ENOENT into the result's stderr, surfaced by the provider as an error event.
53
+ 3. **Conversation id never bound.** The snapshot/diff discovery refuses to bind if more than one new `.db` appears (ambiguous). `scripts/decode-db.ts` against the suspected DB confirms agy wrote steps.
54
+ 4. **Print-mode environmental hang.** `pi -p` can hang with zero output in some containers (upstream [google-antigravity/antigravity-cli#318](https://github.com/google-antigravity/antigravity-cli/issues/318)). It affects built-in providers too, not this extension. Validate the turn with `scripts/test-provider.ts` instead.
55
+
56
+ ## Regression tests worth knowing
57
+
58
+ - `tests/protobuf.test.ts` - pure decoder math (varint, field walking, nested submessages).
59
+ - `tests/runner-streaming.test.ts` - a fake agy writes rows on a delay; asserts events arrive DURING the run (not all at exit) and that abort returns promptly. This is the test that guards the "provider did not actually stream" class of bug.
60
+ - `tests/mcp-server.test.ts` - the MCP tool bridge end-to-end against a real (port 0) server: capability gate, per-pid config lifecycle, shared-secret token gate, 1 MB body cap, protocol-version clamp, and tool-call dispatch through `pi.invokeTool`. Guards the security and transport-correctness of the bridge.
61
+
62
+ ## Module map
63
+
64
+ See [ARCHITECTURE.md](./ARCHITECTURE.md) for the module map and the decode-pipeline / polling internals.
@@ -0,0 +1,186 @@
1
+ # pi-antigravity-bridge: capability gaps
2
+
3
+ Status of the MCP tool bridge between agy (Antigravity CLI, used as pi's Gemini
4
+ provider) and pi's extension/builtin tools. This doc tracks **open gaps only**.
5
+ Shipped work lives in `CHANGELOG.md` (most recently: a conversation-history
6
+ delta digest, and git-sourced edit diffs). Ideas that were weighed and rejected
7
+ are listed at the end under "Discarded ideas".
8
+
9
+ ## What the bridge already does
10
+
11
+ agy -> bridge MCP server -> `pi.invokeTool(name, args)` -> pi's tool registry
12
+ -> result returned through MCP -> agy model context. The chain was verified
13
+ end-to-end with `memory_search` and `ask_user_question`. The bridge exposes
14
+ pi's extension tools (builtins are filtered out since agy has native
15
+ equivalents; `AskAntigravity` is filtered to avoid recursion).
16
+
17
+ What this means in practice: agy can read/write files, use memory, navigate
18
+ code with codegraph, search the web, post to Slack, create Asana tasks, spawn
19
+ subagents, prompt the user with `ask_user_question`, and delegate to peer
20
+ reviewers (Claude, Codex, Antigravity), all by going through pi's installed
21
+ tooling instead of its own. Tools run in pi's process with pi's own credentials,
22
+ so a secret never crosses the bridge. agy's file edits surface as git-sourced
23
+ diffs in pi's thinking stream, and each turn agy receives a delta digest of
24
+ pi-side context (compaction summaries, other-provider turns) it was not spawned
25
+ for.
26
+
27
+ ## Open gaps
28
+
29
+ Three gaps remain. Every one either needs a pi dist patch (the maintenance cost
30
+ the shipped gaps deliberately avoided) or a verify-before-build step. Ordered by
31
+ former G-number.
32
+
33
+ **Numbering note:** the G1/G2/G3 labels below are this living doc's renumbered
34
+ open set, NOT the historical G1-G10 in `CHANGELOG.md`. In that historical list
35
+ G1 = conversation-history digest and G8 = edit diffs; both are now closed
36
+ (shipped in 1.0.0) and so dropped from here, leaving the three renumbered below.
37
+
38
+ ---
39
+
40
+ ### G1. Stream progress for long tool calls [HIGH IMPACT]
41
+
42
+ **Status:** Open
43
+ **Objective:** Stop blocking on long tools (`web_research`, `librarian`,
44
+ `codegraph_explore` on big repos). Push partial output back to agy so pi's UI
45
+ shows live progress instead of a frozen spinner.
46
+
47
+ **Why:** Today the bridge blocks until the tool returns. The user has no signal
48
+ that work is happening. This is partly a pi-side gap (most long tools do not
49
+ emit progress) and partly a bridge-transport gap.
50
+
51
+ **Scope:**
52
+ - `docs/PI-INVOKETOOL-PATCH.md`: expose pi's progress bus.
53
+ - `src/mcp-server.ts`: switch from blocking `invokeTool` to a streaming RPC
54
+ using MCP `notifications/progress` (or a `pi_tool_progress` SSE channel).
55
+ - pi-side: audit long tools and add progress emission where missing.
56
+
57
+ **Acceptance criteria:**
58
+ - [ ] A tool that runs >1s emits at least one progress notification.
59
+ - [ ] pi's TUI spinner updates during the call, not only on completion.
60
+ - [ ] Result content is identical to the blocking path (no data loss).
61
+ - [ ] Fallback: if a tool does not emit progress, behavior matches today.
62
+
63
+ **Effort:** Large. Touches pi's progress bus and the MCP transport.
64
+
65
+ **Blocks:** None. **Blocked by:** None.
66
+
67
+ ---
68
+
69
+ ### G2. Expose pi's UI primitives [MEDIUM-HIGH IMPACT]
70
+
71
+ **Status:** Open
72
+ **Objective:** Let agy drive pi's native UI: confirm dialogs, toasts,
73
+ file/directory pickers, status/footer updates.
74
+
75
+ **Why:** agy can already `ask_user_question`. Missing: confirm/permission
76
+ dialog for destructive ops (agy falls back to its own out-of-theme dialog),
77
+ notification toast (for "task started" / "save ok"), native file picker
78
+ (replaced today by asking for a path in text), and status-bar updates
79
+ ("Antigravity: working on X"). Note: this does NOT unlock a native diff viewer
80
+ for agy edits, that path is structurally closed (see G8 in `CHANGELOG.md`).
81
+
82
+ **Scope:**
83
+ - `docs/PI-INVOKETOOL-PATCH.md`: expose `AgentSession.ui` helpers.
84
+ - `src/mcp-server.ts`: wrappers for `pi_confirm`, `pi_notify`,
85
+ `pi_select_file`, `pi_select_directory`, `pi_set_status`.
86
+
87
+ **Acceptance criteria:**
88
+ - [ ] `pi_confirm(message)` pops pi's native confirm UI and returns boolean.
89
+ - [ ] `pi_notify(message)` shows a toast.
90
+ - [ ] `pi_select_file`/`pi_select_directory` return chosen paths or null.
91
+ - [ ] `pi_set_status(text)` updates the footer; clears on empty string.
92
+ - [ ] Tests cover each primitive with a mocked `ui` seam.
93
+
94
+ **Effort:** Medium-large. Each primitive is a small pi patch plus a wrapper.
95
+
96
+ **Blocks:** None. **Blocked by:** None.
97
+
98
+ ---
99
+
100
+ ### G3. Lifecycle event subscription [MEDIUM IMPACT]
101
+
102
+ **Status:** Open
103
+ **Objective:** Let a long-lived agy session observe pi events: `turn_start`,
104
+ `turn_end`, `tool_call`, `tool_result`, `compaction`.
105
+
106
+ **Why:** Today the bridge handles only `session_start` and `session_shutdown`.
107
+ agy is fire-and-forget per turn. Event subscription would enable a class of
108
+ "observer" tooling.
109
+
110
+ **Caveat:** agy is request-response per turn (`-p`), not event-reactive. Before
111
+ building, confirm there is a real consumer that can act on an async event
112
+ stream; otherwise this risks the same "no consumer" failure that sank
113
+ file-watching (see Discarded ideas).
114
+
115
+ **Scope:**
116
+ - `docs/PI-INVOKETOOL-PATCH.md`: add an event-emitter seam on `AgentSession`.
117
+ - `src/mcp-server.ts`: `pi_subscribe(event)` returns a stream id; an SSE
118
+ channel pushes events.
119
+
120
+ **Acceptance criteria:**
121
+ - [ ] `pi_subscribe("tool_call")` returns a stream id and subsequent tool calls
122
+ arrive on the channel.
123
+ - [ ] Unsubscribe cleans up the stream (no leak).
124
+ - [ ] At least three event types supported at close.
125
+ - [ ] No perf regression on the event hot path.
126
+
127
+ **Effort:** Large. Non-trivial pi-side patching.
128
+
129
+ **Blocks:** None. **Blocked by:** Confirm a real event-driven consumer exists.
130
+
131
+ ---
132
+
133
+ ## Discarded ideas (not worth it)
134
+
135
+ Weighed and rejected; kept here as a graveyard so they are not re-proposed. Full
136
+ reasoning is in project memory.
137
+
138
+ - **Expose pi's other MCP clients** — REMOVED. pi has no native
139
+ MCP-client support and no MCP extension is in use, so there are no pi
140
+ MCP-client tools to double-expose. The bridge already surfaces every tool pi
141
+ actually registers.
142
+ - **Refresh tool list mid-session** — DECLINED. The bridge already
143
+ re-queries `pi.getAllTools()` on every `tools/list` (stateless server), and
144
+ agy reconnects and re-lists every turn (`-p`), so a tool registered
145
+ mid-session appears next turn. The heartbeat would only help a long-lived
146
+ client that caches the list, and there is none.
147
+ - **Settings, env, and secrets access** — DECLINED. Tools exposed via
148
+ the bridge run in pi's process (`pi.invokeTool` -> `tool.execute`) and
149
+ self-authenticate with pi's own credentials, so agy already uses pi's creds
150
+ for every tool; a credential never crosses the bridge. A `pi_get_setting`
151
+ accessor was predicated on credential reuse that does not apply.
152
+ - **Image / binary content blocks** — NOT NEEDED. pi shares the
153
+ path to any image it produces (e.g. `/tmp/pi-clipboard-<uuid>.png`), and agy
154
+ reaches and reads those files directly via the bridge's `read` tool, so
155
+ returning image content blocks over the transport would duplicate a path
156
+ that already works end-to-end. No agy transport change or pi patch required.
157
+ - **File-watching / live state** — DECLINED. agy is request-response
158
+ per turn, not event-reactive; nothing consumes a file-watch SSE stream, and
159
+ re-reads are cheap and correct. Watchers would add inotify/FSEvents handles,
160
+ races, and cleanup for no gain.
161
+
162
+ ## How to close a gap
163
+
164
+ For each open gap, the default shape:
165
+
166
+ 1. Identify the pi-side API to expose (or add).
167
+ 2. Extend the patch in `docs/PI-INVOKETOOL-PATCH.md` with a read (or write)
168
+ accessor.
169
+ 3. Add a bridge tool wrapper in `src/mcp-server.ts` that calls the patched API.
170
+ 4. Register the tool name with the bridge (it appears in agy's tool catalog on
171
+ the next session).
172
+ 5. Add a test under `tests/mcp-server.test.ts` that round-trips a real call.
173
+ 6. Tick the gap's acceptance checkboxes.
174
+
175
+ Most need no change to agy, only the bridge (and, for some, pi's dist). Note:
176
+ the two shipped gaps were closed without any pi dist patch or new MCP tool, by
177
+ working provider/decode-side (`src/provider.ts`, `src/diff-render.ts`); the
178
+ shape above is a default, not a requirement.
179
+
180
+ ## Cross-references
181
+
182
+ - `docs/ARCHITECTURE.md` — bridge design and per-pid config layout.
183
+ - `docs/PI-INVOKETOOL-PATCH.md` — the local patch to pi that this whole
184
+ feature depends on.
185
+ - `docs/DEVELOPMENT.md` — how to run tests, rebuild, and iterate.
186
+ - `CHANGELOG.md` — shipped work (conversation-history digest, edit diffs).