@estebanforge/pi-antigravity-bridge 1.4.9 → 1.5.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 CHANGED
@@ -2,6 +2,37 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.5.0] - 2026-09-08
6
+
7
+ ### Added
8
+
9
+ - First-run engine picker. The choice of engine is left to the user: on a fresh install (no `config.json`, no `AGY_ENGINE`), the first interactive pi start opens a modal that explains both engines - stream-json needs the `agy` CLI installed and authenticated; ACP needs a second Google sign-in plus a ~1.5 GB server binary downloaded from Google. stream-json is preselected as the default; `esc` defers (nothing is written, the modal reappears next start). Headless sessions and existing installs are never asked.
10
+ - The ACP pick chains setup immediately instead of waiting for a restart: the ~1.5 GB server binary downloads right away (live percent in the status bar, phase milestones as chat lines), the Google sign-in opens when it lands, and a restart applies the engine. The next start's self-heal sees binary + auth settled and stays silent.
11
+ - `/agy engine` with no arguments (TUI) opens the same picker modal with identical semantics: plan mode blocks acp, an acp pick chains the download + sign-in, same-engine picks ack, esc acknowledges. Direct `/agy engine acp|stream-json` and the headless usage line are unchanged.
12
+ - Missing-CLI warning: while the stream-json engine is active and the `agy` binary cannot be found (PATH or `AGY_BIN`), every pi start warns with the official install URL (toast in the TUI, stderr headless) until the binary is detected. Auth state is not checked, presence only.
13
+
14
+ ### Fixed
15
+
16
+ - The picker's ACP line no longer claims "adds image input and native diffs": tool-result images ride both engines (probe-verified 2026-09-07) and stream-json has its own diff rendering. A negative test pin keeps the claim from returning.
17
+
18
+ ### Changed
19
+
20
+ - README streamlined: the engine capability table, switching, and setup/auth details moved to docs/ENGINES.md; the approval-gate mechanics, configuration, and sample extension moved to docs/APPROVAL-GATE.md. Both stay linked from the README, which now carries the quick surface and states that engine choice is the user's.
21
+ - Dependencies: fast-uri 3.1.5 → 3.1.7 clears four high-severity advisories (repeated hostname percent-decoding SSRF, malformed IPv6 normalization SSRF, percent-encoded scheme and IDN host confusion; in-range lockfile bump under ajv's `^3.0.1`), qs 6.15.3 → 6.16.0 clears a moderate pair (via express, dev surface). npm audit reports zero.
22
+
23
+ ## [1.4.10] - 2026-09-07
24
+
25
+ ### Added
26
+
27
+ - Approval gate for agy native tool calls. agy runs its own agent loop, and its mutating native tools (`run_command`, `create_file`, `edit_file`, ...) executed with no pi involvement: pi's permission extensions never saw them. The gate routes the calls through a pi-side approval in builtin shape, so the existing permission-extension ecosystem gates them with zero changes: a staged `.agents/hooks.json` `PreToolUse` hook parks the call in the bridge, the provider emits a pi `toolUse` for a builtin-shaped shadow tool (`bash`/`write`/`edit`), and the decision - allow, or deny with a reason the model sees - travels back to the hook. Marker calls never execute locally and are verified against the bridge's pending-ticket set (a forged marker denies even in allow mode); non-marker calls delegate to factory twins of the real builtins, so normal pi behavior is unchanged. Read-only agy tools stay ungated. Opt-in via `approvals.gateMode` (`auto`, the default, keeps it off until a known pi permission extension is detected; `shadow` forces on; `off` forces off) and `approvals.mode` (`ask`, the default, shows a pi confirm dialog and denies headless; `allow`/`deny` skip the dialog). Timeout, an unwired gate, and shutdown all deny fail-closed. Every decision lands in the daily log with tool names, source, and latency. README has the full section plus a sample gate extension.
28
+
29
+ ### Fixed
30
+
31
+ - Concurrent pi sessions on the same workspace no longer disable each other's approval gate. The staged `hooks.json` group was one shared key, so a second session resolving the gate to `off` deleted the first session's live gate, and two gate-on sessions overwrote each other's port/token/script. Groups are now keyed per session (`pi-bridge-gate-<pid>`), shutdown removes only the session's own group, and the next staging sweeps groups whose owning session is dead (found by an external pre-release audit).
32
+ - The shared `~/.gemini/config/mcp_config.json` now lands `0600` (dir `0700`). It carries the bridge's shared-secret token and previously landed at the umask default (typically world-readable) (found by an external pre-release audit).
33
+ - Bridge tools now register for the stream-json engine. That engine registered nothing: the agy CLI discovers MCP servers from `~/.gemini/config/mcp_config.json`, which the bridge never wrote (only the per-invocation `--add-dir` config existed), and the gap was masked because the daily engine is ACP. The bridge now registers itself there per-pid (`pi-bridge-<pid>`) at session start, sweeps stale entries left by crashed sessions, and unregisters on shutdown. Foreign servers in the shared file are preserved; a corrupt file is refused, never rewritten. Probe-verified live: agy discovers the server and completes tool calls through its native `call_mcp_tool` wrapper.
34
+ - Image blocks in tool results now reach the model on the stream-json engine too (they were ACP-only). The engine gate downgraded pixels to a text label over an unverified transport concern; a live probe settled it - the CLI's MCP client delivers tool-result image content, and the model named a two-tone PNG's halves from the tool result alone with no decoders in the frame trail. `read` on an image file now gives agy real pixels on both engines. Stream-json prompt attachments and the late-delivery prompt stay text-only by design.
35
+
5
36
  ## [1.4.9] - 2026-09-05
6
37
 
7
38
  ### Added
package/README.md CHANGED
@@ -21,34 +21,13 @@ Multi-turn works. The provider binds a pi session to an agy conversation id (per
21
21
  Turns run through one of two engines behind the same provider surface (`config.engine`, default `stream-json`):
22
22
 
23
23
  - **stream-json** (default): the persistent `agy` CLI process. The tested default; live token usage; conversation resume via `--conversation`.
24
- - **acp** (beta): Google's official ACP server (`agy_acp_server.par`), JSON-RPC 2.0 over stdio. Beta: parity-verified live against the current build (RC01) - text streaming, multi-turn resume via `session/load`, bridge tools, effort switching, serialization, abort recovery (see `scripts/parity-live.mjs`). Two known RC01 gaps remain: no usage fields (token display shows zero) and no cancel (abort tears the server down and reloads it next turn).
24
+ - **acp** (beta): Google's official ACP server (`agy_acp_server.par`), JSON-RPC 2.0 over stdio. Beta: parity-verified live against the current build (RC01) - text streaming, multi-turn resume via `session/load`, bridge tools, effort switching, serialization, abort recovery (see `scripts/parity-live.mjs`). Two known RC01 gaps remain: no usage fields (token display shows client-side ESTIMATES until Google ships usage; `acp.usageEstimate` off to keep zeros) and no cancel (abort tears the server down and reloads it next turn).
25
25
 
26
- Engine-dependent features: pi image attachments ride natively only on the ACP engine (the picker offers image attach automatically when `config.engine` is `acp`; the stream-json CLI prompt is text-only). With the optional G1 digest enabled, its delivery also differs: ACP ships it as a native `embeddedContext` resource block, stream-json prepends it to the prompt text. The `AskAntigravity` delegation tool is unaffected by `config.engine` and runs the `stream-json` CLI (`agy -p`) across both configurations.
26
+ The choice of engine is left to the user, with the trade-offs explained in the tool: a first-run picker modal asks once on a fresh install (stream-json preselected; `esc` defers, and the modal reappears next start), and `/agy engine` with no arguments reopens it anytime. An `acp` pick downloads the ~1.5 GB server binary and starts the Google sign-in immediately; a restart applies the engine. With stream-json active and the `agy` binary missing, pi warns on every start until the binary is found.
27
27
 
28
- | Capability | `stream-json` (default) | `acp` (beta) |
29
- | --- | --- | --- |
30
- | Show thinking text | No (token count only, floor 64, no text body) | Yes (streams thought text via `agent_thought_chunk`; sparse on RC01 where reasoning often arrives in message text) |
31
- | Live token usage | Yes (live metrics from CLI step events) | No (absent in RC01, displays zero tokens) |
32
- | Image prompt input | No (CLI prompt is text-only; images dropped) | Yes (native image blocks forwarded to server) |
33
- | Audio prompt input | No (dropped) | Protocol advertised (`promptCapabilities.audio: true`) |
34
- | Review-only plan mode | Yes (`--mode plan` review-only via `/agy mode plan`) | No (RC01 modes are permission levels; plan mode refused) |
35
- | Leading slash commands in prompt | Disabled via `--disable-slash-commands` (sent as plain text) | Server intercepts recognized commands (e.g. `/plan`) and executes them under the active policy |
36
- | Dynamic model / effort switch | Recycles process on model or effort change | Dynamic per-turn via `session/set_config_option` (no restart) |
37
- | Process lifecycle | 1 persistent `agy` process per provider; recycles on drift | 1 persistent server process hosting N sessions concurrently |
38
- | Session resume & persistence | Client-side map in `sessions.json` via `--conversation <id>` | Server-side session store via `session/load` and `session/new` |
39
- | Turn cancel / abort | Kills process group; in-flight turn terminates | Teardown, kill, and auto-reload on RC01 (-32601 fallback) |
40
- | MCP tool bridge routing | Injected filesystem config via `--add-dir` | Direct `mcpServers` param in `session/new` and `session/load` |
41
- | Tool execution & visibility | Native re-exec (read-only) + wrapper replay (mutating) | Server executes tools natively; events stream with content |
42
- | Inline file edit diffs | Sourced from git working tree in thinking block | Sourced from `tool_call content[]` or disk vs git HEAD |
43
- | Permission handling | `--dangerously-skip-permissions` (unattended CLI requirement) | Protocol-native `session/request_permission` (auto-approve when `skipPermissions` is on; auto-deny when off) |
44
- | Context digest delivery (G1) | Prepend plain text inline in prompt | Native `embeddedContext` resource block |
45
- | System prompt delivery (G10) | Prepend to first prompt of conversation | Prepend to first prompt of conversation |
46
- | Authentication methods | Inherits existing `agy` CLI OAuth state | 4 methods: `oauth-personal`, `oauth-business`, `gemini-api-key`, `agent-platform` |
47
- | Wire protocol | Undocumented CLI NDJSON stream format | Versioned JSON-RPC 2.0 over stdio (`protocolVersion: 1`) |
48
- | Diagnostics (`/agy doctor`) | Child PID, state, process spawns, recycles, queue stats | Server version, agentInfo, session counts, reconnect count, cancel support |
49
- | Integration channel | Spawns internal CLI stream-json dialect | Official Google first-party ACP server binary |
50
-
51
- Switch with `/agy engine acp|stream-json` (takes effect on restart). Setup is automatic: switching to `acp` installs Google's official ACP server binary from the [antigravity-acp registry entry](https://github.com/agentclientprotocol/registry) (`~/.local/opt/agy-acp/<build>/` + a `current` symlink, zip sha256 recorded; layout and pinning in [docs/ACP-ADOPTION-PLAN.md](docs/ACP-ADOPTION-PLAN.md)) and prepares the login. The login is your Antigravity subscription: the same account and plan you use for the Antigravity CLI (`agy`). Sign in explicitly with `/agy auth` (engine `acp` selected): it opens the Google login in your browser and completes when you finish it. If no browser is available (an SSH session on a remote machine), pi shows the sign-in URL to copy, plus the ssh port-forward command for the login redirect. It is no different from logging into the CLI; the server just keeps its own token file on your machine, like any Google tool, and this extension never sees your credentials. If you also export `GEMINI_API_KEY`, it is ignored: the server uses the auth type in settings.json, and setup always writes `oauth-personal`. A session start self-heals the same way, silently when everything is ready. Manual instructions (`/agy auth-manual`) surface only when a step fails. Sessions are engine-scoped, so switching engines never crosses conversations.
28
+ Full capability comparison, switching, and setup/auth details: [docs/ENGINES.md](docs/ENGINES.md).
29
+
30
+ Switch and setup details live in [docs/ENGINES.md](docs/ENGINES.md): switching to `acp` self-installs Google's official server binary from the [antigravity-acp registry entry](https://github.com/agentclientprotocol/registry) and prepares the login (your Antigravity subscription, same account as the `agy` CLI; the extension never sees your credentials). `/agy auth` signs in explicitly, `/agy doctor` diagnoses, and a session start self-heals silently when everything is ready. Sessions are engine-scoped, so switching engines never crosses conversations.
52
31
 
53
32
  ## What it cannot do
54
33
 
@@ -57,7 +36,7 @@ agy runs its own closed tool loop (`read_file`, `write_file`, `edit_file`, `run_
57
36
  Residual limits (with or without the bridge):
58
37
 
59
38
  - agy's own edits still land directly on disk; pi's inline diff review does not engage for them.
60
- - agy commands run without per-action approval, same as every other tool in pi. See [Permissions](#permissions) below.
39
+ - agy commands run without per-action approval by default, same as every other tool in pi. The [Approval gate](docs/APPROVAL-GATE.md) can put pi-side review in front of agy's mutating native tools (off by default; `auto` enables it only when a pi permission extension is installed). See also [Permissions](#permissions) below.
61
40
  - No cost accounting: cost stays zero because agy runs on your subscription quota. Token usage is live.
62
41
 
63
42
  ## MCP tool bridge (agy uses pi's tools)
@@ -91,6 +70,12 @@ your pi Agent Skills catalog; calling it returns the SKILL.md body. The bridge
91
70
  answers it directly, no pi round-trip. `/agy doctor` prints driver counters,
92
71
  bridge port, and the last lifecycle events without spending tokens.
93
72
 
73
+ ## Approval gate (agy native tools)
74
+
75
+ agy runs its own agent loop with native tools (`run_command`, `create_file`, `edit_file`, ...), which pi's permission extensions never saw. The optional approval gate routes those calls through a pi-side approval: a staged PreToolUse hook parks the call, the provider surfaces it as a shadow `bash`/`write`/`edit` toolUse, and your permission extension (or the built-in ask/allow/deny fallback) decides before agy executes it. Off by default (`approvals.gateMode: auto` enables it only when a pi permission extension is detected); denials fail closed; read-only agy tools stay ungated.
76
+
77
+ Full mechanics, configuration, and a sample gate extension: [docs/APPROVAL-GATE.md](docs/APPROVAL-GATE.md).
78
+
94
79
  ## Install
95
80
 
96
81
  > **No patch required.** The bridge runs on pi's public APIs only; the extension never edits your pi install. If an older version of this extension patched your pi (adding `pi.invokeTool()`), the leftover is inert and a pi update removes it. The extension detects it once and offers `/agy patch-cleanup` to restore the original files from the backup immediately.
@@ -101,7 +86,7 @@ Install with pi's package manager:
101
86
  pi install npm:@estebanforge/pi-antigravity-bridge
102
87
  ```
103
88
 
104
- 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.
89
+ 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. While the stream-json engine is active and the binary cannot be found, pi warns on every start (toast in the TUI, stderr headless) pointing at the install guide; the warning stops once the binary is detected.
105
90
 
106
91
  ## Usage
107
92
 
@@ -157,7 +142,7 @@ The `activate_skill` catalog mirrors pi's directory-based skill discovery: the t
157
142
  /agy system-prompt on|off send pi's system prompt + AGENTS.md + the Pi Bridge tool-priority note to new agy conversations (default on)
158
143
  /agy bridge all|mcp|none which pi tools the MCP bridge exposes to agy (default all; none = bridge off)
159
144
  /agy acp-bin <path|auto> point the ACP engine at a specific server binary (auto = setup installs, or AGY_ACP_BIN; applies on the next ACP turn)
160
- /agy engine acp|stream-json switch the turn engine (restart to apply; default stream-json; acp is beta and runs self-service setup: binary install + auth bootstrap)
145
+ /agy engine acp|stream-json switch the turn engine (restart to apply; default stream-json; acp is beta and runs self-service setup: binary install + auth bootstrap). No arguments opens the engine picker modal (TUI)
161
146
  /agy auth-manual manual ACP credential setup (fallback; auto-setup normally covers this; default login = your Antigravity subscription, same account as the agy CLI)
162
147
  /agy patch-cleanup restore the original pi files if an older version patched them
163
148
  /agy clear drop all session bindings (force fresh conversations)
@@ -171,6 +156,8 @@ Because agy runs non-interactively under this provider (nothing can answer a `y/
171
156
 
172
157
  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)).
173
158
 
159
+ For per-action review of agy's mutating native tools (`run_command`, `create_file`, `edit_file`, ...), see the [Approval gate](docs/APPROVAL-GATE.md): with it on, the call must pass a pi-side approval (your permission extension, or the built-in ask/allow/deny fallback) before agy executes it.
160
+
174
161
  ### Run pi inside a sandbox
175
162
 
176
163
  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.
@@ -46,8 +46,11 @@ Adopt the official Google ACP server (`agy_acp_server.par`, registry id
46
46
  `antigravity-acp`) as a second turn engine for the bridge, behind a config
47
47
  switch. The existing stream-json engine stays the default until the ACP
48
48
  engine proves parity. Every change is an improvement or a one-to-one
49
- replacement. No functionality is removed until a later phase deletes the
50
- streaming engine on purpose.
49
+ replacement.
50
+
51
+ STANDING DECISION (user, 2026-09-07): BOTH engines are permanently
52
+ maintained peers. The streaming engine is never deleted - no phase removes
53
+ it, regardless of upstream progress (including Gate B).
51
54
 
52
55
  Cross-references: [ARCHITECTURE.md](./ARCHITECTURE.md),
53
56
  [PI-BRIDGE-GAPS.md](./PI-BRIDGE-GAPS.md), [DEVELOPMENT.md](./DEVELOPMENT.md).
@@ -165,8 +168,8 @@ is an unmaintained contract. ACP is the maintained one.
165
168
  | Module | Role today | Disposition under ACP |
166
169
  | --- | --- | --- |
167
170
  | `src/provider.ts` | streamSimple: pi Context to agy turn to pi events; G9 round-trips; G1 digest; G10 system prompt | KEEP unchanged (additive thought-delta `if` only). Engine-agnostic by contract |
168
- | `src/driver.ts` | persistent stream-json process, turn queue, recycle, timers | KEEP as streaming engine. DELETE in phase 4 |
169
- | `src/stream-events.ts` | NDJSON parser + usage mapping | KEEP as streaming. DELETE in phase 4 |
171
+ | `src/driver.ts` | persistent stream-json process, turn queue, recycle, timers | KEEP permanently - both engines are maintained peers |
172
+ | `src/stream-events.ts` | NDJSON parser + usage mapping | KEEP permanently - both engines are maintained peers |
170
173
  | `src/sessions.ts` | pi session to agy conversation store | KEEP. Add `engine` tag (section 9.4) |
171
174
  | `src/config.ts` | runtime config | KEEP. Add engine + acp block (section 9.5) |
172
175
  | `src/models.ts` | `agy models` to pi Model projection | KEEP. Catalog source stays the CLI until ACP exposes one (probe A.6) |
@@ -200,7 +203,7 @@ in git history.
200
203
  | Cancel | Kill process group; in-flight turn dies | `session/cancel` to `stopReason: cancelled`, process survives | **REGRESSION-MANAGED (Gate D FAIL on RC01): cancel NOT implemented.** Fallback kill + `session/load` VERIFIED; re-check per build |
201
204
  | Process lifecycle | One agy child per provider; recycle on model/effort/mode/cwd/conversation drift | One server hosts N sessions | IMPROVEMENT |
202
205
  | Model + effort selection | CLI flags + recycle | `session/set_config_option` (`configId`) | PASS (VERIFIED live, Gate A): per-session switching, no recycle |
203
- | Usage tokens | `toPiUsage` from step events | Not in any payload (Gate B FINAL) | **ABSENT on RC01.** Zero-usage documented; stream-json retained as secondary engine; phase-4 deletion conditioned on Gate B lift |
206
+ | Usage tokens | `toPiUsage` from step events | Not in any payload (Gate B FINAL) | **ABSENT on RC01.** Zero-usage documented and ACCEPTED for the flip (user decision 2026-09-07: Gate B no longer blocks the default). Both engines are permanently maintained - no deletion |
204
207
  | MCP tools (G9) | Our HTTP bridge server via `--add-dir` config | `mcpServers` param (`{name,type:"http",url,headers:[]}`) | PARITY at shape level (verified). Phase-1 acceptance: bridge `tools/list`+`tools/call` end-to-end |
205
208
  | Skills | `--disable-slash-commands`; bridge owns skills | Unknown slash behavior; `available_commands_update` exists | PARITY. Keep our bridge; probe F.7 |
206
209
  | G1 digest, G10 system prompt | Provider-side prompt assembly | Same (we compose the prompt either way) | PARITY. `embeddedContext` is a later enhancement |
@@ -318,7 +321,7 @@ Tested with the parity suite (section 11) and `scripts/parity-live.mjs`
318
321
 
319
322
  | Risk | Impact | Mitigation |
320
323
  | --- | --- | --- |
321
- | Usage tokens absent (CONFIRMED, Gate B) | Cost/token display zeros on ACP | Zero-usage documented; stream-json retained as SECONDARY engine until upstream ships usage phase-4 deletion is conditioned on Gate B lifting (review 4, finding 2) |
324
+ | Usage tokens absent (CONFIRMED, Gate B) | Cost/token display zeros on ACP | ACCEPTED 2026-09-07 (user decision): Gate B no longer blocks the default flip - zero-usage is documented behavior on ACP. Both engines are permanently maintained; no deletion (standing decision), so zero-usage is opt-in per user forever (review 4, finding 2, as amended) |
322
325
  | ~~No model/effort switch per session~~ RESOLVED: Gate A PASS | — | `session/set_config_option` verified live; per-session switching, no recycle |
323
326
  | Slash commands expanded server-side | Could double-expand with our prompt assembly | Probe F.7. If present: keep our prefixes out of command-looking lines, or ignore server command list |
324
327
  | `request_permission` with no pi-side permission UI | Cannot render a native dialog | Single `auto` policy (parity with `skipPermissions: true`); per-tool gating for pi tools belongs to the tools/extensions and is preserved end-to-end by G9 (9.3). Never hang |
@@ -463,7 +466,7 @@ src/acp/connection.ts process lifecycle: spawn AGY_ACP_BIN, initialize
463
466
  / load / prompt / cancel / set_config_option, client-side
464
467
  method handlers (request_permission policy, fs, terminal),
465
468
  outbound event queue.
466
- src/acp/driver.ts AcpDriver: implements the AgyDriver surface (section 9.2)
469
+ src/acp/driver.ts AcpDriver: implements the StreamDriver surface (section 9.2)
467
470
  so provider.ts keeps working unchanged. State machine,
468
471
  timers, parks, snapshot, close.
469
472
  src/acp/events.ts session/update to DriverActivity mapping + stopReason
@@ -475,7 +478,7 @@ scripts/smoke-acp.mjs live smoke, gated by AGY_ACP_LIVE=1 (spends quota),
475
478
 
476
479
  ### 9.2 Driver contract (unchanged surface, additive only)
477
480
 
478
- `AcpDriver` matches the existing `AgyDriver` public surface: `state`,
481
+ `AcpDriver` matches the existing `StreamDriver` public surface: `state`,
479
482
  `activeHandle`, `run()`, `reentry()`, `kickIdle()`, `set onTurnEnd()`,
480
483
  `snapshot()`, `close()`. `DriverActivity` gains one additive variant; the
481
484
  streaming engine never emits it:
@@ -490,8 +493,9 @@ streaming engine never emits it:
490
493
  the current token-count behavior. One `if`, additive, streaming untouched.
491
494
 
492
495
  Interface extraction (review 2, finding 7): `provider.ts` imports the
493
- concrete `AgyDriver` class today; deleting `driver.ts` in phase 4 would break
494
- the import. Phase 1 extracts a `TurnDriver` interface into a neutral module
496
+ concrete `StreamDriver` class today; without a neutral type the two engine
497
+ wirings would each drag the other's module in. Phase 1 extracts a
498
+ `TurnDriver` interface into a neutral module
495
499
  (`src/driver-types.ts`); both drivers implement it; `provider.ts` and
496
500
  `ToolRoundTrips` depend on the interface only. `/agy engine` changes require
497
501
  a pi restart (driver wiring happens at extension load); a live engine router
@@ -771,21 +775,22 @@ Acceptance: parity suite remains 14/14, doctor parity, no breakage to stream-jso
771
775
 
772
776
  Status: 🚧 Phase 3 remaining items are COMPLETE (2026-09-04): 168/168 tests,
773
777
  tsc clean, live parity 14/14, embeddedContext verified live (resource block
774
- with a secret word answered correctly). Remaining phase-3 acceptance:
775
- deleted-code census lands with phase 4's deletions (nothing deletes in
776
- phase 3 the streaming modules stay for the `stream-json` engine).
778
+ with a secret word answered correctly). Phase-3 acceptance: nothing deletes
779
+ in phase 3 the streaming modules stay (permanently, see the standing
780
+ decision at the top and Phase 4).
777
781
 
778
- ### Phase 4: default flip and streaming deletion (deferred, next month)
782
+ ### Phase 4: default flip (deferred)
779
783
 
780
784
  All items below are deferred until a full soak cycle of both engines has completed and upstream conditions are met:
781
785
 
782
786
  1. AskAntigravity migration to ACP one-shot (migrating while `stream-json` is default breaks streaming-only users who haven't onboarded ACP auth; streaming conversation ids cannot resume under ACP). `mode: "plan"` delegations keep the `agy -p --mode plan` path (committed exception).
783
787
  2. Delete `src/discovery.ts` (`/proc` fd-scan) once AskAntigravity migration completes.
784
- 3. Default flip: `config.engine` default becomes `"acp"` after one full release soak cycle with both engines shipping. Gate A PASS is verified.
785
- 4. Upstream Gate B resolution: Google ships usage counters in ACP payloads.
786
- 5. Streaming engine deletion: `src/driver.ts`, `src/stream-events.ts`, and streaming tests deleted one release after the flip AND only once Gate B has lifted (so zero-usage is never forced).
788
+ 3. Default flip: `config.engine` default becomes `"acp"` after one full release soak cycle with both engines shipping. Gate A PASS is verified. Gate B is NOT a precondition (lifted as a blocker 2026-09-07); the soft items in section 17 ship in the same release.
789
+ 4. Upstream Gate B resolution (optional, informational): Google ships usage counters in ACP payloads. The `/agy doctor` watch reports it when it happens. Mapping it in is a small additive job; the streaming engine stays regardless.
790
+
791
+ NO deletion phase: the streaming engine is a permanently maintained peer (standing user decision, 2026-09-07). The old phase-4 "streaming deletion" item is void.
787
792
 
788
- Acceptance: default-flip release ships with the parity suite as acceptance evidence; deletion release has zero references to the removed modules.
793
+ Acceptance: the default-flip release ships with the parity suite as acceptance evidence.
789
794
 
790
795
  ## 11. Test strategy
791
796
 
@@ -873,3 +878,63 @@ answers: model catalog ships in `session/new` (CLI catalog unnecessary on
873
878
  ACP); edits surface as diffs inside `tool_call` content even with fs
874
879
  capabilities off; no usage fields anywhere; available_commands_update carries
875
880
  `plan` and `logout`; cold start ~5-6 s, steady RSS ~327 MB.
881
+
882
+ ## 17. Default-flip readiness (phase 4) — analysis as of 2026-09-07
883
+
884
+ Standing analysis so nobody re-derives it: what blocks flipping the default
885
+ engine from stream-json to ACP. Re-verify only the dated facts (Gate B
886
+ status via `/agy doctor`); the structural conclusions hold.
887
+
888
+ **Verdict: nothing blocks the flip. Gate B was REMOVED as a blocker on
889
+ 2026-09-07 (user decision: token usage does not matter for the extension's
890
+ purpose - agy runs on subscription quota, cost stays zero either way).
891
+ Zero-usage on ACP is accepted as documented behavior. Remaining before the
892
+ flip: the four soft items below, shipped in the same release.**
893
+
894
+ Already green:
895
+
896
+ - Parity contract (section 6) is ALL `[x]`, live-verified on both engines
897
+ (parity suite 14/14).
898
+ - Everything shipped since 1.4.9 is engine-aware: bridge registration is
899
+ ACP-native (`session/new` mcpServers) AND stream-json-native
900
+ (`~/.gemini/config/mcp_config.json`); tool-result images ride both engines
901
+ (probe-verified on each); the approval gate's ACP hook firing is the
902
+ LIVE-verified side (stream-json is docs-attested only), so the flip does
903
+ not weaken it.
904
+ - Rollback is one config flip; sessions are engine-scoped so bindings never
905
+ cross.
906
+
907
+ Former blocker — Gate B, LIFTED 2026-09-07:
908
+
909
+ - RC01 sends no usage/token fields in ANY payload, so ACP shows zero
910
+ tokens. This used to block the default flip; the user lifted it: agy
911
+ runs on subscription quota and cost is zero regardless, so the display
912
+ gap is cosmetic for this extension. Zero-usage on ACP is documented
913
+ behavior.
914
+ - Both engines are PERMANENTLY maintained (standing user decision
915
+ 2026-09-07): no deletion phase exists, regardless of Gate B. stream-json
916
+ is the supported fallback/secondary forever; zero-usage is opt-in per
917
+ user by choosing the engine.
918
+ - The `/agy doctor` Gate B watch stays armed as INFORMATIONAL: it prints
919
+ "acp tokens: AVAILABLE" if upstream ever ships usage, at which point the
920
+ mapping is a small additive job.
921
+
922
+ Soft items to ship alongside the flip (not blockers):
923
+
924
+ 1. Clean-machine first-run: ACP as DEFAULT means fresh installs hit the
925
+ self-setup path (binary install + auth bootstrap) immediately. Self-heal
926
+ exists; never tested as the first-run experience. Test once on a clean
927
+ env.
928
+ 2. User-visible abort change: Esc on ACP = kill + reconnect + reload (RC01
929
+ has no `session/cancel`). Document in README and `/agy status`.
930
+ 3. Pull soak evidence from the daily logs (warn rates per engine, 1.4.9+)
931
+ and attach to this section.
932
+ 4. Note that `AskAntigravity` stays on `agy -p` regardless (phase 4+).
933
+
934
+ Mechanics of the flip (minutes): default in `src/config.ts` + the
935
+ `acp-config` test default + README engine table + AGENTS.md + beta wording.
936
+ `AGY_ENGINE` env override keeps an escape hatch either way.
937
+
938
+ Recommendation on record: Gate B is no longer a condition. Remaining
939
+ pre-flip work is the soft items above; the flip itself is a small,
940
+ mechanical change whenever the soak cycle is judged complete.
@@ -259,9 +259,10 @@ unreachable on RC01 — no cancel method).
259
259
 
260
260
  NO usage/token fields appeared anywhere in `initialize`, `session/new`,
261
261
  `set_config_option`, `agent_message_chunk`, prompt results, or tool flows
262
- (run 6). Gate B FINAL: ABSENT on RC01. Consequence in the plan: stream-json
263
- is retained as a secondary engine until upstream ships usage (phase-4
264
- deletion conditioned on Gate B lifting).
262
+ (run 6). Gate B FINAL: ABSENT on RC01. Consequence in the plan (updated
263
+ 2026-09-07): Gate B is INFORMATIONAL - zero-usage is documented and accepted
264
+ on ACP, never a blocker. Both engines are permanently maintained peers; no
265
+ deletion is planned.
265
266
 
266
267
  ## session/cancel
267
268
 
@@ -0,0 +1,33 @@
1
+ # Approval gate (agy native tools)
2
+
3
+ agy is not a plain model: it runs its own agent loop with its own native tools (`run_command`, `create_file`, `edit_file`, ...). Those calls execute inside agy with no pi involvement, so pi's permission extensions never saw them. The approval gate closes that gap: agy native tool calls pass through a pi-side approval in a form the existing permission-extension ecosystem gates with zero changes.
4
+
5
+ Mechanics: the extension stages an `.agents/hooks.json` group in the workspace; the Antigravity server/CLI fires a `PreToolUse` hook before each mutating native tool runs. The hook script (generated, per-pid, mode 0600 because it embeds the bridge token) POSTs the call to the bridge and polls for a decision. The bridge parks it, the provider interrupts the pi-side view of the agy turn with a `toolUse` for a SHADOW tool named `bash`/`write`/`edit` (same schema as the real builtin plus internal `__agy*` marker fields), and pi's whole `tool_call` surface applies: any installed permission extension gates the call unchanged, and only if nothing blocks does the fallback policy run. The decision travels back to the hook and agy enforces it; on deny the reason text is what agy's model sees. Marker calls never execute locally (a ticket check denies forged ones); non-marker calls delegate to a factory twin of the real builtin, so normal pi bash/write/edit behavior is unchanged. Read-only agy tools stay ungated. Every decision lands in the daily log with tool names, source, and latency.
6
+
7
+ Configuration (`/agy` config keys or environment):
8
+
9
+ ```jsonc
10
+ {
11
+ "approvals": {
12
+ "gateMode": "auto", // auto | shadow | dedicated | off
13
+ "mode": "ask" // ask | allow | deny (fallback when no extension gates)
14
+ }
15
+ }
16
+ // env: AGY_APPROVALS=shadow AGY_APPROVALS_MODE=ask
17
+ ```
18
+
19
+ `auto` (default) keeps the gate OFF until one of the known pi permission packages is detected (pi settings `packages` name-match or known config markers). `shadow` forces it on; `off` forces it off. `dedicated` currently stages the same shadow tools (the explicit `antigravity_approve` variant is planned; the config value is accepted today so the schema is stable). The fallback `mode` is consulted only when no extension blocked the call: `ask` shows a pi confirm dialog (headless runs deny, fail-closed), `allow` approves, `deny` blocks. Timeouts deny fail-closed: the staged hook timeout always exceeds the park budget, but a hook that outlives its timeout soft-passes upstream (verified against the ACP server), so the park always answers first.
20
+
21
+ If you write your own gate extension, it sees a normal pi tool call:
22
+
23
+ ```typescript
24
+ export default function (pi) {
25
+ pi.on("tool_call", async (event) => {
26
+ if (event.input?.__agyGate && event.input.command?.startsWith("rm ")) {
27
+ return { block: true, reason: "rm is not allowed through the agy gate" };
28
+ }
29
+ });
30
+ }
31
+ ```
32
+
33
+ Live end-to-end verification (drive a real mutating agy turn through the gate) is still pending; see docs/TODO.md section 1.
@@ -26,8 +26,13 @@ src/sessions.ts atomic JSON store: pi session -> agy conversation + waterm
26
26
  src/config.ts persisted runtime config (engine + acp block, bridgeTools, digest, mode, permissions, model/thinking defaults)
27
27
  src/daily-log.ts daily NDJSON support log (one file per day, 14-day retention, secret redaction, AGY_DEBUG verbose gate); fed by both drivers, the bridge, round-trips, /agy, and ask-tool
28
28
  src/ask-tool.ts the AskAntigravity one-shot delegation tool (model/thinking defaults)
29
- src/mcp-server.ts MCP tool bridge server: ferries tools/list + tools/call; calls park in the provider round-trip
29
+ src/mcp-server.ts MCP tool bridge server: ferries tools/list + tools/call; calls park in the provider round-trip. Also the approval park: POST /approval (ticket early-ack) + GET /approval/<id>, fail-closed on timeout/unwired/close
30
+ src/mcp-registration.ts registers/unregisters the bridge in ~/.gemini/config/mcp_config.json for the stream-json CLI (per-pid, atomic, stale sweep)
31
+ src/approval-gate.ts shadow tool factory (bash/write/edit): marker calls are ticket-verified approval round-trips, non-marker calls delegate; maps agy native tools onto the shadow surface
32
+ src/approval-detect.ts third-party pi permission-extension detection; resolves approvals.gateMode auto (off until a gate extension exists)
33
+ src/approval-hook.ts merge-safe .agents/hooks.json staging (PreToolUse) + generated 0600 early-ack/poll hook script; staged timeout exceeds the park budget (hook timeouts soft-pass)
30
34
  src/diff-render.ts stream-json: render agy's file edits as git diffs in pi's thinking stream; formatInlineDiff (no git) renders ACP's native diffs
35
+ src/engine-picker.ts first-run onboarding: engine picker overlay (SelectList + DynamicBorder), first-run gate (no config file + no AGY_ENGINE), agy binary detection (PATH / AGY_BIN), missing-CLI toast copy
31
36
  src/driver-types.ts TurnDriver contract shared by both engines (request/handle/snapshot types)
32
37
  src/acp/jsonrpc.ts NDJSON JSON-RPC 2.0 framing with line buffering and typed error results
33
38
  src/acp/connection.ts ACP server process + protocol (initialize, session/new+load, prompt with image/resource blocks, config options, cancel probing, auto permissions)
@@ -71,9 +76,10 @@ The pre-1.3.0 engine (spawn `agy -p`, poll the SQLite conversation DB, decode pr
71
76
 
72
77
  `config.engine: "acp"` (or `/agy engine acp`) routes turns through Google's
73
78
  official ACP server (`agy_acp_server.par`, registry id `antigravity-acp`)
74
- over JSON-RPC stdio. Off by default; stream-json remains the default and a
75
- supported secondary phase-4 deletion is conditioned on upstream shipping
76
- usage fields (Gate B), per docs/ACP-ADOPTION-PLAN.md.
79
+ over JSON-RPC stdio. Off by default; stream-json remains the default. BOTH
80
+ engines are permanently maintained peers (standing decision - no deletion,
81
+ regardless of Gate B). Gate B (absent usage fields) is informational:
82
+ zero-usage is documented on ACP. See docs/ACP-ADOPTION-PLAN.md section 17.
77
83
 
78
84
  Modules: `src/acp/jsonrpc.ts` (framing/correlation), `src/acp/connection.ts`
79
85
  (process + protocol methods + in-connection `auto` permission answering),
@@ -85,7 +91,9 @@ Phase-2/3 additions (all ACP-only, verified live):
85
91
 
86
92
  - **Images**: pi image attachments ride as typed content blocks in the
87
93
  prompt array; models advertise `input: ["text","image"]` only when the
88
- engine is `acp` (decided at extension load). stream-json stays text-only.
94
+ engine is `acp` (decided at extension load). stream-json prompt input
95
+ stays text-only (tool-RESULT images ride both engines; see
96
+ PI-BRIDGE-GAPS).
89
97
  - **Digest delivery**: with `config.digest` on, ACP ships the G1 digest as
90
98
  a native `embeddedContext` resource block (images → resource → text);
91
99
  stream-json keeps it inline. Same cache churn either way.
@@ -57,6 +57,18 @@ AGY_ACP_LIVE=1 AGY_ACP_BIN=~/.local/opt/agy-acp/current/agy_acp_server.par \
57
57
  AGY_ACP_LIVE=1 AGY_ACP_BIN=~/.local/opt/agy-acp/current/agy_acp_server.par \
58
58
  npx tsx scripts/probe-acp-phase2.mjs
59
59
 
60
+ # Live probe: does the ACP server deliver MCP tool-result IMAGE content to
61
+ # the model? One bridge tool returns a two-tone PNG in its result; the model
62
+ # must name both halves from the tool result alone.
63
+ AGY_ACP_LIVE=1 AGY_ACP_BIN=~/.local/opt/agy-acp/current/agy_acp_server.par \
64
+ npx tsx scripts/probe-acp-image-result.mjs
65
+
66
+ # Live probe: same image question for the stream-json engine. AGY
67
+ # bridge tool returns a two-tone PNG in its result; model must name both
68
+ # halves. AGY_PROBE_REG_ONLY=1 skips the turn and dumps MCP registration
69
+ # state (no quota).
70
+ AGY_LIVE=1 npx tsx scripts/probe-stream-json-image.mjs
71
+
60
72
  # Live parity run: the SAME scenario set (streaming, continuity, bridge
61
73
  # round-trip, effort switch, serialization, abort+recover, usage) through
62
74
  # BOTH engines. Needs the agy CLI AND the ACP binary. Spends ~13 flash-low
@@ -86,6 +98,11 @@ Most "stuck" reports trace to one of:
86
98
  - `tests/acp-driver.test.ts` - the ACP driver over the fake server (`tests/helpers/fake-acp-server.mjs`, scenario-selected): happy flow, load-replay, permission auto-answer, Gate D abort (cancel probe, teardown, `cancelSupported` memory), the stale-exit race (a killed connection's late exit must not fail its replacement - `ACP_FAKE_SLOW_DEATH_MS`), auth errors, park/kickIdle timer pause with remaining budget.
87
99
  - `tests/acp-config.test.ts` - engine selection narrowing (`AGY_ENGINE`/`config.engine`), acp block parsing.
88
100
  - `tests/daily-log.test.ts` - the support log: day rotation, retention cutoff boundary, secret redaction (incl. header blocks), the 4 KB record cap, never-throw on a broken dir, and the two-tier gate (debug records dropped unless `AGY_DEBUG`).
101
+ - `tests/mcp-registration.test.ts` - the `~/.gemini/config/mcp_config.json` registration for the stream-json CLI: exact agy entry shape, foreign servers preserved, corrupt config refused, atomic writes, stale-entry sweep.
102
+ - `tests/engine-picker.test.ts` - first-run onboarding: picker gate (no config file + no AGY_ENGINE, fail-closed fs), option order (stream-json first), the ACP download/sign-in disclosure pins (intro + saved toast), agy binary detection (PATH scan, explicit path, fail-closed), missing-CLI toast copy.
103
+ - `tests/provider-escalation.test.ts` - the early-ack + poll pipeline (escalation registry, poll views, late-delivery tombstones) and tool-result image forwarding on both engines.
104
+ - `tests/approval-gate.test.ts` - the shadow tool factory: marker calls never execute, ticket verification denies forged/stale markers before the policy, denials throw, native-to-shadow mapping.
105
+ - `tests/approval-park.test.ts` - the approval park end-to-end over a real (port 0) server: POST ticket early-ack, poll pending -> terminal, timeout deny, ungated/unwired deny, close fail-closed, provider round-trip (allow / block-deny / timeout), `__agy*` strip on G9 args.
89
106
 
90
107
  ## Module map
91
108
 
@@ -0,0 +1,46 @@
1
+ # Engines
2
+
3
+ Turns run through one of two engines behind the same provider surface (`config.engine`, default `stream-json`). The choice of engine is left to the user: a first-run picker explains the trade-offs and asks once, and `/agy engine` (no arguments, TUI) reopens the same modal anytime. Direct switches work too: `/agy engine acp|stream-json`.
4
+
5
+ - **stream-json** (default): the persistent `agy` CLI process. The tested default; live token usage; conversation resume via `--conversation`.
6
+ - **acp** (beta): Google's official ACP server (`agy_acp_server.par`), JSON-RPC 2.0 over stdio. Beta: parity-verified live against the current build (RC01) - text streaming, multi-turn resume via `session/load`, bridge tools, effort switching, serialization, abort recovery (see `scripts/parity-live.mjs`). Two known RC01 gaps remain: no usage fields (token display shows client-side ESTIMATES until Google ships usage; `acp.usageEstimate` off to keep zeros) and no cancel (abort tears the server down and reloads it next turn).
7
+
8
+ ## First run
9
+
10
+ On a fresh install (no `config.json` yet), the first interactive pi start opens a picker modal that explains both engines - stream-json needs the `agy` CLI installed and authenticated; ACP needs a second Google sign-in plus a ~1.5 GB server binary downloaded from Google. stream-json is preselected (the default); `esc` decides later (nothing is written, the modal reappears on the next start).
11
+
12
+ Picking **acp** starts the server download immediately (progress in the status bar, milestones in the chat), then opens the Google sign-in; a restart applies the engine. Picking **stream-json** persists and toasts; while that engine is active and the `agy` binary is missing, pi warns on every start with the install link until the binary shows up.
13
+
14
+ ## Capabilities
15
+
16
+ | Capability | `stream-json` (default) | `acp` (beta) |
17
+ | --- | --- | --- |
18
+ | Show thinking text | No (token count only, floor 64, no text body) | Yes (streams thought text via `agent_thought_chunk`; sparse on RC01 where reasoning often arrives in message text) |
19
+ | Live token usage | Yes (live metrics from CLI step events) | Estimated client-side (absent in RC01; `acp.usageEstimate`, default on) |
20
+ | Image prompt input | No (CLI prompt is text-only; images dropped) | Yes (native image blocks forwarded to server) |
21
+ | Image tool results | Yes (bridge tool results carry pixels; probe-verified 2026-09-07) | Yes (probe-verified 2026-09-05) |
22
+ | Audio prompt input | No (dropped) | Protocol advertised (`promptCapabilities.audio: true`) |
23
+ | Review-only plan mode | Yes (`--mode plan` review-only via `/agy mode plan`) | No (RC01 modes are permission levels; plan mode refused) |
24
+ | Leading slash commands in prompt | Disabled via `--disable-slash-commands` (sent as plain text) | Server intercepts recognized commands (e.g. `/plan`) and executes them under the active policy |
25
+ | Dynamic model / effort switch | Recycles process on model or effort change | Dynamic per-turn via `session/set_config_option` (no restart) |
26
+ | Process lifecycle | 1 persistent `agy` process per provider; recycles on drift | 1 persistent server process hosting N sessions concurrently |
27
+ | Session resume & persistence | Client-side map in `sessions.json` via `--conversation <id>` | Server-side session store via `session/load` and `session/new` |
28
+ | Turn cancel / abort | Kills process group; in-flight turn terminates | Teardown, kill, and auto-reload on RC01 (-32601 fallback) |
29
+ | MCP tool bridge routing | Injected filesystem config via `--add-dir` | Direct `mcpServers` param in `session/new` and `session/load` |
30
+ | Tool execution & visibility | Native re-exec (read-only) + wrapper replay (mutating) | Server executes tools natively; events stream with content |
31
+ | Inline file edit diffs | Sourced from git working tree in thinking block | Sourced from `tool_call content[]` or disk vs git HEAD |
32
+ | Permission handling | `--dangerously-skip-permissions` (unattended CLI requirement) | Protocol-native `session/request_permission` (auto-approve when `skipPermissions` is on; auto-deny when off) |
33
+ | Context digest delivery (G1) | Prepend plain text inline in prompt | Native `embeddedContext` resource block |
34
+ | System prompt delivery (G10) | Prepend to first prompt of conversation | Prepend to first prompt of conversation |
35
+ | Authentication methods | Inherits existing `agy` CLI OAuth state | 4 methods: `oauth-personal`, `oauth-business`, `gemini-api-key`, `agent-platform` |
36
+ | Wire protocol | Undocumented CLI NDJSON stream format | Versioned JSON-RPC 2.0 over stdio (`protocolVersion: 1`) |
37
+ | Diagnostics (`/agy doctor`) | Child PID, state, process spawns, recycles, queue stats | Server version, agentInfo, session counts, reconnect count, cancel support |
38
+ | Integration channel | Spawns internal CLI stream-json dialect | Official Google first-party ACP server binary |
39
+
40
+ ## Engine-dependent features
41
+
42
+ pi image attachments ride natively only on the ACP engine (the picker offers image attach automatically when `config.engine` is `acp`; the stream-json CLI prompt is text-only). With the optional G1 digest enabled, its delivery also differs: ACP ships it as a native `embeddedContext` resource block, stream-json prepends it to the prompt text. The `AskAntigravity` delegation tool is unaffected by `config.engine` and runs the `stream-json` CLI (`agy -p`) across both configurations.
43
+
44
+ ## Switching and setup
45
+
46
+ Switch with `/agy engine acp|stream-json` (takes effect on restart), or run `/agy engine` with no arguments for the same picker modal as first run (an `acp` pick there runs the same download + sign-in chain). Setup is automatic: switching to `acp` installs Google's official ACP server binary from the [antigravity-acp registry entry](https://github.com/agentclientprotocol/registry) (`~/.local/opt/agy-acp/<build>/` + a `current` symlink, zip sha256 recorded; layout and pinning in [docs/ACP-ADOPTION-PLAN.md](ACP-ADOPTION-PLAN.md)) and prepares the login. The login is your Antigravity subscription: the same account and plan you use for the Antigravity CLI (`agy`). Sign in explicitly with `/agy auth` (engine `acp` selected): it opens the Google login in your browser and completes when you finish it. If no browser is available (an SSH session on a remote machine), pi shows the sign-in URL to copy, plus the ssh port-forward command for the login redirect. It is no different from logging into the CLI; the server just keeps its own token file on your machine, like any Google tool, and this extension never sees your credentials. If you also export `GEMINI_API_KEY`, it is ignored: the server uses the auth type in settings.json, and setup always writes `oauth-personal`. A session start self-heals the same way, silently when everything is ready. Manual instructions (`/agy auth-manual`) surface only when a step fails. Sessions are engine-scoped, so switching engines never crosses conversations.
@@ -61,13 +61,34 @@ Weighed and rejected; kept so they are not re-proposed.
61
61
  credentials, so agy already uses pi's creds for every tool; a
62
62
  credential never crosses the bridge. A `pi_get_setting` accessor was
63
63
  predicated on credential reuse that does not apply.
64
- - **Image / binary content blocks over the bridge** — NOT NEEDED. pi shares
65
- the path to any image it produces (e.g. `/tmp/pi-clipboard-<uuid>.png`), and agy
64
+ - **Image / binary content blocks over the bridge** — DONE (ACP engine).
65
+ Reopened 2026-09-05: the prior verdict below was falsified. On the ACP
66
+ engine, native `view_file` rejects real filesystem paths (artifact sandbox;
67
+ the tool-priority note steers agy to bridge tools), and bridge `read`
68
+ flattened pi's tool result with `blocksToText`, which drops the image block.
69
+ agy received only the label text and fell back to shell + `mcp-cli-ent
70
+ ai-vision`. Probe `scripts/probe-acp-image-result.mjs` (2026-09-05) proved
71
+ the ACP server delivers MCP tool-result image content to the model (PASS:
72
+ the model identified a two-tone PNG from the tool result alone, with no
73
+ image in the prompt). Fix: image blocks in parked pi tool results now ride
74
+ bridge results as MCP image content on the ACP engine (fast resolve path,
75
+ escalation registry, and `bridge_poll_result`), so `read` on an image file
76
+ gives agy real pixels. DONE on stream-json too (probe
77
+ `scripts/probe-stream-json-image.mjs`, 2026-09-07: the CLI's MCP client
78
+ delivers tool-result image content to the model — two-tone PNG named from
79
+ the result alone, bridge tool called once, zero decoders in the frame
80
+ trail). Image blocks now ride parked pi tool results on both engines
81
+ (fast resolve path, escalation registry, and `bridge_poll_result`), so
82
+ `read` on an image file gives agy real pixels everywhere. Still
83
+ text-only, deliberately: the late-delivery prompt
84
+ (`buildLateResultPrompt`), and the stream-json prompt attachments.
85
+ Superseded verdict (kept for the record): NOT NEEDED. pi shares the path to
86
+ any image it produces (e.g. `/tmp/pi-clipboard-<uuid>.png`), and agy
66
87
  reaches and reads those files directly via the bridge's `read` tool, so
67
88
  returning image content blocks over the transport would duplicate a path
68
- that already works end-to-end. No agy transport change or pi patch required.
69
- Update (2026-09-04): user-provided image *attachments* now ride natively on
70
- the ACP engine as typed prompt content blocks (see README, Two
89
+ that already works end-to-end. No agy transport change or pi patch
90
+ required. Update (2026-09-04): user-provided image *attachments* now ride
91
+ natively on the ACP engine as typed prompt content blocks (see README, Two
71
92
  engines); the stream-json CLI prompt stays text-only, and the bridge
72
93
  direction above is unchanged.
73
94
  - **File-watching / live state** — DECLINED. agy is request-response
package/docs/TODO.md ADDED
@@ -0,0 +1,21 @@
1
+ # TODO
2
+
3
+ ## 1. Approval gate: live end-to-end verification
4
+
5
+ The gate is wired and unit-pinned (333 tests) but has never run against a live
6
+ agy turn. Enable it, drive an agy turn that mutates a file, and watch the
7
+ round trip: PreToolUse hook -> POST /approval park -> shadow toolUse ->
8
+ decision -> hook stdout -> agy enforces.
9
+
10
+ - Enable without a third-party extension: `AGY_APPROVALS=shadow AGY_APPROVALS_MODE=ask` (interactive pi; headless denies). Deny path: a fake gate extension that blocks marker calls (`pi.on("tool_call")` + `{block: true, reason}`).
11
+ - Probe artifacts live outside the repo: `~/tmp/pi-antigravity-bridge-probes/` (run scripts via `npx tsx` from the repo cwd - they import src/*.ts).
12
+ - Live-behavior risks to watch: hook timeout soft-passes (V3) - the staged timeout must keep exceeding the park budget; denied calls emit no `tool_call` session/update frames on ACP (V2); edit-class arg names beyond `create_file` are docs-attested, never live-captured - check the confirm-dialog text on a real `run_command` and a real `replace_file_content`.
13
+ - On pass: clear the "NOT yet live-verified" notes (AGENTS.md, this file).
14
+
15
+ ## 2. Explicit `antigravity_approve` variant (dedicated mode)
16
+
17
+ `approvals.gateMode: "dedicated"` currently stages the same shadow tools
18
+ (warn-logged remap). Planned: one registered tool `antigravity_approve` with
19
+ input `{toolName, args}`, for setups that prefer explicit names (gotgenes
20
+ `shellTools` alias users). Full v2 spec preserved in git history:
21
+ `dd7845b:docs/TODO.md` (sections 2.1-2.9).