@estebanforge/pi-antigravity-bridge 1.4.8 → 1.4.10

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,36 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.4.10] - 2026-09-07
6
+
7
+ ### Added
8
+
9
+ - 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.
10
+
11
+ ### Fixed
12
+
13
+ - 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).
14
+ - 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).
15
+ - 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.
16
+ - 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.
17
+
18
+ ## [1.4.9] - 2026-09-05
19
+
20
+ ### Added
21
+
22
+ - Warn toasts in the pi UI: bridge tool failures, stalls, timeouts, process exits, and other warnings surface as native warning notifications the moment they happen, so users see them and can report them. Over SSH or without a UI the same text falls back to stderr. Deliberate aborts, connection exits (the turn's own error block carries real crashes), and `call-tool-fail` (same-instant duplicate of `round-trip-fail`) stay silent.
23
+ - Gate B watch in `/agy doctor`: the ACP connection latches the first `session/update` payload that carries usage/token fields, and doctor prints one "acp tokens: AVAILABLE" line when that happens. Silent until then; the day the line appears, wiring real token counts becomes a small mapping job. Auth-style string `"token"` keys inside tool frames cannot trip the latch.
24
+
25
+ ### Fixed
26
+
27
+ - Esc-abort on the ACP engine no longer dumps the dying server's raw stderr tail (a google3 stack trace) into the transcript. Teardown exits (abort kill, shutdown, idle recycle) are marked expected and never render; a genuine mid-turn crash surfaces as the turn's own clean error block instead. Raw tails still land in the file log for post-mortems.
28
+
29
+ ### Changed
30
+
31
+ - The ACP engine is now beta. Parity is verified live against RC01 (text streaming, multi-turn resume, bridge tools, effort switching, serialization, abort recovery - see `scripts/parity-live.mjs`), so it graduates from opt-in-experimental to a supported alternative engine. It stays behind `config.engine` for now; the two known RC01 gaps (no usage fields, kill+reload abort) are documented in the README.
32
+ - Daily log volume: the default tier now writes ONLY errors, so a regular session costs the disk nothing. Warns live in the UI (see above); `AGY_DEBUG=1` restores the full debug/info/warn/error trail for reproducing a problem. Docs: README debug-logs section, `/agy doctor` hint.
33
+ - The tool-priority note now steers agy's native `view_file` (artifact-sandboxed on RC01) to the Pi Bridge file tools (`read`, `ls`, `grep`, `find`, `edit`, `execute`) for any real filesystem path. Observed live: repeated `invalid_args` rejections on `operator/pkg/tmux/client.go` before the model fell back to `edit`.
34
+
5
35
  ## [1.4.8] - 2026-09-05
6
36
 
7
37
  ### Added
package/README.md CHANGED
@@ -21,15 +21,16 @@ 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**: Google's official ACP server (`agy_acp_server.par`), JSON-RPC 2.0 over stdio. Opt-in while it matures: the current build (RC01) ships no usage fields (token display shows zero) and no cancel (abort tears the server down and reloads it next turn). Everything else is parity-verified live - text streaming, multi-turn resume via `session/load`, bridge tools, effort switching, serialization, abort recovery - see `scripts/parity-live.mjs`.
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).
25
25
 
26
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.
27
27
 
28
- | Capability | `stream-json` (default) | `acp` |
28
+ | Capability | `stream-json` (default) | `acp` (beta) |
29
29
  | --- | --- | --- |
30
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
31
  | Live token usage | Yes (live metrics from CLI step events) | No (absent in RC01, displays zero tokens) |
32
32
  | Image prompt input | No (CLI prompt is text-only; images dropped) | Yes (native image blocks forwarded to server) |
33
+ | Image tool results | Yes (bridge tool results carry pixels; probe-verified 2026-09-07) | Yes (probe-verified 2026-09-05) |
33
34
  | Audio prompt input | No (dropped) | Protocol advertised (`promptCapabilities.audio: true`) |
34
35
  | Review-only plan mode | Yes (`--mode plan` review-only via `/agy mode plan`) | No (RC01 modes are permission levels; plan mode refused) |
35
36
  | 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 |
