@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 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. The default `stream-json` engine runs one persistent agy process in your workspace, feeds it each turn, parses its stream-json events, and streams the agent text back into pi token by token. Token usage is live. A `legacy-sqlite` fallback engine (spawn `agy -p`, poll its SQLite conversation DB, decode its protobuf steps) still exists; see [Engine and bridge surface](#engine-and-bridge-surface).
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 on the `stream-json` engine; the `legacy-sqlite` fallback reports usage as zero.
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 (stream-json engine)
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 engine state,
52
- driver counters, bridge port, and the last lifecycle events without spending
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
- ### Engine and bridge surface
86
+ ### Bridge surface
90
87
 
91
- `config.json` selects the turn engine and the bridge surface:
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: `AGY_ENGINE`, `AGY_BRIDGE_TOOLS`, `AGY_DIGEST`. The `legacy-sqlite` engine is kept as a fallback and is scheduled for removal once the stream-json engine has burned in.
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 engine state, driver counters, bridge port, last lifecycle events
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 engine table)
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 on the default engine, its SQLite conversation DB on the fallback engine. 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.
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
 
@@ -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
- ## Engines
5
+ ## Turn engine
6
6
 
7
- The provider runs one of two turn engines (`config.engine`, default `stream-json`):
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
- - **`stream-json` (default):** one 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.
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/runner.ts legacy engine: spawn agy -p, concurrent poll loop, abort/timeout, emit events
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 (engine, bridgeTools, digest, mode, permissions, model/thinking defaults)
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 native SQLite dependency (`node:sqlite` covers the legacy engine's reads).
30
+ No generated protobuf code, no SQLite dependency.
37
31
 
38
- ## Stream-json engine (default)
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
- ## Legacy-sqlite engine internals
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
- 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.
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.
@@ -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 (decoder? poller? provider? pi loader?).
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 (both engines). 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.
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. On `legacy-sqlite` the snapshot/diff discovery refuses to bind if more than one new `.db` appears (ambiguous). `npm run decode-db` against the suspected DB confirms agy wrote steps.
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 runner (no agy) and asserts the exact event sequence: text/thinking close-on-switch, tool labels through the thinking block, empty-turn fallback.
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, legacy decode/polling).
58
+ See [ARCHITECTURE.md](./ARCHITECTURE.md) for the module map and the engine internals (stream-json events, no-patch round-trip).
@@ -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
 
@@ -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 spawns `agy -p`, polls the conversation SQLite DB
6
- // agy writes, decodes the protobuf step payloads, and streams the agent text
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.1",
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
  }