@estebanforge/pi-antigravity-bridge 1.6.3 → 1.7.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 +20 -1
- package/README.md +19 -17
- package/extensions/index.ts +163 -30
- package/package.json +5 -21
- package/src/acp/connection.ts +42 -2
- package/src/acp/driver.ts +42 -19
- package/src/acp/events.ts +8 -4
- package/src/acp/usage-estimate.ts +3 -1
- package/src/bridge-catalog.ts +29 -0
- package/src/config.ts +11 -0
- package/src/driver-types.ts +3 -2
- package/src/provider.ts +50 -15
- package/src/web-tools.ts +311 -0
- package/docs/ACP-ADOPTION-PLAN.md +0 -940
- package/docs/ACP-PROTOCOL-REFERENCE.md +0 -462
- package/docs/ANTIGRAVITY-INTEGRATIONS.md +0 -50
- package/docs/APPROVAL-GATE.md +0 -35
- package/docs/ARCHITECTURE.md +0 -116
- package/docs/DEVELOPMENT.md +0 -110
- package/docs/ENGINES.md +0 -46
- package/docs/PI-BRIDGE-GAPS.md +0 -97
- package/docs/TODO.md +0 -21
package/CHANGELOG.md
CHANGED
|
@@ -2,11 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.7.0] - 2026-09-23
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Opt-in web tools for any provider's model** (`agy_web_search`, `agy_read_url`). Pi ships no web tools and no MCP by default, so sessions driven by non-Antigravity models had no way to search or fetch. With `webTools: on` (`AGY_WEB_TOOLS`, `/agy web on`; off by default because Antigravity sessions already have native web tools on both engines), each call spawns a one-shot search-only `agy` agent (`--mode plan`, bridge MCP inheritance off, per-call agent dir under `~/.gemini/config/agents/pi-bridge-web-*` removed in `finally`), enforces a 120 s deadline and a 2 MiB output cap, tolerates agy's `OK`/`SUCCESS` result-status drift, requires an observed native `search_web`/`read_url_content` step before trusting the answer, and refuses the call if the agent touches any disallowed tool - the init frame advertises a broad tool list regardless of the agent's `tools:` restriction, so the response-side gate is the real anti-hallucination device. Stale agent dirs from crashed calls are swept by pid liveness at registration (marker-less dirs after a 24 h grace); dir names carry a random nonce so same-millisecond parallel calls cannot collide. URLs are scheme-allowlisted (http/https); queries cap at 2000 chars. Every call spends Antigravity subscription quota.
|
|
10
|
+
- **ACP native tool display cards.** The ACP engine had no card story: agy's own tool steps surfaced only as thinking-stream labels. agy's tool events now render as display-only pi entries (status icon plus the file path with colored diff lines, the first command line, or the captured output; persisted content bounded at 4000/12000/500 chars), streamed as steps start and complete. Events attributed to `mcpServer: "pi-bridge"` are filtered out, so bridge round-trips that already produce genuine pi tool cards never double-render. Stream-json keeps its stronger native re-exec cards.
|
|
11
|
+
- **Live ACP usage estimates.** The ACP engine showed zeros until a turn settled (Gate B). Estimates now stream live: the turn's prompt is tokenized once, each text/thought delta updates the running counts, and an `ACP ≈ X in / Y out` status line tracks the turn (suppressed when `acp.usageEstimate` is `off`). Estimates are superseded only by a real `PromptResponse.usage` the day the server sends one; diagnostic usage-shaped frames (context windows, occupancy) never disable them.
|
|
12
|
+
- **`/agy tools` session-only catalog control.** `hide <name>` / `show <name>` / `reset` filter the bridge catalog for the current session only (multi-word names OK; names validated against pi's registered tools; saved config never touched). Bare `/agy tools` lists exposed and hidden tools.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- **The bridge catalog is computed live and guarded at dispatch.** The exposed set used to be computed once at registration; `/mcp` or `pi.setActiveTools()` changes mid-session left stale tools callable. The catalog is now derived from pi's live active set on every `tools/list` AND re-checked immediately before every `tools/call` - a call for anything outside the fresh set is rejected (`a cached MCP catalog is not authorization`). The bridge's own internal tools (AskAntigravity, the display-only `antigravity` wrapper, `activate_skill`, `bridge_poll_result`, `agy_web_search`/`agy_read_url`) are excluded from the catalog, closing a gap where the wrapper tool was exposed. `/agy bridge` mode changes apply to a running server immediately; a stopped one picks them up on the next start.
|
|
17
|
+
- **Global MCP registration is stream-json-only; hygiene is universal.** Registering and unregistering the bridge's per-pid entry in `~/.gemini/config/mcp_config.json` now happens only for the stream-json engine (ACP supplies the bridge per-session and never touches the global config), while the stale-entry sweep and the stuck-suppression heal run for BOTH engines - an ACP-only session no longer leaves dead entries or a stuck suppression flag behind by a crashed stream-json session.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- **Concurrent native tool results no longer cross-pair on ACP.** When several of agy's tools ran at once, a completed event for one could attach its diff/output to a different pending call. Adoption now happens only when exactly one call is pending, finished and errored ids leave the tracking map, and a known-acceptable residual is documented at the site: a done event for a genuinely unrelated call can still pair with the single pending entry.
|
|
22
|
+
- **Diagnostic usage frames can no longer kill ACP estimates.** The old suppression latch tripped on ANY usage-shaped frame and lived for the whole connection, so one context-window notice in turn one silenced the live estimates for every later turn, with no exact data ever replacing them. Suppression now keys on a real per-turn `PromptResponse.usage` only; a two-turn regression test pins that estimates survive the diagnostic frames.
|
|
23
|
+
|
|
5
24
|
## [1.6.3] - 2026-09-22
|
|
6
25
|
|
|
7
26
|
### Fixed
|
|
8
27
|
|
|
9
|
-
- **The approval gate no longer leaks into standalone sessions, and its verdicts parse.** Thanks @explesy for the report (
|
|
28
|
+
- **The approval gate no longer leaks into standalone sessions, and its verdicts parse.** Thanks @explesy for the report (issue #5). Two defects in the generated hook script and its staging. Every terminal path printed the bare decision scalar (`"deny"` / `"allow"`) instead of a JSON object, so any session that loaded a staged gate failed hook parsing outright (`protojson: syntax error`): the direct-POST and polling branches now print the full response object guarded by a `typeof json.decision === "string"` check, fall back to a fail-closed deny object on malformed bodies, and read ticket and poll status through `json?.`. The gate group itself was staged into the shared workspace `.agents/hooks.json`, so any standalone IDE or CLI session in that workspace loaded pi's gate: tool calls were denied with "no active antigravity turn" while pi sat idle, and mid-turn they parked into pi's approval queue. A live probe of the CLI settled the discovery rule (hooks load from `--add-dir` directories in stream-json and interactive sessions; print mode reads only the global config), and the gate now stages `.agents/hooks.json` into the session-private per-pid bridge dir beside `mcp_config.json`, which only this session's agy receives as an extra `--add-dir`. Staging into a pid-private file dropped the merge/backup/symlink surface to a plain atomic replace; the driver passes the dir when the gate is staged even without an MCP config; a legacy sweep at session start strips groups that dead sessions left in workspace files (live and foreign groups are never touched). The hook also gained per-fetch aborts (`AbortSignal.timeout`, POST capped at 10s, polls bounded by the remaining park budget) so a hung bridge request can never outlive the park deadline into an ungated soft-pass. Seven stub-server hook-execution regression tests pin the object contract; the staging suite covers the private-dir semantics and the sweep. 364 tests, tsc clean.
|
|
10
29
|
|
|
11
30
|
## [1.6.2] - 2026-09-19
|
|
12
31
|
|
package/README.md
CHANGED
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
# pi-antigravity-bridge
|
|
2
2
|
|
|
3
|
-
A Gemini model provider **and** the `AskAntigravity` delegation tool for [pi](https://github.com/earendil-works/pi
|
|
3
|
+
A Gemini model provider **and** the `AskAntigravity` delegation tool for [pi](https://github.com/earendil-works/pi), built on Google's official Antigravity binaries: the `agy` CLI by default, or **Google's official ACP server** (opt-in). It registers `antigravity/gemini-*` models in pi's `/model` picker (streaming), and provides the `AskAntigravity` tool for one-shot delegation - the same combined shape as `pi-claude-bridge`.
|
|
4
4
|
|
|
5
|
-
<p>
|
|
6
|
-
<img width="1374" height="869" alt="image" src="https://github.com/user-attachments/assets/fdd6c1f0-5144-40ef-9371-77a94de50b76" align="center" />
|
|
7
|
-
</p>
|
|
8
|
-
<p>
|
|
9
|
-
<img width="1376" height="451" alt="SCR-20260908-mbox" src="https://github.com/user-attachments/assets/d105c8a7-1ace-42f7-8285-642150453761" align="center" />
|
|
10
|
-
</p>
|
|
11
|
-
<p>
|
|
12
|
-
<img width="1375" height="1342" alt="SCR-20260908-mbvc" src="https://github.com/user-attachments/assets/bb614c55-e0f8-4677-bb0e-6541071e91ef" align="center" />
|
|
13
|
-
</p>
|
|
14
5
|
|
|
15
|
-
|
|
16
|
-
If you also have [`@estebanforge/pi-ask-antigravity`](https://github.com/EstebanForge/pi-ask-antigravity) installed, this bridge takes over: pi-ask-antigravity detects the bridge and registers nothing, so the `AskAntigravity` tool is never duplicated.
|
|
6
|
+
If you also have [`@estebanforge/pi-ask-antigravity`](https://github.com/EstebanForge/pi-extensions/tree/main/packages/pi-ask-antigravity) installed, this bridge takes over: pi-ask-antigravity detects the bridge and registers nothing, so the `AskAntigravity` tool is never duplicated.
|
|
17
7
|
|
|
18
8
|
## What it does
|
|
19
9
|
|
|
@@ -26,7 +16,7 @@ Multi-turn works. The provider binds a pi session to an agy conversation id (per
|
|
|
26
16
|
Turns run through one of two engines behind the same provider surface (`config.engine`, default `stream-json`):
|
|
27
17
|
|
|
28
18
|
- **stream-json** (default): the persistent `agy` CLI process. The tested default; live token usage; conversation resume via `--conversation`.
|
|
29
|
-
- **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
|
|
19
|
+
- **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 runs on live client-side estimates via `acp.usageEstimate`, default `estimate`, streamed per delta and superseded automatically the day the server starts sending real per-turn usage) and no cancel (abort tears the server down and reloads it next turn).
|
|
30
20
|
|
|
31
21
|
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.
|
|
32
22
|
|
|
@@ -48,7 +38,7 @@ Residual limits (with or without the bridge):
|
|
|
48
38
|
|
|
49
39
|
While agy is the active model it normally cannot see pi's universe of extensions: agentmemory, codegraph, web search, slack/asana, the `Ask*` delegations, and any other installed pi tool. This extension optionally bridges that gap.
|
|
50
40
|
|
|
51
|
-
The bridge starts a localhost MCP server inside pi's process. `tools/list`
|
|
41
|
+
The bridge starts a localhost MCP server inside pi's process. The exposed catalog is computed live from pi's active tools on every `tools/list` and re-checked immediately before every `tools/call`: the `bridgeTools` mode, the session's `/agy tools` hidden set, and the bridge's own internal tools (`AskAntigravity`, the display-only `antigravity` wrapper, `activate_skill`, `bridge_poll_result`, the `agy_web_search`/`agy_read_url` wrappers) are filtered each time, and a call for anything outside the fresh set is rejected - a cached MCP catalog is not authorization. A call that passes routes into pi's own tool loop via the round-trip described below. Discovery has two layers. The provider's agy gets a per-invocation config: the bridge writes `.agents/mcp_config.json` into a bridge-controlled dir (`~/.pi/agent/antigravity-bridge/agy-mcp-<pid>/`) and the driver passes that dir as an extra `--add-dir` when it spawns agy. A per-pid entry (`pi-bridge-<pid>`) is also registered in the user's global agy config (`~/.gemini/config/mcp_config.json`; foreign servers preserved, stale entries swept at start), so agy builds that read only the global config still find the bridge.
|
|
52
42
|
|
|
53
43
|
**No patch required.** Bridge calls park in the provider's round-trip store; the provider ends the pi assistant message with a `toolUse` stop reason for the real pi tool, pi executes it in its own loop (native cards, permissions, hooks), and the toolResult completes the parked MCP response on the next stream call. This is the same mechanism tianzuo/pi-antigravity uses; upstream pi APIs only.
|
|
54
44
|
|
|
@@ -56,7 +46,7 @@ The bridge starts a localhost MCP server inside pi's process. `tools/list` retur
|
|
|
56
46
|
|
|
57
47
|
**Recursion safety.** Only the provider's agy receives the extra `--add-dir`. `AskAntigravity` is also filtered from the exposed tool list. Its delegated `agy -p` spawns with just the workspace, and for the whole delegated run the bridge hides its global per-pid entries: disabled before the spawn, released only on process close or error - agy watches the config and hot-reloads MCP servers on file changes, so a mid-run re-enable would poke the live delegation back into pi. A cross-process marker (`suppression.json` in the bridge's extensions-data dir) coordinates concurrent delegations: entries come back only when no live delegation remains, a session starting mid-delegation registers its own entry disabled, and the session-start heal re-enables only once the last delegator is gone (dead entries pruned by pid liveness with a 24h age bound). The inner agy therefore sees no bridge and cannot re-enter pi. Foreign servers in the global config are never touched.
|
|
58
48
|
|
|
59
|
-
**Cost / fan-out.** Every registered pi tool except builtins
|
|
49
|
+
**Cost / fan-out.** Every registered pi tool except builtins and the bridge's own internal tools is exposed (respecting the `bridgeTools` mode and any `/agy tools` hides), including other delegation tools like `AskClaude`/`AskCodex`. agy can therefore chain into other models via the bridge, which is a new cost/time fan-out vector that did not exist before this feature.
|
|
60
50
|
|
|
61
51
|
**Security.** The MCP server binds to `127.0.0.1` only and requires a per-session shared-secret header (`x-bridge-token`) that agy sends from the bridge config; browsers cannot set custom headers on a simple cross-origin POST, so this blocks web CSRF against the loopback server. Request bodies are size-capped. This is intended for single-user developer machines: any local process running as the same user can read the token from the per-pid config and call the exposed tools, so do not run it on a shared host where you do not trust other same-user processes.
|
|
62
52
|
|
|
@@ -75,6 +65,12 @@ your pi Agent Skills catalog; calling it returns the SKILL.md body. The bridge
|
|
|
75
65
|
answers it directly, no pi round-trip. `/agy doctor` prints driver counters,
|
|
76
66
|
bridge port, and the last lifecycle events without spending tokens.
|
|
77
67
|
|
|
68
|
+
On the ACP engine there is no re-exec: agy's own native tool steps render as
|
|
69
|
+
display-only cards in pi instead - a status icon plus the file path with
|
|
70
|
+
colored diff lines, the command line, or the captured output - streamed as the
|
|
71
|
+
steps start and complete. The stream-json engine keeps its native re-exec
|
|
72
|
+
cards for read-only steps.
|
|
73
|
+
|
|
78
74
|
## Approval gate (agy native tools)
|
|
79
75
|
|
|
80
76
|
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.
|
|
@@ -120,13 +116,14 @@ If `agy models` fails at load (binary missing, auth not done, network stall), a
|
|
|
120
116
|
| Key | Values | Default |
|
|
121
117
|
| --- | --- | --- |
|
|
122
118
|
| `askTool` | `on` (register the AskAntigravity delegation tool), `off` (no delegation tool; provider and models only) | `on` |
|
|
119
|
+
| `webTools` | `off` (no web tools), `on` (register `agy_web_search` + `agy_read_url` as Pi tools, usable by ANY provider's model). Each call spawns a one-shot search-only `agy` agent (plan mode, bridge MCP inheritance off), gates the answer on an observed native `search_web`/`read_url_content` step, and spends Antigravity quota. Off by default: Antigravity sessions already have native web tools on both engines; these serve other providers | `off` |
|
|
123
120
|
| `bridgeTools` | `none` (bridge off), `all` (every non-builtin tool, incl. other `Ask*` delegations), `mcp` (pi-mcp-adapter tools + skills bridge only) | `all` |
|
|
124
121
|
| `digest` | `off` (stable prompts; agy's prompt cache hits) or `on` (inject a delta of pi-side context - compaction summaries, other-provider turns - into each agy prompt; the delta changes every turn, so agy re-bills the full context). Enable for mixed-provider sessions where agy must see pi-side context | `off` |
|
|
125
122
|
| `systemPrompt` | `on` (prepend pi's system prompt - operating instructions plus the global agent-dir `AGENTS.md` and ancestor `AGENTS.md`/`CLAUDE.md` - to the first prompt of each new agy conversation, plus a tool-priority note: Pi Bridge tools win over agy's native interactive ones, which never reach the user) or `off` (agy-native behavior) | `on` |
|
|
126
123
|
| `turnTimeoutMin` | Overall cap on ONE agy turn, minutes, both engines. When it fires, the bridge kills the agy process mid-task ("ACP turn exceeded the 10m deadline" / "agy exceeded the 10m turn timeout") - it is a bridge cap, not a Google server limit. Default is TTY-aware: `0` (no cap) on an interactive pi - you are the backstop with Esc; `20` headless, where nobody can abort and one runaway turn blocks the serialized turn queue. Opt into a gate with 1-1440 (free type via `config.json` or `/agy timeout <1-1440|off>`, or the `/agy` picker's preset list); `0` disables explicitly; anything else falls back to the TTY-aware default. The 5m inactivity stall guard always still bounds a hung server | `0` TTY / `20` headless |
|
|
127
124
|
| `inactivityTimeoutMin` | Silence cap, minutes, both engines: no stream-json stdout / no ACP session/update for this long fails the turn as a stall. `0` disables the guard | `5` |
|
|
128
125
|
|
|
129
|
-
Env overrides: `AGY_BRIDGE_TOOLS`, `AGY_ASK_TOOL`, `AGY_DIGEST`, `AGY_SYSTEM_PROMPT`, `AGY_TURN_TIMEOUT_MIN`, `AGY_INACTIVITY_TIMEOUT_MIN`. Env wins over the file, so while `AGY_DIGEST` or `AGY_SYSTEM_PROMPT` is set, the matching `/agy digest` or `/agy system-prompt` toggle persists a value that never takes effect.
|
|
126
|
+
Env overrides: `AGY_BRIDGE_TOOLS`, `AGY_ASK_TOOL`, `AGY_WEB_TOOLS`, `AGY_DIGEST`, `AGY_SYSTEM_PROMPT`, `AGY_TURN_TIMEOUT_MIN`, `AGY_INACTIVITY_TIMEOUT_MIN`. Env wins over the file, so while `AGY_DIGEST` or `AGY_SYSTEM_PROMPT` is set, the matching `/agy digest` or `/agy system-prompt` toggle persists a value that never takes effect.
|
|
130
127
|
|
|
131
128
|
The `activate_skill` catalog mirrors pi's directory-based skill discovery: the two global dirs plus project dirs, the latter only when pi has trusted the project (same gate pi itself applies). Pi's other skill sources - the `skills` settings array, `package.json` entries, and `--skill` CLI paths - are not mirrored and won't appear in the catalog.
|
|
132
129
|
|
|
@@ -147,7 +144,9 @@ The `activate_skill` catalog mirrors pi's directory-based skill discovery: the t
|
|
|
147
144
|
/agy thinking low|medium|high fallback thinking tier for the AskAntigravity tool; callers may override per call
|
|
148
145
|
/agy digest on|off inject pi-side context into agy prompts (default off; see table above)
|
|
149
146
|
/agy system-prompt on|off send pi's system prompt + AGENTS.md + the Pi Bridge tool-priority note to new agy conversations (default on)
|
|
150
|
-
/agy bridge all|mcp|none which pi tools the MCP bridge exposes to agy (default all; none = bridge off)
|
|
147
|
+
/agy bridge all|mcp|none which pi tools the MCP bridge exposes to agy (default all; none = bridge off; a running server picks up the new catalog immediately, a stopped one on the next start)
|
|
148
|
+
/agy tools [hide|show <name>|reset] session-only bridge-catalog hides (multi-word names OK; reset restores the full set; bare lists exposed + hidden; never touches saved config)
|
|
149
|
+
/agy web on|off register the agy_web_search + agy_read_url pi tools for ANY provider's model (default off; applies on the next pi start or /reload; every call spawns agy and spends Antigravity quota)
|
|
151
150
|
/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)
|
|
152
151
|
/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)
|
|
153
152
|
/agy auth-manual manual ACP credential setup (fallback; auto-setup normally covers this; default login = your Antigravity subscription, same account as the agy CLI)
|
|
@@ -180,9 +179,12 @@ For isolation when running any agent that executes commands without a confirmati
|
|
|
180
179
|
| `AGY_CONVERSATIONS_DIR` | Override the conversations DB directory. |
|
|
181
180
|
| `AGY_MODE` | Override execution mode: `plan` (review-only) or `accept-edits` (default). Wins over the config file. |
|
|
182
181
|
| `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. |
|
|
182
|
+
| `AGY_USAGE_ESTIMATE` | ACP token display: `estimate` (default; live client-side per-delta estimates), `direct` (pass through the server's own per-delta counts when it sends them), `off` (no usage display). Anything else falls back to `estimate`. Wins over the config file. |
|
|
183
183
|
| `AGY_DEFAULT_MODEL` | Default model alias for the `AskAntigravity` tool (`flash`/`pro`/`gemini`, or a tier/version qualifier). Wins over the config file. |
|
|
184
184
|
| `AGY_DEFAULT_THINKING` | Default thinking tier for the `AskAntigravity` tool: `low`/`medium`/`high`. Anything else falls back to `medium`. Wins over the config file. |
|
|
185
185
|
| `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. |
|
|
186
|
+
| `AGY_APPROVALS` | Approval gate mode: `auto` (default; gate on only when a pi permission extension is detected), `shadow` (force the shadow-tool gate on), `dedicated`, `off`. Unknown values fall back to `auto`, so a typo can never force the gate on. Wins over `config.approvals.gateMode`. |
|
|
187
|
+
| `AGY_APPROVALS_MODE` | Fallback decision when no permission extension answers a gated call: `ask` (interactive prompt), `allow`, `deny` (headless default). Wins over `config.approvals.mode`. |
|
|
186
188
|
|
|
187
189
|
## Debug logs
|
|
188
190
|
|
package/extensions/index.ts
CHANGED
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
createStreamSimple,
|
|
51
51
|
formatEscalatedAck,
|
|
52
52
|
formatPollAnswer,
|
|
53
|
+
type NativeDisplayEvent,
|
|
53
54
|
} from "../src/provider.js";
|
|
54
55
|
import {
|
|
55
56
|
createShadowTool,
|
|
@@ -69,6 +70,7 @@ import { CONFIG_PATH, loadConfig, logsDir, MAX_TURN_CAP_MIN, parseCapMinutes, sa
|
|
|
69
70
|
import { agyMissingMessage, isAgyInstalled, savedEngineMessage, showEnginePicker, shouldOfferEnginePicker } from "../src/engine-picker.js";
|
|
70
71
|
import { createDailyLogger, type DailyLogger } from "../src/daily-log.js";
|
|
71
72
|
import { registerAskAntigravityTool, toolModelsFromRaw } from "../src/ask-tool.js";
|
|
73
|
+
import { registerWebTools } from "../src/web-tools.js";
|
|
72
74
|
import { bridgeMcpConfigDir, startMcpServer, TOKEN_HEADER, type McpServerHandle } from "../src/mcp-server.js";
|
|
73
75
|
import {
|
|
74
76
|
registerBridgeServer,
|
|
@@ -86,6 +88,7 @@ import {
|
|
|
86
88
|
type SkillLite,
|
|
87
89
|
} from "../src/skills.js";
|
|
88
90
|
import { mapAgyToolToNative } from "../src/native-tools.js";
|
|
91
|
+
import { bridgedPiTools } from "../src/bridge-catalog.js";
|
|
89
92
|
import { Type } from "typebox";
|
|
90
93
|
import { patchStatus, restorePatch } from "../src/patch-cleanup.js";
|
|
91
94
|
import { withDialogLock } from "../src/dialog-lock.js";
|
|
@@ -177,7 +180,7 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
177
180
|
};
|
|
178
181
|
fileLog.log(
|
|
179
182
|
"extension-load",
|
|
180
|
-
{ engine, models: models.length, fallback: usingFallback, bridge: loadConfig().bridgeTools, askTool: loadConfig().askTool },
|
|
183
|
+
{ engine, models: models.length, fallback: usingFallback, bridge: loadConfig().bridgeTools, askTool: loadConfig().askTool, webTools: loadConfig().webTools },
|
|
181
184
|
"info",
|
|
182
185
|
);
|
|
183
186
|
|
|
@@ -185,6 +188,9 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
185
188
|
// MCP bridge handle, declared early: the ACP engine reads the bridge port
|
|
186
189
|
// at session/new / session/load time.
|
|
187
190
|
let mcpHandle: McpServerHandle | null = null;
|
|
191
|
+
// Session-only visibility overrides; never change either application's MCP config.
|
|
192
|
+
const hiddenBridgeTools = new Set<string>();
|
|
193
|
+
const getBridgeTools = () => bridgedPiTools(pi.getAllTools(), pi.getActiveTools(), loadConfig().bridgeTools, hiddenBridgeTools);
|
|
188
194
|
// Approval-gate hook script (per-pid, token embedded). Written at session
|
|
189
195
|
// start when the gate is active; removed at session_shutdown.
|
|
190
196
|
let gateScriptPath: string | null = null;
|
|
@@ -328,12 +334,53 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
328
334
|
),
|
|
329
335
|
);
|
|
330
336
|
const replay = new WrapperReplay();
|
|
337
|
+
// ACP tool activity is display-only: durable Pi entries with native-looking
|
|
338
|
+
// titles and optional diffs. Unlike a synthetic Pi toolCall this does not
|
|
339
|
+
// re-execute edits, park the turn, or add tool results to model context.
|
|
340
|
+
if (engine === "acp") {
|
|
341
|
+
pi.registerEntryRenderer<NativeDisplayEvent>("agy-native-event", (entry, { expanded }, theme) => {
|
|
342
|
+
const event = entry.data;
|
|
343
|
+
const title = (event?.path ? path.basename(event.path) : event?.command?.split(/\r?\n/, 1)[0] ?? event?.name ?? "tool").slice(0, 160);
|
|
344
|
+
const status = event?.status === "failed" ? theme.fg("error", "✗") : theme.fg("success", "✓");
|
|
345
|
+
const lines = [`${status} ${theme.fg("toolTitle", event?.name ?? "Antigravity")} ${theme.fg("muted", title)}`];
|
|
346
|
+
if (expanded) {
|
|
347
|
+
if (event?.path) lines.push(theme.fg("dim", event.path));
|
|
348
|
+
if (event?.diff) {
|
|
349
|
+
for (const line of event.diff.split("\n")) {
|
|
350
|
+
const color = line.startsWith("+") ? "toolDiffAdded" : line.startsWith("-") ? "toolDiffRemoved" : "toolDiffContext";
|
|
351
|
+
lines.push(theme.fg(color, line));
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (event?.output && (!event.diff || event.status === "failed")) lines.push(theme.fg("toolOutput", event.output));
|
|
355
|
+
}
|
|
356
|
+
return new Text(lines.join("\n"), 0, 0);
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
let pendingNativeTools = 0;
|
|
360
|
+
const onNativeEvent = (event: NativeDisplayEvent): void => {
|
|
361
|
+
if (engine !== "acp" || event.mcpServer === "pi-bridge") return;
|
|
362
|
+
// MCP calls through our own pi-bridge already have genuine Pi tool cards;
|
|
363
|
+
// only native Antigravity / other MCP events need display-only entries.
|
|
364
|
+
if (event.status === "started") {
|
|
365
|
+
pendingNativeTools++;
|
|
366
|
+
activeUi?.setStatus("agy-native", `agy ${event.name}… (${pendingNativeTools})`);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
pendingNativeTools = Math.max(0, pendingNativeTools - 1);
|
|
370
|
+
if (pendingNativeTools === 0) activeUi?.setStatus("agy-native", undefined);
|
|
371
|
+
// Bound persisted session size; the ACP result and diff may be megabytes.
|
|
372
|
+
pi.appendEntry<NativeDisplayEvent>("agy-native-event", {
|
|
373
|
+
...event,
|
|
374
|
+
output: event.output?.slice(0, 4000),
|
|
375
|
+
diff: event.diff?.slice(0, 12000),
|
|
376
|
+
command: event.command?.slice(0, 500),
|
|
377
|
+
});
|
|
378
|
+
};
|
|
331
379
|
// Native re-exec only emits for builtins actually active in the session;
|
|
332
380
|
// anything else (or an unknown name) falls back to the wrapper card.
|
|
333
381
|
const nativeActive = (name: string): boolean => {
|
|
334
382
|
try {
|
|
335
|
-
|
|
336
|
-
return getAll().some((t) => t.name === name);
|
|
383
|
+
return pi.getActiveTools().includes(name);
|
|
337
384
|
} catch {
|
|
338
385
|
return false;
|
|
339
386
|
}
|
|
@@ -354,6 +401,35 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
354
401
|
};
|
|
355
402
|
streamDriver.onTurnEnd = onTurnEnd;
|
|
356
403
|
acpDriver.onTurnEnd = onTurnEnd;
|
|
404
|
+
// Pi's normal footer receives the incremental usage above. Mark the
|
|
405
|
+
// numbers as estimates in the status area too; ACP currently drops the
|
|
406
|
+
// SDK's exact usage metadata before it reaches the client.
|
|
407
|
+
if (engine === "acp") {
|
|
408
|
+
let lastUsageStatus = "";
|
|
409
|
+
pi.on("message_update", (event, ctx) => {
|
|
410
|
+
if (!ctx.hasUI || event.message.role !== "assistant" || event.message.provider !== "antigravity" || loadConfig().acp.usageEstimate === "off") return;
|
|
411
|
+
const { input, output } = event.message.usage;
|
|
412
|
+
const status = `ACP ≈ ${input} in / ${output} out`;
|
|
413
|
+
if (status !== lastUsageStatus) {
|
|
414
|
+
lastUsageStatus = status;
|
|
415
|
+
ctx.ui.setStatus("agy-usage", status);
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
const clearUsageStatus = (_event: unknown, ctx: { hasUI: boolean; ui: ExtensionUIContext }) => {
|
|
419
|
+
lastUsageStatus = "";
|
|
420
|
+
if (ctx.hasUI) ctx.ui.setStatus("agy-usage", undefined);
|
|
421
|
+
};
|
|
422
|
+
pi.on("agent_end", (event, ctx) => {
|
|
423
|
+
clearUsageStatus(event, ctx);
|
|
424
|
+
pendingNativeTools = 0;
|
|
425
|
+
if (ctx.hasUI) ctx.ui.setStatus("agy-native", undefined);
|
|
426
|
+
});
|
|
427
|
+
pi.on("session_shutdown", (event, ctx) => {
|
|
428
|
+
clearUsageStatus(event, ctx);
|
|
429
|
+
pendingNativeTools = 0;
|
|
430
|
+
if (ctx.hasUI) ctx.ui.setStatus("agy-native", undefined);
|
|
431
|
+
});
|
|
432
|
+
}
|
|
357
433
|
const streamSimple = createStreamSimple({
|
|
358
434
|
entries,
|
|
359
435
|
store,
|
|
@@ -362,6 +438,7 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
362
438
|
roundTrips,
|
|
363
439
|
replay,
|
|
364
440
|
nativeActive,
|
|
441
|
+
onNativeEvent,
|
|
365
442
|
engine,
|
|
366
443
|
log: fileLog.log.bind(fileLog),
|
|
367
444
|
});
|
|
@@ -393,6 +470,8 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
393
470
|
acpDriver,
|
|
394
471
|
engine,
|
|
395
472
|
getMcpPort: () => mcpHandle?.port ?? null,
|
|
473
|
+
bridgeTools: getBridgeTools,
|
|
474
|
+
hiddenBridgeTools,
|
|
396
475
|
acpLog,
|
|
397
476
|
fileLog,
|
|
398
477
|
authCapture: authCapture ?? null,
|
|
@@ -409,6 +488,9 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
409
488
|
// pi-ask-antigravity keeps deferring even then: off means NO delegation
|
|
410
489
|
// tool from either package, not a fallback to pi-ask-antigravity.
|
|
411
490
|
if (loadConfig().askTool) await registerAskAntigravityTool(pi, toolModels, fileLog.log.bind(fileLog));
|
|
491
|
+
// Web tools are opt-in (config.webTools, default off): Antigravity sessions
|
|
492
|
+
// already have native web tools; these serve NON-Antigravity providers.
|
|
493
|
+
if (loadConfig().webTools) registerWebTools(pi, { log: fileLog.log.bind(fileLog) });
|
|
412
494
|
|
|
413
495
|
// Display-only wrapper tool: the provider emits mutating agy steps as
|
|
414
496
|
// toolCalls against it (never re-executed - execute() replays the output
|
|
@@ -633,22 +715,13 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
633
715
|
const bridgeMode: BridgeTools = loadConfig().bridgeTools;
|
|
634
716
|
if (bridgeMode === "none") return; // user opted out
|
|
635
717
|
if (mcpHandle) return; // already running (reload re-fires session_start)
|
|
636
|
-
const SKIP = new Set(["AskAntigravity"]);
|
|
637
718
|
// pi loads project skill locations only after the project is trusted;
|
|
638
719
|
// mirror that gate. Global skill dirs are always scanned.
|
|
639
720
|
const skills: SkillLite[] = scanSkills(ctx.isProjectTrusted() ? process.cwd() : undefined);
|
|
640
|
-
const getAll = (pi as unknown as {
|
|
641
|
-
getAllTools: () => Array<{ name: string; description?: string; parameters?: object; sourceInfo?: { source?: string } }>;
|
|
642
|
-
}).getAllTools.bind(pi);
|
|
643
721
|
const listTools = () => {
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
? all.filter((t) => /pi-mcp-adapter/.test(t.sourceInfo?.source ?? ""))
|
|
648
|
-
: all.filter((t) => t.sourceInfo?.source !== "builtin");
|
|
649
|
-
const tools = filtered
|
|
650
|
-
.filter((t) => !SKIP.has(t.name))
|
|
651
|
-
.map((t) => {
|
|
722
|
+
// /mcp and pi.setActiveTools() update the same live catalog. ACP gets
|
|
723
|
+
// this endpoint through session/new or session/load; no global config write.
|
|
724
|
+
const tools = getBridgeTools().map((t) => {
|
|
652
725
|
let inputSchema: object = { type: "object", properties: {}, additionalProperties: true };
|
|
653
726
|
try {
|
|
654
727
|
if (t.parameters) inputSchema = JSON.parse(JSON.stringify(t.parameters)) as object;
|
|
@@ -657,7 +730,7 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
657
730
|
}
|
|
658
731
|
return { name: t.name, description: t.description ?? t.name, inputSchema };
|
|
659
732
|
});
|
|
660
|
-
if (skills.length > 0) {
|
|
733
|
+
if (skills.length > 0 && loadConfig().bridgeTools !== "none") {
|
|
661
734
|
tools.push({
|
|
662
735
|
name: ACTIVATE_SKILL_TOOL_NAME,
|
|
663
736
|
description: `Activate a pi Agent Skill by name. Catalog:\n${catalogSummary(skills)}`,
|
|
@@ -695,6 +768,11 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
695
768
|
return Promise.resolve(formatPollAnswer(wanted, roundTrips.poll(wanted)));
|
|
696
769
|
}
|
|
697
770
|
if (name !== ACTIVATE_SKILL_TOOL_NAME) {
|
|
771
|
+
// A cached MCP catalog is not authorization. An inactive/hidden tool
|
|
772
|
+
// must not get parked into Pi, even if agy retained its old schema.
|
|
773
|
+
if (!getBridgeTools().some((tool) => tool.name === name)) {
|
|
774
|
+
return Promise.resolve({ content: [{ type: "text", text: `Pi tool ${name} is not active or not exposed to Antigravity` }], isError: true });
|
|
775
|
+
}
|
|
698
776
|
return roundTrips.onToolCall(callId, name, args, signal).then((r) => {
|
|
699
777
|
// Early-ack: answer the HTTP request before agy's ~180s client
|
|
700
778
|
// deadline with a poll handle; pi keeps executing meanwhile.
|
|
@@ -704,7 +782,7 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
704
782
|
});
|
|
705
783
|
}
|
|
706
784
|
const wanted = typeof args.name === "string" ? args.name : "";
|
|
707
|
-
const skill = findSkillByName(skills, wanted);
|
|
785
|
+
const skill = loadConfig().bridgeTools !== "none" ? findSkillByName(skills, wanted) : undefined;
|
|
708
786
|
const body = skill ? readSkillBody(skill) : `unknown skill: ${wanted || "(none given)"}`;
|
|
709
787
|
return Promise.resolve({
|
|
710
788
|
content: [
|
|
@@ -730,14 +808,21 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
730
808
|
// suppressed are healed here - but only when no live delegation is in
|
|
731
809
|
// flight anywhere (marker-aware heal). A blind re-enable used to
|
|
732
810
|
// un-hide the bridge during another session's active delegation.
|
|
811
|
+
// Global-config hygiene runs on BOTH engines: a crashed stream-json
|
|
812
|
+
// session must not leave dead-pid entries or stuck suppression flags
|
|
813
|
+
// for the next one.
|
|
733
814
|
sweepStaleBridgeServers();
|
|
734
815
|
healBridgeSuppression();
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
816
|
+
// ACP supplies the bridge per-session (session/new | session/load);
|
|
817
|
+
// only the stream-json CLI reads the global MCP config.
|
|
818
|
+
if (engine === "stream-json") {
|
|
819
|
+
registerBridgeServer({
|
|
820
|
+
pid: process.pid,
|
|
821
|
+
port: r.handle.port,
|
|
822
|
+
token: r.handle.token,
|
|
823
|
+
tokenHeader: TOKEN_HEADER,
|
|
824
|
+
});
|
|
825
|
+
}
|
|
741
826
|
// --- Approval gate (docs/TODO.md 2.5) ------------------------------
|
|
742
827
|
// agy native tool calls pass through a pi-side approval: a PreToolUse
|
|
743
828
|
// hook parks in the bridge, the provider emits a shadow toolUse, and
|
|
@@ -862,7 +947,8 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
862
947
|
// ("quit") - the connection kill is identical and nothing runs after.
|
|
863
948
|
await streamDriver.close("recycle", "session shutdown");
|
|
864
949
|
await acpDriver.close("recycle", "session shutdown");
|
|
865
|
-
unregisterBridgeServer(process.pid);
|
|
950
|
+
if (engine === "stream-json") unregisterBridgeServer(process.pid);
|
|
951
|
+
hiddenBridgeTools.clear();
|
|
866
952
|
// Approval gate: unstage hooks and remove the per-pid script. Pending
|
|
867
953
|
// approvals already failed closed via handle close (bridge shutdown deny).
|
|
868
954
|
const unstaged = removeGateHooks();
|
|
@@ -897,6 +983,8 @@ interface AgyCommandCtx {
|
|
|
897
983
|
/** Engine latched at extension load (see the provider wiring note). */
|
|
898
984
|
engine: Engine;
|
|
899
985
|
getMcpPort: () => number | null;
|
|
986
|
+
bridgeTools: () => Array<{ name: string }>;
|
|
987
|
+
hiddenBridgeTools: Set<string>;
|
|
900
988
|
/** Shared ACP log sink (login URL surfacing + failure events). */
|
|
901
989
|
acpLog: (msg: string, data?: unknown) => void;
|
|
902
990
|
/** Daily file logger (src/daily-log.ts); command + doctor surfacing. */
|
|
@@ -916,6 +1004,7 @@ interface PendingConfig {
|
|
|
916
1004
|
defaultThinking?: ThinkingTier;
|
|
917
1005
|
turnTimeoutMin?: number;
|
|
918
1006
|
askTool?: boolean;
|
|
1007
|
+
webTools?: boolean;
|
|
919
1008
|
bridgeTools?: BridgeTools;
|
|
920
1009
|
digest?: boolean;
|
|
921
1010
|
systemPrompt?: boolean;
|
|
@@ -935,6 +1024,7 @@ function statusText(ctx: AgyCommandCtx): string {
|
|
|
935
1024
|
row("mode:", config.mode),
|
|
936
1025
|
row("permissions:", perm),
|
|
937
1026
|
row("AskAntigravity tool:", config.askTool ? "on" : "off"),
|
|
1027
|
+
row("Web tools:", config.webTools ? "on" : "off"),
|
|
938
1028
|
row("AskAntigravity model:", config.defaultModel),
|
|
939
1029
|
row("AskAntigravity thinking:", config.defaultThinking),
|
|
940
1030
|
row("sessions:", `${ctx.store.size} bound`),
|
|
@@ -943,7 +1033,7 @@ function statusText(ctx: AgyCommandCtx): string {
|
|
|
943
1033
|
row("digest:", config.digest ? "on" : "off"),
|
|
944
1034
|
row("system prompt:", config.systemPrompt ? "on" : "off"),
|
|
945
1035
|
"",
|
|
946
|
-
"Subcommands: /agy auth, /agy auth-manual, /agy engine stream-json|acp, /agy mode plan|accept-edits, /agy permissions on|off, /agy ask on|off, /agy model <alias>, /agy thinking low|medium|high, /agy bridge all|mcp|none, /agy digest on|off, /agy system-prompt on|off, /agy acp-bin <path|auto>, /agy patch-cleanup, /agy clear",
|
|
1036
|
+
"Subcommands: /agy auth, /agy auth-manual, /agy engine stream-json|acp, /agy mode plan|accept-edits, /agy permissions on|off, /agy ask on|off, /agy model <alias>, /agy thinking low|medium|high, /agy bridge all|mcp|none, /agy tools [hide|show <name>|reset], /agy web on|off, /agy digest on|off, /agy system-prompt on|off, /agy acp-bin <path|auto>, /agy patch-cleanup, /agy clear",
|
|
947
1037
|
].join("\n");
|
|
948
1038
|
}
|
|
949
1039
|
|
|
@@ -951,7 +1041,7 @@ function statusText(ctx: AgyCommandCtx): string {
|
|
|
951
1041
|
function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
952
1042
|
pi.registerCommand("agy", {
|
|
953
1043
|
description:
|
|
954
|
-
"Antigravity provider: status, doctor, settings picker, clear sessions. Usage: /agy [status|doctor|auth|auth-manual|engine stream-json|acp|mode plan|accept-edits|permissions on|off|ask on|off|model <alias>|thinking low|medium|high|bridge all|mcp|none|digest on|off|system-prompt on|off|timeout <1-1440|off>|acp-bin <path|auto>|patch-cleanup|clear]",
|
|
1044
|
+
"Antigravity provider: status, doctor, settings picker, clear sessions. Usage: /agy [status|doctor|auth|auth-manual|engine stream-json|acp|mode plan|accept-edits|permissions on|off|ask on|off|model <alias>|thinking low|medium|high|bridge all|mcp|none|tools [hide|show <name>|reset]|web on|off|digest on|off|system-prompt on|off|timeout <1-1440|off>|acp-bin <path|auto>|patch-cleanup|clear]",
|
|
955
1045
|
handler: async (args, cmdCtx: ExtensionCommandContext) => {
|
|
956
1046
|
const ui = cmdCtx.ui;
|
|
957
1047
|
if (ui) activeUi = ui;
|
|
@@ -1245,13 +1335,31 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1245
1335
|
if (val === "all" || val === "mcp" || val === "none") {
|
|
1246
1336
|
const next = saveConfig({ bridgeTools: val });
|
|
1247
1337
|
ui?.notify(
|
|
1248
|
-
|
|
1249
|
-
?
|
|
1250
|
-
: `bridge
|
|
1338
|
+
ctx.getMcpPort() === null
|
|
1339
|
+
? `bridge ${next.bridgeTools}. The bridge server is not running; /reload to apply.`
|
|
1340
|
+
: `bridge ${next.bridgeTools}. The active catalog and call guard update now; ACP may retain cached schemas until the next session/load.`,
|
|
1251
1341
|
"info",
|
|
1252
1342
|
);
|
|
1253
1343
|
} else {
|
|
1254
|
-
ui?.notify(`bridge: ${loadConfig().bridgeTools}\nusage: /agy bridge all|mcp|none\n all:
|
|
1344
|
+
ui?.notify(`bridge: ${loadConfig().bridgeTools}\nusage: /agy bridge all|mcp|none\n all: active non-builtin pi tools. mcp: active pi-mcp-adapter tools + skills. none: bridge off.`, "info");
|
|
1345
|
+
}
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
if (sub === "tools") {
|
|
1349
|
+
const name = (args ?? "").trim().split(/\s+/).slice(2).join(" ");
|
|
1350
|
+
if (val === "reset") {
|
|
1351
|
+
ctx.hiddenBridgeTools.clear();
|
|
1352
|
+
ui?.notify("Antigravity tool visibility reset for this Pi session. Pi's active tools are unchanged.", "info");
|
|
1353
|
+
} else if ((val === "hide" || val === "show") && name) {
|
|
1354
|
+
if (!pi.getAllTools().some((tool) => tool.name === name)) {
|
|
1355
|
+
ui?.notify(`Unknown Pi tool: ${name}`, "error");
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
if (val === "hide") ctx.hiddenBridgeTools.add(name);
|
|
1359
|
+
else ctx.hiddenBridgeTools.delete(name);
|
|
1360
|
+
ui?.notify(`${name}: ${val === "hide" ? "hidden from Antigravity" : "visible if active in Pi"}. No MCP config changed.`, "info");
|
|
1361
|
+
} else {
|
|
1362
|
+
ui?.notify(`Exposed active Pi tools: ${ctx.bridgeTools().map((tool) => tool.name).join(", ") || "(none)"}\nHidden in this session: ${[...ctx.hiddenBridgeTools].join(", ") || "(none)"}\n/agy tools hide|show <exact Pi tool name> | reset\nUse Pi's /mcp or pi.setActiveTools() to change the underlying active tools.`, "info");
|
|
1255
1363
|
}
|
|
1256
1364
|
return;
|
|
1257
1365
|
}
|
|
@@ -1278,6 +1386,20 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1278
1386
|
}
|
|
1279
1387
|
return;
|
|
1280
1388
|
}
|
|
1389
|
+
if (sub === "web") {
|
|
1390
|
+
if (val === "on" || val === "off") {
|
|
1391
|
+
const next = saveConfig({ webTools: val === "on" });
|
|
1392
|
+
ui?.notify(
|
|
1393
|
+
next.webTools
|
|
1394
|
+
? "Web tools on: agy_web_search and agy_read_url register on the next pi start (or /reload). Each call spends Antigravity quota."
|
|
1395
|
+
: "Web tools off: agy_web_search and agy_read_url will not register on the next pi start (or /reload).",
|
|
1396
|
+
"info",
|
|
1397
|
+
);
|
|
1398
|
+
} else {
|
|
1399
|
+
ui?.notify(`Web tools: ${loadConfig().webTools ? "on" : "off"}\nusage: /agy web on|off\nExposes agy_web_search + agy_read_url as Pi tools for ANY provider (Antigravity sessions already have native web tools). Default off; each call spends Antigravity quota.`, "info");
|
|
1400
|
+
}
|
|
1401
|
+
return;
|
|
1402
|
+
}
|
|
1281
1403
|
if (sub === "digest") {
|
|
1282
1404
|
if (val === "on" || val === "off") {
|
|
1283
1405
|
const next = saveConfig({ digest: val === "on" });
|
|
@@ -1367,6 +1489,14 @@ async function openAgyPicker(ui: ExtensionUIContext, ctx: AgyCommandCtx): Promis
|
|
|
1367
1489
|
currentValue: config.askTool ? "on" : "off",
|
|
1368
1490
|
values: ["on", "off"],
|
|
1369
1491
|
},
|
|
1492
|
+
{
|
|
1493
|
+
id: "web",
|
|
1494
|
+
label: "Web tools",
|
|
1495
|
+
description:
|
|
1496
|
+
"Register agy_web_search + agy_read_url as Pi tools for ANY provider. Default off: Antigravity sessions already have native web tools, and each call spends Antigravity quota. Takes effect on the next pi start (or /reload).",
|
|
1497
|
+
currentValue: config.webTools ? "on" : "off",
|
|
1498
|
+
values: ["on", "off"],
|
|
1499
|
+
},
|
|
1370
1500
|
{
|
|
1371
1501
|
id: "model",
|
|
1372
1502
|
label: "AskAntigravity model",
|
|
@@ -1437,6 +1567,8 @@ async function openAgyPicker(ui: ExtensionUIContext, ctx: AgyCommandCtx): Promis
|
|
|
1437
1567
|
pending.defaultThinking = newValue as ThinkingTier;
|
|
1438
1568
|
} else if (id === "ask") {
|
|
1439
1569
|
pending.askTool = newValue === "on";
|
|
1570
|
+
} else if (id === "web") {
|
|
1571
|
+
pending.webTools = newValue === "on";
|
|
1440
1572
|
} else if (id === "bridge") {
|
|
1441
1573
|
pending.bridgeTools = newValue as BridgeTools;
|
|
1442
1574
|
} else if (id === "turn-cap") {
|
|
@@ -1483,6 +1615,7 @@ async function openAgyPicker(ui: ExtensionUIContext, ctx: AgyCommandCtx): Promis
|
|
|
1483
1615
|
? `permissions=${next.skipPermissions ? "auto-approved" : "prompt"}`
|
|
1484
1616
|
: null,
|
|
1485
1617
|
pending.askTool !== undefined ? `AskAntigravity tool=${next.askTool ? "on" : "off"}` : null,
|
|
1618
|
+
pending.webTools !== undefined ? `webTools=${next.webTools ? "on" : "off"}` : null,
|
|
1486
1619
|
pending.defaultModel !== undefined ? `AskAntigravity model=${next.defaultModel}` : null,
|
|
1487
1620
|
pending.defaultThinking !== undefined ? `AskAntigravity thinking=${next.defaultThinking}` : null,
|
|
1488
1621
|
pending.bridgeTools !== undefined ? `bridge=${next.bridgeTools}` : null,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@estebanforge/pi-antigravity-bridge",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Gemini provider for Pi on the Antigravity ACP server (official Google ACP) or the stream-json agy CLI. antigravity/* models in Pi's /model picker, no-patch MCP bridge: agy runs Pi's tools. ToS safe to use.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -26,24 +26,19 @@
|
|
|
26
26
|
},
|
|
27
27
|
"repository": {
|
|
28
28
|
"type": "git",
|
|
29
|
-
"url": "git+https://github.com/EstebanForge/pi-
|
|
29
|
+
"url": "git+https://github.com/EstebanForge/pi-extensions.git",
|
|
30
|
+
"directory": "packages/pi-antigravity-bridge"
|
|
30
31
|
},
|
|
31
32
|
"publishConfig": {
|
|
32
33
|
"access": "public"
|
|
33
34
|
},
|
|
34
35
|
"type": "module",
|
|
35
|
-
"scripts": {
|
|
36
|
-
"build": "tsc --noEmit",
|
|
37
|
-
"test": "vitest run",
|
|
38
|
-
"prepublishOnly": "npm run build && npm test",
|
|
39
|
-
"smoke:pi": "bash scripts/smoke-in-pi.sh"
|
|
40
|
-
},
|
|
41
36
|
"files": [
|
|
42
37
|
"extensions",
|
|
43
38
|
"src",
|
|
44
|
-
"docs",
|
|
45
39
|
"README.md",
|
|
46
|
-
"CHANGELOG.md"
|
|
40
|
+
"CHANGELOG.md",
|
|
41
|
+
"LICENSE"
|
|
47
42
|
],
|
|
48
43
|
"pi": {
|
|
49
44
|
"extensions": [
|
|
@@ -58,17 +53,6 @@
|
|
|
58
53
|
"optional": true
|
|
59
54
|
}
|
|
60
55
|
},
|
|
61
|
-
"devDependencies": {
|
|
62
|
-
"@earendil-works/pi-ai": "^0.87.0",
|
|
63
|
-
"@earendil-works/pi-coding-agent": "^0.87.0",
|
|
64
|
-
"@earendil-works/pi-server": "^0.87.0",
|
|
65
|
-
"@earendil-works/pi-tui": "^0.87.0",
|
|
66
|
-
"@types/node": "^22.0.0",
|
|
67
|
-
"tsx": "^4.19.0",
|
|
68
|
-
"typebox": "^1.1.38",
|
|
69
|
-
"typescript": "^5.8.0",
|
|
70
|
-
"vitest": "^4.1.7"
|
|
71
|
-
},
|
|
72
56
|
"dependencies": {
|
|
73
57
|
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
74
58
|
}
|