@@ -57,7 +58,7 @@ agy runs its own closed tool loop (`read_file`, `write_file`, `edit_file`, `run_
57
58
  Residual limits (with or without the bridge):
58
59
 
59
60
  - 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.
61
+ - agy commands run without per-action approval by default, same as every other tool in pi. The [Approval gate](#approval-gate-agy-native-tools) 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
62
  - No cost accounting: cost stays zero because agy runs on your subscription quota. Token usage is live.
62
63
 
63
64
  ## MCP tool bridge (agy uses pi's tools)
@@ -91,6 +92,38 @@ your pi Agent Skills catalog; calling it returns the SKILL.md body. The bridge
91
92
  answers it directly, no pi round-trip. `/agy doctor` prints driver counters,
92
93
  bridge port, and the last lifecycle events without spending tokens.
93
94
 
95
+ ## Approval gate (agy native tools)
96
+
97
+ 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.
98
+
99
+ 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.
100
+
101
+ Configuration (`/agy` config keys or environment):
102
+
103
+ ```jsonc
104
+ {
105
+ "approvals": {
106
+ "gateMode": "auto", // auto | shadow | dedicated | off
107
+ "mode": "ask" // ask | allow | deny (fallback when no extension gates)
108
+ }
109
+ }
110
+ // env: AGY_APPROVALS=shadow AGY_APPROVALS_MODE=ask
111
+ ```
112
+
113
+ `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.
114
+
115
+ If you write your own gate extension, it sees a normal pi tool call:
116
+
117
+ ```typescript
118
+ export default function (pi) {
119
+ pi.on("tool_call", async (event) => {
120
+ if (event.input?.__agyGate && event.input.command?.startsWith("rm ")) {
121
+ return { block: true, reason: "rm is not allowed through the agy gate" };
122
+ }
123
+ });
124
+ }
125
+ ```
126
+
94
127
  ## Install
95
128
 
96
129
  > **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.
@@ -157,7 +190,7 @@ The `activate_skill` catalog mirrors pi's directory-based skill discovery: the t
157
190
  /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
191
  /agy bridge all|mcp|none which pi tools the MCP bridge exposes to agy (default all; none = bridge off)
159
192
  /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 runs self-service setup: binary install + auth bootstrap)
193
+ /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)
161
194
  /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
195
  /agy patch-cleanup restore the original pi files if an older version patched them
163
196
  /agy clear drop all session bindings (force fresh conversations)
