@estebanforge/pi-antigravity-bridge 1.3.1 → 1.3.3
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 +18 -0
- package/README.md +13 -15
- package/docs/ARCHITECTURE.md +9 -40
- package/docs/DEVELOPMENT.md +5 -20
- package/docs/PI-BRIDGE-GAPS.md +7 -1
- package/extensions/index.ts +19 -7
- package/package.json +1 -3
- package/src/config.ts +17 -13
- package/src/provider.ts +51 -182
- package/src/poller.ts +0 -202
- package/src/protobuf.ts +0 -184
- package/src/runner.ts +0 -390
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.3.3] - 2026-09-01
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- pi system prompt injection (G10): pi's composed system prompt - operating instructions plus the global agent-dir `AGENTS.md` and ancestor `AGENTS.md`/`CLAUDE.md` files - is prepended as a delimited block to the first prompt of each NEW agy conversation. agy has no system-prompt flag, so the prompt text is the only delivery path; the block is sent once per conversation and stays byte-identical afterwards, so agy's server-side prompt cache keeps hitting (unlike the per-turn G1 digest, which stays off by default for that reason). On by default (`config.systemPrompt`, `AGY_SYSTEM_PROMPT`, `/agy system-prompt on|off`); existing conversations keep the version they started with.
|
|
10
|
+
|
|
11
|
+
## [1.3.2] - 2026-09-01
|
|
12
|
+
|
|
13
|
+
### Removed
|
|
14
|
+
|
|
15
|
+
- The `legacy-sqlite` fallback engine: `src/runner.ts`, `src/poller.ts`, `src/protobuf.ts`, the `run-agy` and `decode-db` scripts, and the `engine` config key / `AGY_ENGINE` env var. agy 1.1.18 changed step-row storage to a two-phase write (a placeholder row first, grown in place later); the polling engine read each row once as an empty placeholder and never re-read it, so turns completed with the full reply in the database and zero text in pi (issue #1). The engine decoded an undocumented storage format, so every agy storage change risked repeating that failure silently. The stream-json engine shares none of that code path; verified live against agy 1.1.18-era storage (1.1.23 installed). A stale `engine` value in an existing `config.json` is ignored.
|
|
16
|
+
Reported by @imatimba in #1. Thanks for the exact repro and the root-cause analysis; the report drove this removal.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- `scripts/test-provider.ts` wires the stream-json driver explicitly (it exercised the legacy path implicitly before).
|
|
21
|
+
- `tests/provider-streaming.test.ts` covers effort mapping against a fake driver. The legacy event-mapping tests died with the engine; stream-json event coverage lives in `tests/stream-roundtrip.test.ts`.
|
|
22
|
+
|
|
5
23
|
## [1.3.1] - 2026-08-31
|
|
6
24
|
|
|
7
25
|
### Changed
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ If you also have [`@estebanforge/pi-ask-antigravity`](https://github.com/Esteban
|
|
|
8
8
|
|
|
9
9
|
## What it does
|
|
10
10
|
|
|
11
|
-
You pick a Gemini model in pi's `/model` picker. pi routes each turn through this provider.
|
|
11
|
+
You pick a Gemini model in pi's `/model` picker. pi routes each turn through this provider. A single persistent `agy` process runs in your workspace; pi feeds it each turn, parses its stream-json events, and streams the agent text back into pi token by token. Token usage is live.
|
|
12
12
|
|
|
13
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
14
|
|
|
@@ -20,7 +20,7 @@ Residual limits (with or without the bridge):
|
|
|
20
20
|
|
|
21
21
|
- agy's own edits still land directly on disk; pi's inline diff review does not engage for them.
|
|
22
22
|
- agy commands run without per-action approval, same as every other tool in pi. See [Permissions](#permissions) below.
|
|
23
|
-
- No cost accounting: cost stays zero because agy runs on your subscription quota. Token usage is live
|
|
23
|
+
- No cost accounting: cost stays zero because agy runs on your subscription quota. Token usage is live.
|
|
24
24
|
|
|
25
25
|
## MCP tool bridge (agy uses pi's tools)
|
|
26
26
|
|
|
@@ -36,7 +36,7 @@ The bridge starts a localhost MCP server inside pi's process. `tools/list` retur
|
|
|
36
36
|
|
|
37
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
38
|
|
|
39
|
-
### Native cards, wrapper replay, and skills
|
|
39
|
+
### Native cards, wrapper replay, and skills
|
|
40
40
|
|
|
41
41
|
Read-only agy steps (view_file, list_dir, grep_search, find_by_name) re-run as
|
|
42
42
|
real pi builtins (`read`, `ls`, `grep`, `find`) when those builtins are active,
|
|
@@ -48,9 +48,8 @@ anything with side effects.
|
|
|
48
48
|
|
|
49
49
|
When the bridge is on, agy also gets one `activate_skill` tool whose enum is
|
|
50
50
|
your pi Agent Skills catalog; calling it returns the SKILL.md body. The bridge
|
|
51
|
-
answers it directly, no pi round-trip. `/agy doctor` prints
|
|
52
|
-
|
|
53
|
-
tokens.
|
|
51
|
+
answers it directly, no pi round-trip. `/agy doctor` prints driver counters,
|
|
52
|
+
bridge port, and the last lifecycle events without spending tokens.
|
|
54
53
|
|
|
55
54
|
## Install
|
|
56
55
|
|
|
@@ -64,8 +63,6 @@ pi install npm:@estebanforge/pi-antigravity-bridge
|
|
|
64
63
|
|
|
65
64
|
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.
|
|
66
65
|
|
|
67
|
-
Also requires Node 22.5 or newer (uses the built-in `node:sqlite`).
|
|
68
|
-
|
|
69
66
|
## Usage
|
|
70
67
|
|
|
71
68
|
Pick a model and talk to pi as usual:
|
|
@@ -86,17 +83,17 @@ Model ids are slugified from the `agy models` output (`Gemini 3.6 Flash (Medium)
|
|
|
86
83
|
|
|
87
84
|
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.
|
|
88
85
|
|
|
89
|
-
###
|
|
86
|
+
### Bridge surface
|
|
90
87
|
|
|
91
|
-
`config.json` selects the
|
|
88
|
+
`config.json` selects the bridge surface:
|
|
92
89
|
|
|
93
90
|
| Key | Values | Default |
|
|
94
91
|
| --- | --- | --- |
|
|
95
|
-
| `engine` | `stream-json` (persistent agy process, toolUse round-trips, live usage) or `legacy-sqlite` (spawn `agy -p`, poll its SQLite) | `stream-json` |
|
|
96
92
|
| `bridgeTools` | `none` (bridge off), `mcp` (pi-mcp-adapter tools), `all` (every non-builtin tool, incl. other `Ask*` delegations) | `mcp` |
|
|
97
93
|
| `digest` | `off` (stable prompts; agy's prompt cache hits) or `on` (inject a delta of pi-side context - compaction summaries, other-provider turns - into each agy prompt; the delta changes every turn, so agy re-bills the full context). Enable for mixed-provider sessions where agy must see pi-side context | `off` |
|
|
94
|
+
| `systemPrompt` | `on` (prepend pi's system prompt - operating instructions plus the global agent-dir `AGENTS.md` and ancestor `AGENTS.md`/`CLAUDE.md` - to the first prompt of each new agy conversation) or `off` (agy-native behavior) | `on` |
|
|
98
95
|
|
|
99
|
-
Env overrides: `
|
|
96
|
+
Env overrides: `AGY_BRIDGE_TOOLS`, `AGY_DIGEST`, `AGY_SYSTEM_PROMPT`. Env wins over the file, so while `AGY_DIGEST` or `AGY_SYSTEM_PROMPT` is set, the matching `/agy digest` or `/agy system-prompt` toggle persists a value that never takes effect.
|
|
100
97
|
|
|
101
98
|
### The /agy command
|
|
102
99
|
|
|
@@ -105,13 +102,14 @@ Env overrides: `AGY_ENGINE`, `AGY_BRIDGE_TOOLS`, `AGY_DIGEST`. The `legacy-sqlit
|
|
|
105
102
|
```
|
|
106
103
|
/agy status, or open the mode/permissions/model/thinking picker (TUI)
|
|
107
104
|
/agy status print current mode, permissions, model + session counts
|
|
108
|
-
/agy doctor
|
|
105
|
+
/agy doctor bridge state, driver counters, bridge port, last lifecycle events
|
|
109
106
|
/agy mode plan review-only: agy plans but writes nothing
|
|
110
107
|
/agy mode accept-edits agy applies edits directly (default)
|
|
111
108
|
/agy permissions on|off auto-approve / prompt for tool calls (see warning)
|
|
112
109
|
/agy model flash|pro|gemini default model alias for the AskAntigravity tool
|
|
113
110
|
/agy thinking low|medium|high default thinking tier for the AskAntigravity tool
|
|
114
|
-
/agy digest on|off inject pi-side context into agy prompts (default off; see
|
|
111
|
+
/agy digest on|off inject pi-side context into agy prompts (default off; see table above)
|
|
112
|
+
/agy system-prompt on|off send pi's system prompt + AGENTS.md to new agy conversations (default on)
|
|
115
113
|
/agy patch-cleanup restore the original pi files if an older version patched them
|
|
116
114
|
/agy clear drop all session bindings (force fresh conversations)
|
|
117
115
|
```
|
|
@@ -148,7 +146,7 @@ Build, test, and debug instructions live in [docs/DEVELOPMENT.md](docs/DEVELOPME
|
|
|
148
146
|
|
|
149
147
|
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.
|
|
150
148
|
|
|
151
|
-
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 what `agy` itself produces locally: its stream-json output
|
|
149
|
+
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 what `agy` itself produces locally: its stream-json output. 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.
|
|
152
150
|
|
|
153
151
|
Google's reported enforcement to date (the February 2026 suspensions) targeted token-reuse tools, not spawning the official CLI.
|
|
154
152
|
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -2,14 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
How the provider works internally. For build/test/debug workflow see [DEVELOPMENT.md](./DEVELOPMENT.md).
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Turn engine
|
|
6
6
|
|
|
7
|
-
The provider runs one
|
|
7
|
+
The provider runs one turn engine: a long-lived `agy --input-format stream-json --output-format stream-json` process per provider. Turns are fed over stdin; agy emits NDJSON events on stdout; the driver parses them and streams text into pi token by token. Conversation binding comes from the `init` event, tool steps arrive as typed events (no protobuf decoding), and token usage is live.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
- **`legacy-sqlite` (fallback):** the pre-1.3.0 engine - spawn `agy -p`, poll its SQLite conversation DB on a 250 ms interval, decode the protobuf step payloads. Kept as a fallback and scheduled for removal once `stream-json` has burned in (`AGY_ENGINE=legacy-sqlite` selects it).
|
|
11
|
-
|
|
12
|
-
Shared by both engines: session binding (`sessions.json`), runtime config, the `AskAntigravity` tool, the MCP tool bridge surface, and the G1 context digest (off by default - see below).
|
|
9
|
+
Shared infrastructure: session binding (`sessions.json`), runtime config, the `AskAntigravity` tool, the MCP tool bridge surface, and the G1 context digest (off by default - see below).
|
|
13
10
|
|
|
14
11
|
## Module map
|
|
15
12
|
|
|
@@ -21,21 +18,18 @@ src/stream-events.ts agy NDJSON event parser (init / step_update / result) + us
|
|
|
21
18
|
src/native-tools.ts maps agy read-only tool steps to real pi builtins (read/ls/grep/find) for native re-execution
|
|
22
19
|
src/skills.ts activate_skill bridge: exposes the pi Agent Skills catalog to agy, answered by the bridge directly
|
|
23
20
|
src/patch-cleanup.ts detects a leftover invokeTool patch from pre-1.3.0 installs; /agy patch-cleanup restores the backup
|
|
24
|
-
src/
|
|
25
|
-
src/poller.ts legacy engine: read-only node:sqlite handle over one conversation DB
|
|
26
|
-
src/protobuf.ts legacy engine: hand-rolled varint walker + extractors (field 20.1 = text)
|
|
27
|
-
src/discovery.ts legacy engine: snapshot/diff + pid fd-scan to bind the conversation id agy -p never prints
|
|
21
|
+
src/discovery.ts conversation-id binding for the AskAntigravity one-shot tool (agy -p never prints its conversation id)
|
|
28
22
|
src/models.ts agy models -> pi Model projection (full catalog, per-model effort)
|
|
29
23
|
src/sessions.ts atomic JSON store: pi session -> agy conversation + watermark
|
|
30
|
-
src/config.ts persisted runtime config (
|
|
24
|
+
src/config.ts persisted runtime config (bridgeTools, digest, mode, permissions, model/thinking defaults)
|
|
31
25
|
src/ask-tool.ts the AskAntigravity one-shot delegation tool (model/thinking defaults)
|
|
32
26
|
src/mcp-server.ts MCP tool bridge server: ferries tools/list + tools/call; calls park in the provider round-trip
|
|
33
27
|
src/diff-render.ts render agy's file edits as git diffs in pi's thinking stream
|
|
34
28
|
```
|
|
35
29
|
|
|
36
|
-
No generated protobuf code, no
|
|
30
|
+
No generated protobuf code, no SQLite dependency.
|
|
37
31
|
|
|
38
|
-
## Stream-json engine
|
|
32
|
+
## Stream-json engine
|
|
39
33
|
|
|
40
34
|
### Process and events
|
|
41
35
|
|
|
@@ -61,31 +55,6 @@ Display follows the same split: agy read-only steps (`view_file`, `list_dir`, `g
|
|
|
61
55
|
|
|
62
56
|
By default the prompt agy receives is only the latest user message: agy keeps its own history. When `config.digest` is on (`AGY_DIGEST`, `/agy digest on`), the provider prepends a DELTA digest of pi-side context agy was not spawned for - compaction summaries, other-provider turns, pi-tool results. Off by default because the digest changes every turn and defeats agy's server-side prompt cache (~25-30k tokens re-billed per turn). Enable it for mixed-provider sessions where agy must see pi-side context; pure antigravity sessions gain nothing, and bridge round-trips deliver tool results through the bridge, not the digest.
|
|
63
57
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
The sections below describe the `legacy-sqlite` fallback only (`AGY_ENGINE=legacy-sqlite`). The stream-json engine shares none of this code path.
|
|
67
|
-
|
|
68
|
-
### The decode pipeline
|
|
69
|
-
|
|
70
|
-
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.
|
|
71
|
-
|
|
72
|
-
### Step types
|
|
73
|
-
|
|
74
|
-
| step_type | meaning |
|
|
75
|
-
| --- | --- |
|
|
76
|
-
| 15 | agent text (payload field 20 -> field 1) |
|
|
77
|
-
| 14 | thinking |
|
|
78
|
-
| 23 | title update (payload field 30 -> field 4) |
|
|
79
|
-
| 5, 7, 8, 9, 17, 21, 33, 101, 132, 138 | tool run (payload field 5 -> field 4 -> name@2/9, input@3) |
|
|
80
|
-
|
|
81
|
-
Status 3 = complete; anything else = in-flight.
|
|
82
|
-
|
|
83
|
-
### Polling
|
|
84
|
-
|
|
85
|
-
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.
|
|
86
|
-
|
|
87
|
-
### Conversation id discovery
|
|
88
|
-
|
|
89
|
-
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).
|
|
58
|
+
### Removed: the legacy-sqlite engine (1.3.2)
|
|
90
59
|
|
|
91
|
-
|
|
60
|
+
The pre-1.3.0 engine (spawn `agy -p`, poll the SQLite conversation DB, decode protobuf step payloads) was removed in 1.3.2. agy 1.1.18 changed the step-row storage to a two-phase write (a metadata-only placeholder row that grows in place), which the polling decoder read once as an empty placeholder and never re-read: turns completed with the full reply in the database and zero text streamed to pi (issue #1, reported by @imatimba). The engine reverse-engineered an undocumented storage format, so every agy storage change risked repeating that silent failure. The stream-json engine shares none of that code path and is unaffected by storage-format changes. `AGY_ENGINE` and the `engine` config key are gone; a stale value in an existing `config.json` is ignored.
|
package/docs/DEVELOPMENT.md
CHANGED
|
@@ -14,22 +14,9 @@ The integration scripts below spawn a real `agy` process and need a logged-in ac
|
|
|
14
14
|
|
|
15
15
|
## Standalone scripts
|
|
16
16
|
|
|
17
|
-
These exercise the pipeline without pi. Useful for isolating where a bug lives (
|
|
17
|
+
These exercise the pipeline without pi. Useful for isolating where a bug lives (driver? provider? pi loader?).
|
|
18
18
|
|
|
19
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
20
|
# Drive the provider's streamSimple directly (no pi TUI) and assert the
|
|
34
21
|
# full event lifecycle: start -> text_start -> text_delta -> text_end ->
|
|
35
22
|
# done. The closest thing to a pi turn without pi.
|
|
@@ -53,21 +40,19 @@ AGY_LIVE=1 node --experimental-strip-types scripts/smoke-stream-json.mjs
|
|
|
53
40
|
|
|
54
41
|
Most "stuck" reports trace to one of:
|
|
55
42
|
|
|
56
|
-
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 mode
|
|
43
|
+
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 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.
|
|
57
44
|
2. **agy never started.** Check `AGY_BIN` is on PATH (or set explicitly). The spawner swallows spawn ENOENT into the result's stderr, surfaced by the provider as an error event.
|
|
58
|
-
3. **Conversation id never bound.** On `stream-json` the `init` event carries the id
|
|
45
|
+
3. **Conversation id never bound.** On `stream-json` the `init` event carries the id, so a missing binding means the turn never produced a result event. `/agy doctor` prints the last lifecycle events.
|
|
59
46
|
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.
|
|
60
47
|
|
|
61
48
|
## Regression tests worth knowing
|
|
62
49
|
|
|
63
50
|
- `tests/stream-roundtrip.test.ts` - the stream-json engine pieces: NDJSON parser, native re-exec mapping, and the no-patch toolUse round-trip store.
|
|
64
|
-
- `tests/provider-streaming.test.ts` - drives streamSimple with an injected fake
|
|
51
|
+
- `tests/provider-streaming.test.ts` - drives streamSimple with an injected fake driver (no agy) and asserts how pi's reasoning level maps onto the agy `--effort` tier (forward, clamp, omit).
|
|
65
52
|
- `tests/provider-digest.test.ts` - the G1 context digest builder: injects pi-side context without replaying agy's own history.
|
|
66
53
|
- `tests/patch-cleanup.test.ts` - legacy-patch detection and restore, real fs via tmpdirs, no mocks.
|
|
67
|
-
- `tests/runner-streaming.test.ts` - (legacy engine) a fake agy writes rows on a delay; asserts events arrive DURING the run (not all at exit) and that abort returns promptly. Guards the "provider did not actually stream" class of bug.
|
|
68
|
-
- `tests/protobuf.test.ts` - (legacy engine) pure decoder math (varint, field walking, nested submessages).
|
|
69
54
|
- `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. The provider owns the tool catalog and the round-trip; the server only ferries list/call.
|
|
70
55
|
|
|
71
56
|
## Module map
|
|
72
57
|
|
|
73
|
-
See [ARCHITECTURE.md](./ARCHITECTURE.md) for the module map and the engine internals (stream-json events, no-patch round-trip
|
|
58
|
+
See [ARCHITECTURE.md](./ARCHITECTURE.md) for the module map and the engine internals (stream-json events, no-patch round-trip).
|
package/docs/PI-BRIDGE-GAPS.md
CHANGED
|
@@ -27,7 +27,13 @@ credentials, so a secret never crosses the bridge, and a long call renders in
|
|
|
27
27
|
pi's native UI while it runs. agy's file edits surface as git-sourced diffs in
|
|
28
28
|
pi's thinking stream. A delta digest of pi-side context (compaction summaries,
|
|
29
29
|
other-provider turns) is available but OFF by default (`/agy digest on`): the
|
|
30
|
-
digest changes every turn and defeats agy's server-side prompt cache.
|
|
30
|
+
digest changes every turn and defeats agy's server-side prompt cache. The
|
|
31
|
+
reverse direction is on by default (`/agy system-prompt off` to disable):
|
|
32
|
+
pi's composed system prompt - its operating instructions plus the global
|
|
33
|
+
agent-dir `AGENTS.md` and ancestor `AGENTS.md`/`CLAUDE.md` files - is
|
|
34
|
+
prepended as a delimited block to the first prompt of each new agy
|
|
35
|
+
conversation, once, so the prompt cache keeps hitting (G10 in
|
|
36
|
+
`CHANGELOG.md`).
|
|
31
37
|
|
|
32
38
|
## Open gaps
|
|
33
39
|
|
package/extensions/index.ts
CHANGED
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Registers Gemini (via the agy CLI) as a pi model provider so it shows up in
|
|
4
4
|
// the /model picker as antigravity/gemini-*. When selected, pi routes each turn
|
|
5
|
-
// through streamSimple, which
|
|
6
|
-
//
|
|
7
|
-
// back into pi's TUI.
|
|
5
|
+
// through streamSimple, which feeds the persistent stream-json driver process
|
|
6
|
+
// and streams the agent text back into pi's TUI.
|
|
8
7
|
//
|
|
9
8
|
// Architectural wall (cannot be worked around - see PLAN.md):
|
|
10
9
|
// agy runs its OWN closed tool loop against --add-dir. pi's read/write/edit/
|
|
@@ -301,11 +300,11 @@ function statusText(ctx: AgyCommandCtx): string {
|
|
|
301
300
|
` tool thinking: ${config.defaultThinking}`,
|
|
302
301
|
` sessions: ${ctx.store.size} bound`,
|
|
303
302
|
` config: ${CONFIG_PATH}`,
|
|
304
|
-
` engine: ${config.engine}`,
|
|
305
303
|
` bridge tools: ${config.bridgeTools}`,
|
|
306
304
|
` digest: ${config.digest ? "on" : "off"}`,
|
|
305
|
+
` system prompt: ${config.systemPrompt ? "on" : "off"}`,
|
|
307
306
|
"",
|
|
308
|
-
"Subcommands: /agy mode plan|accept-edits, /agy permissions on|off, /agy model flash|pro|gemini, /agy thinking low|medium|high, /agy clear",
|
|
307
|
+
"Subcommands: /agy mode plan|accept-edits, /agy permissions on|off, /agy model flash|pro|gemini, /agy thinking low|medium|high, /agy digest on|off, /agy system-prompt on|off, /agy clear",
|
|
309
308
|
].join("\n");
|
|
310
309
|
}
|
|
311
310
|
|
|
@@ -313,7 +312,7 @@ function statusText(ctx: AgyCommandCtx): string {
|
|
|
313
312
|
function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
314
313
|
pi.registerCommand("agy", {
|
|
315
314
|
description:
|
|
316
|
-
"Antigravity provider: status, doctor, mode picker, clear sessions. Usage: /agy [status|doctor|mode [plan|accept-edits]|digest on|off|patch-cleanup|clear]",
|
|
315
|
+
"Antigravity provider: status, doctor, mode picker, clear sessions. Usage: /agy [status|doctor|mode [plan|accept-edits]|digest on|off|system-prompt on|off|patch-cleanup|clear]",
|
|
317
316
|
handler: async (args, cmdCtx: ExtensionCommandContext) => {
|
|
318
317
|
const ui = cmdCtx.ui;
|
|
319
318
|
const mode = cmdCtx.mode;
|
|
@@ -352,7 +351,6 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
352
351
|
const port = ctx.getMcpPort();
|
|
353
352
|
const lines = [
|
|
354
353
|
"Antigravity doctor (no tokens spent)",
|
|
355
|
-
` engine: ${config.engine}`,
|
|
356
354
|
` bridge: ${config.bridgeTools}${port ? ` (port ${port})` : " (not running)"}`,
|
|
357
355
|
` driver: ${snap.state}${snap.pid ? ` pid=${snap.pid}` : ""}${snap.conversationId ? ` conv=${snap.conversationId.slice(0, 8)}` : ""}`,
|
|
358
356
|
` driver stats: spawns=${snap.stats.spawns} turns=${snap.stats.turns} reused=${snap.stats.reused} recycles=${snap.stats.recycles}${snap.stats.lastRecycleReason ? ` (last: ${snap.stats.lastRecycleReason})` : ""}`,
|
|
@@ -409,6 +407,20 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
409
407
|
}
|
|
410
408
|
return;
|
|
411
409
|
}
|
|
410
|
+
if (sub === "system-prompt") {
|
|
411
|
+
if (val === "on" || val === "off") {
|
|
412
|
+
const next = saveConfig({ systemPrompt: val === "on" });
|
|
413
|
+
ui?.notify(
|
|
414
|
+
next.systemPrompt
|
|
415
|
+
? "system-prompt on. pi's system prompt (incl. global and project AGENTS.md) is prepended to the first prompt of each new agy conversation. Existing conversations keep the version they started with."
|
|
416
|
+
: "system-prompt off. agy runs on its own system prompt; pi instructions and AGENTS.md files are not sent.",
|
|
417
|
+
"info",
|
|
418
|
+
);
|
|
419
|
+
} else {
|
|
420
|
+
ui?.notify(`system-prompt: ${loadConfig().systemPrompt ? "on" : "off"}\nusage: /agy system-prompt on|off`, "info");
|
|
421
|
+
}
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
412
424
|
|
|
413
425
|
if (sub === "thinking") {
|
|
414
426
|
if (val === "low" || val === "medium" || val === "high") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@estebanforge/pi-antigravity-bridge",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.3",
|
|
4
4
|
"description": "Streaming Gemini provider for pi, built on the agy CLI. Registers antigravity/* models in pi's /model picker; drives agy through its stream-json protocol (persistent process, tool round-trips, live usage).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -30,8 +30,6 @@
|
|
|
30
30
|
"build": "tsc --noEmit",
|
|
31
31
|
"test": "vitest run",
|
|
32
32
|
"prepublishOnly": "npm run build && npm test",
|
|
33
|
-
"run-agy": "tsx scripts/run-agy.ts",
|
|
34
|
-
"decode-db": "tsx scripts/decode-db.ts",
|
|
35
33
|
"smoke:pi": "bash scripts/smoke-in-pi.sh"
|
|
36
34
|
},
|
|
37
35
|
"files": [
|
package/src/config.ts
CHANGED
|
@@ -24,7 +24,6 @@ const CONFIG_PATH = path.join(
|
|
|
24
24
|
|
|
25
25
|
export type AgyMode = "accept-edits" | "plan";
|
|
26
26
|
export type ThinkingTier = "low" | "medium" | "high";
|
|
27
|
-
export type AgyEngine = "stream-json" | "legacy-sqlite";
|
|
28
27
|
export type BridgeTools = "none" | "mcp" | "all";
|
|
29
28
|
|
|
30
29
|
export interface AgyConfig {
|
|
@@ -39,11 +38,6 @@ export interface AgyConfig {
|
|
|
39
38
|
defaultModel: string;
|
|
40
39
|
/** AskAntigravity tool: default thinking tier when the alias names none. */
|
|
41
40
|
defaultThinking: ThinkingTier;
|
|
42
|
-
/** Turn engine. "stream-json" (default): one persistent agy process fed
|
|
43
|
-
* NDJSON user events; enables live toolUse round-trips, native usage, and
|
|
44
|
-
* conversation binding from the init event. "legacy-sqlite": the old
|
|
45
|
-
* spawn-`agy -p`-and-poll-SQLite path, kept as a fallback for one release. */
|
|
46
|
-
engine: AgyEngine;
|
|
47
41
|
/** Set after the one-time notice about a leftover legacy invokeTool patch
|
|
48
42
|
* on the installed pi. The notice never repeats; /agy patch-cleanup is
|
|
49
43
|
* always available. */
|
|
@@ -66,6 +60,16 @@ export interface AgyConfig {
|
|
|
66
60
|
* sessions gain nothing: agy already keeps its own history, and bridge
|
|
67
61
|
* round-trips deliver tool results through the bridge, not the digest. */
|
|
68
62
|
digest: boolean;
|
|
63
|
+
/** Prepend pi's composed system prompt (pi tool guidance + the global
|
|
64
|
+
* agent-dir AGENTS.md and ancestor AGENTS.md/CLAUDE.md) to the FIRST
|
|
65
|
+
* prompt of each fresh agy conversation.
|
|
66
|
+
*
|
|
67
|
+
* Default ON: agy keeps its own history, so the prefix is sent once per
|
|
68
|
+
* conversation and stays byte-identical afterwards - agy's server-side
|
|
69
|
+
* prompt cache keeps hitting. This is why it is safe here while the G1
|
|
70
|
+
* digest (per-turn) is not. Turn off for agy-native behavior (agy's own
|
|
71
|
+
* system prompt only). */
|
|
72
|
+
systemPrompt: boolean;
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
const DEFAULTS: AgyConfig = {
|
|
@@ -73,9 +77,9 @@ const DEFAULTS: AgyConfig = {
|
|
|
73
77
|
skipPermissions: true,
|
|
74
78
|
defaultModel: "flash",
|
|
75
79
|
defaultThinking: "medium",
|
|
76
|
-
engine: "stream-json",
|
|
77
80
|
bridgeTools: "mcp",
|
|
78
81
|
digest: false,
|
|
82
|
+
systemPrompt: true,
|
|
79
83
|
};
|
|
80
84
|
|
|
81
85
|
/** Load config merged over defaults. Env vars override the file when set. */
|
|
@@ -120,11 +124,6 @@ export function loadConfig(configPath: string = CONFIG_PATH): AgyConfig {
|
|
|
120
124
|
const defaultThinking: ThinkingTier =
|
|
121
125
|
thinkRaw === "low" || thinkRaw === "high" ? thinkRaw : "medium";
|
|
122
126
|
|
|
123
|
-
const engine: AgyEngine =
|
|
124
|
-
process.env.AGY_ENGINE === "legacy-sqlite" || file.engine === "legacy-sqlite"
|
|
125
|
-
? "legacy-sqlite"
|
|
126
|
-
: "stream-json";
|
|
127
|
-
|
|
128
127
|
const bridgeRaw = (process.env.AGY_BRIDGE_TOOLS ?? file.bridgeTools ?? DEFAULTS.bridgeTools).toLowerCase();
|
|
129
128
|
const bridgeTools: BridgeTools =
|
|
130
129
|
bridgeRaw === "none" || bridgeRaw === "all" ? bridgeRaw : "mcp";
|
|
@@ -133,14 +132,19 @@ export function loadConfig(configPath: string = CONFIG_PATH): AgyConfig {
|
|
|
133
132
|
? ["1", "true", "on"].includes(process.env.AGY_DIGEST.toLowerCase())
|
|
134
133
|
: file.digest ?? false;
|
|
135
134
|
|
|
135
|
+
const envSys = process.env.AGY_SYSTEM_PROMPT;
|
|
136
|
+
const systemPrompt = envSys !== undefined
|
|
137
|
+
? ["1", "true", "on"].includes(envSys.toLowerCase())
|
|
138
|
+
: file.systemPrompt ?? DEFAULTS.systemPrompt;
|
|
139
|
+
|
|
136
140
|
return {
|
|
137
141
|
mode,
|
|
138
142
|
skipPermissions,
|
|
139
143
|
defaultModel,
|
|
140
144
|
defaultThinking,
|
|
141
|
-
engine,
|
|
142
145
|
bridgeTools,
|
|
143
146
|
digest,
|
|
147
|
+
systemPrompt,
|
|
144
148
|
patchCleanupNotified: file.patchCleanupNotified === true,
|
|
145
149
|
};
|
|
146
150
|
}
|