@@ -171,6 +204,8 @@ Because agy runs non-interactively under this provider (nothing can answer a `y/
171
204
 
172
205
  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
206
 
207
+ For per-action review of agy's mutating native tools (`run_command`, `create_file`, `edit_file`, ...), see the [Approval gate](#approval-gate-agy-native-tools): 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.
208
+
174
209
  ### Run pi inside a sandbox
175
210
 
176
211
  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.
@@ -188,7 +223,7 @@ For isolation when running any agent that executes commands without a confirmati
188
223
  | `AGY_SKIP_PERMISSIONS` | `1`/`true` (default) to pass `--dangerously-skip-permissions` so commands don't hang on an unanswerable prompt in `-p` mode. `0`/`false` to prompt (hangs any `run_command` non-interactively). Wins over the config file. |
189
224
  | `AGY_DEFAULT_MODEL` | Default model alias for the `AskAntigravity` tool (`flash`/`pro`/`gemini`, or a tier/version qualifier). Wins over the config file. |
190
225
  | `AGY_DEFAULT_THINKING` | Default thinking tier for the `AskAntigravity` tool: `low`/`medium`/`high`. Anything else falls back to `medium`. Wins over the config file. |
191
- | `AGY_DEBUG` | `1`/`true`/`on` writes verbose debug records to the daily log (driver lifecycle, raw bridge traffic). Default off: only the light info/warn/error stream. |
226
+ | `AGY_DEBUG` | `1`/`true`/`on` writes the full trail to the daily log (driver lifecycle, raw bridge traffic). Default off: **only `error` records land on disk** - routine disk writes are zero for regular users, and warnings surface as UI toasts instead. |
192
227
 
193
228
  ## Debug logs
194
229
 
@@ -198,14 +233,14 @@ The extension keeps a daily log on your machine, sorted by day:
198
233
  ~/.pi/extensions-data/estebanforge/pi-antigravity-bridge/logs/<YYYY-MM-DD>.ndjson
199
234
  ```
200
235
 
201
- One JSON record per line. Two verbosity tiers keep the disk cost negligible for regular users: by default only `info`/`warn`/`error` records land on disk, which is the useful skeleton: turn starts and outcomes with error text (both engines), bridge tool calls, escalations and poll traffic, late deliveries, and round-trip failures, `AskAntigravity` runs, `/agy` commands, ACP setup/self-heal, auth URLs, and every driver failure (stall, abort, timeout, nonzero exit). Set `AGY_DEBUG=1` before reproducing a problem for the full trail: per-event driver lifecycle (spawn, exit, session load/new, unparks), list-tools traffic, recycle causes, and the raw bridge chatter. `/agy doctor` prints the log directory.
236
+ One JSON record per line. Two verbosity tiers keep the disk cost at zero for regular users: by default only `error` records land on disk - everything routine (turn starts, tool calls, escalations, commands, driver failures) is withheld. Warnings are not lost: the extension toasts each one in the pi UI (bridge tool failures, stalls, timeouts, process exits), so users see them and can report them; deliberate aborts and failures pi already renders as the turn's own error block stay silent. Set `AGY_DEBUG=1` before reproducing a problem for the full trail: per-event driver lifecycle (spawn, exit, session load/new, unparks), turn starts and outcomes, bridge tool calls, escalations and poll traffic, late deliveries, list-tools traffic, recycle causes, and the raw bridge chatter. `/agy doctor` prints the log directory.
202
237
 
203
238
  Notes:
204
239
 
205
240
  - Retention: 14 days. Older files are pruned automatically.
206
241
  - Privacy: prompt text, tool arguments, and tool output never land in the log. Secret-shaped values (tokens, API keys, credentials, header blocks) are redacted, and long strings are truncated. The `auth-url` record strips the login URL's query string.
207
242
  - Logging never throws: an unwritable directory is skipped silently and retried on the next record.
208
- - SSD wear: default volume is a handful of records per turn. Verbose mode (`AGY_DEBUG=1`) writes more; turn it off after reproducing.
243
+ - SSD wear: default volume is effectively zero until something actually errors. Verbose mode (`AGY_DEBUG=1`) writes more; turn it off after reproducing.
209
244
 
210
245
  When you report an issue, attach the last day or two of files from that directory. For anything that needs reproduction, run with `AGY_DEBUG=1` once and attach that day's file. They usually contain the exact failure sequence (engine, session, bridge call, error) with no need to guess.
211
246
 
@@ -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
 
@@ -26,7 +26,11 @@ 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
31
35
  src/driver-types.ts TurnDriver contract shared by both engines (request/handle/snapshot types)
32
36
  src/acp/jsonrpc.ts NDJSON JSON-RPC 2.0 framing with line buffering and typed error results
@@ -71,9 +75,10 @@ The pre-1.3.0 engine (spawn `agy -p`, poll the SQLite conversation DB, decode pr
71
75
 
72
76
  `config.engine: "acp"` (or `/agy engine acp`) routes turns through Google's
73
77
  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.
78
+ over JSON-RPC stdio. Off by default; stream-json remains the default. BOTH
79
+ engines are permanently maintained peers (standing decision - no deletion,
80
+ regardless of Gate B). Gate B (absent usage fields) is informational:
81
+ zero-usage is documented on ACP. See docs/ACP-ADOPTION-PLAN.md section 17.
77
82
 
78
83
  Modules: `src/acp/jsonrpc.ts` (framing/correlation), `src/acp/connection.ts`
79
84
  (process + protocol methods + in-connection `auto` permission answering),
@@ -85,7 +90,9 @@ Phase-2/3 additions (all ACP-only, verified live):
85
90
 
86
91
  - **Images**: pi image attachments ride as typed content blocks in the
87
92
  prompt array; models advertise `input: ["text","image"]` only when the
88
- engine is `acp` (decided at extension load). stream-json stays text-only.
93
+ engine is `acp` (decided at extension load). stream-json prompt input
94
+ stays text-only (tool-RESULT images ride both engines; see
95
+ PI-BRIDGE-GAPS).
89
96
  - **Digest delivery**: with `config.digest` on, ACP ships the G1 digest as
90
97
  a native `embeddedContext` resource block (images → resource → text);
91
98
  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,10 @@ 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/provider-escalation.test.ts` - the early-ack + poll pipeline (escalation registry, poll views, late-delivery tombstones) and tool-result image forwarding on both engines.
103
+ - `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.
104
+ - `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
105
 
90
106
  ## Module map
91
107
 
@@ -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 (304 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).