@estebanforge/pi-antigravity-bridge 1.3.3 → 1.4.1
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 +57 -0
- package/README.md +22 -3
- package/docs/ACP-ADOPTION-PLAN.md +875 -0
- package/docs/ACP-PROTOCOL-REFERENCE.md +460 -0
- package/docs/ANTIGRAVITY-INTEGRATIONS.md +50 -551
- package/docs/ARCHITECTURE.md +50 -3
- package/docs/DEVELOPMENT.md +33 -0
- package/docs/PI-BRIDGE-GAPS.md +41 -140
- package/extensions/index.ts +235 -39
- package/package.json +12 -5
- package/src/acp/connection.ts +409 -0
- package/src/acp/driver.ts +723 -0
- package/src/acp/events.ts +250 -0
- package/src/acp/jsonrpc.ts +185 -0
- package/src/acp/setup.ts +355 -0
- package/src/config.ts +33 -0
- package/src/diff-render.ts +15 -0
- package/src/driver-types.ts +144 -0
- package/src/driver.ts +69 -78
- package/src/mcp-server.ts +8 -1
- package/src/models.ts +9 -7
- package/src/provider.ts +148 -23
package/extensions/index.ts
CHANGED
|
@@ -37,9 +37,12 @@ import {
|
|
|
37
37
|
import { SessionStore } from "../src/sessions.js";
|
|
38
38
|
import { ToolRoundTrips, WrapperReplay, createStreamSimple } from "../src/provider.js";
|
|
39
39
|
import { AgyDriver } from "../src/driver.js";
|
|
40
|
-
import {
|
|
40
|
+
import { AcpDriver } from "../src/acp/driver.js";
|
|
41
|
+
import { ensureAcpReady, inspectAcpSetup } from "../src/acp/setup.js";
|
|
42
|
+
import type { TurnDriver } from "../src/driver-types.js";
|
|
43
|
+
import { CONFIG_PATH, loadConfig, saveConfig, type AgyMode, type BridgeTools, type Engine, type ThinkingTier } from "../src/config.js";
|
|
41
44
|
import { registerAskAntigravityTool, toolModelsFromRaw } from "../src/ask-tool.js";
|
|
42
|
-
import { startMcpServer, type McpServerHandle } from "../src/mcp-server.js";
|
|
45
|
+
import { startMcpServer, TOKEN_HEADER, type McpServerHandle } from "../src/mcp-server.js";
|
|
43
46
|
import {
|
|
44
47
|
ACTIVATE_SKILL_TOOL_NAME,
|
|
45
48
|
activateSkillSchema,
|
|
@@ -79,14 +82,72 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
79
82
|
const toolModels = toolModelsFromRaw(raw);
|
|
80
83
|
const usingFallback = discovered.length === 0;
|
|
81
84
|
const entries: AgyModelEntry[] = usingFallback ? FALLBACK_MODELS : discovered;
|
|
82
|
-
|
|
85
|
+
// Engine latched at load: /agy engine takes effect on the next pi start
|
|
86
|
+
// (documented). Everything below resolves from THIS value - per-call
|
|
87
|
+
// config reads would let a mid-session flip leave ToolRoundTrips,
|
|
88
|
+
// kickIdle, and reentry pointing at the other engine (round-7 finding).
|
|
89
|
+
const engine: Engine = loadConfig().engine;
|
|
90
|
+
// Engine switching requires a restart, so the catalog-time engine read is
|
|
91
|
+
// authoritative for input advertising: image attach rides only when turns
|
|
92
|
+
// will run on the ACP engine (the legacy CLI prompt is text-only).
|
|
93
|
+
const modelInput: Array<"text" | "image"> = engine === "acp" ? ["text", "image"] : ["text"];
|
|
94
|
+
const models = entries.map((e) => toPiModel(e, modelInput));
|
|
83
95
|
|
|
84
96
|
const store = new SessionStore();
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
97
|
+
// MCP bridge handle, declared early: the ACP engine reads the bridge port
|
|
98
|
+
// at session/new / session/load time.
|
|
99
|
+
let mcpHandle: McpServerHandle | null = null;
|
|
100
|
+
// ACP self-heal runs once per process (session_start re-fires on /reload;
|
|
101
|
+
// a ready setup is two file stats, so re-running is harmless anyway).
|
|
102
|
+
let acpSelfHealRan = false;
|
|
103
|
+
// Two turn engines behind one contract (plan §9): stream-json (tested
|
|
104
|
+
// default) and the official ACP server (opt-in via config.engine, off by
|
|
105
|
+
// default). Neither spawns anything until its first turn.
|
|
106
|
+
// ACP log routing: only genuine failures reach stderr. Routine lifecycle
|
|
107
|
+
// (driver-created, spawn, session-new, ...) stays in the driver's
|
|
108
|
+
// #lifecycle ring buffer, visible via /agy doctor. An unfiltered sink fired
|
|
109
|
+
// console.error at extension load ("driver-created"), before any UI exists,
|
|
110
|
+
// and leaked raw driver lines into the terminal on every startup.
|
|
111
|
+
const acpFailures = new Set([
|
|
112
|
+
"start-failed", "spawn-error", "parse-error", "write-failed",
|
|
113
|
+
"mode-apply-failed", "timeout", "stall", "auth-required",
|
|
114
|
+
"session-load-failed-creating-fresh", "connection-exited", "cancel-failed",
|
|
115
|
+
"unsupported-server-request",
|
|
116
|
+
]);
|
|
117
|
+
const legacyDriver = new AgyDriver();
|
|
118
|
+
const acpDriver = new AcpDriver({
|
|
119
|
+
// Resolved per connection: the setup flow can install the binary and
|
|
120
|
+
// update acp.bin mid-session; the next turn picks it up (no restart).
|
|
121
|
+
bin: () => loadConfig().acp.bin,
|
|
122
|
+
log: (msg, data) => {
|
|
123
|
+
if (!acpFailures.has(msg)) return;
|
|
124
|
+
console.error(`[antigravity-bridge acp] ${msg}${data !== undefined ? " " + JSON.stringify(data) : ""}`);
|
|
125
|
+
},
|
|
126
|
+
mcpServers: () => {
|
|
127
|
+
const handle = mcpHandle;
|
|
128
|
+
if (!handle) return [];
|
|
129
|
+
// The bridge 403s any request without the shared-secret header; the
|
|
130
|
+
// legacy engine carries it via mcp_config.json, ACP via headers[].
|
|
131
|
+
return [
|
|
132
|
+
{
|
|
133
|
+
name: "pi-bridge",
|
|
134
|
+
type: "http",
|
|
135
|
+
url: `http://127.0.0.1:${handle.port}/mcp`,
|
|
136
|
+
headers: [{ name: TOKEN_HEADER, value: handle.token }],
|
|
137
|
+
},
|
|
138
|
+
];
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
// The active engine is resolved from the latched load-time value.
|
|
142
|
+
const activeDriver = (): TurnDriver => (engine === "acp" ? acpDriver : legacyDriver);
|
|
143
|
+
// The provider's stream-json slot gets the LEGACY driver explicitly - never
|
|
144
|
+
// activeDriver(), or a load-time acp engine would make deps.driver and
|
|
145
|
+
// deps.acpDriver the same object and break the engine identity check.
|
|
146
|
+
const driver = legacyDriver;
|
|
147
|
+
// The no-patch pi-tool round-trip store: the MCP bridge parks calls here;
|
|
148
|
+
// the provider emits them as real pi toolUse turns and completes them from
|
|
149
|
+
// the next call's toolResult.
|
|
150
|
+
const roundTrips = new ToolRoundTrips(activeDriver);
|
|
90
151
|
const replay = new WrapperReplay();
|
|
91
152
|
// Native re-exec only emits for builtins actually active in the session;
|
|
92
153
|
// anything else (or an unknown name) falls back to the wrapper card.
|
|
@@ -99,9 +160,20 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
99
160
|
}
|
|
100
161
|
};
|
|
101
162
|
// A settled turn cannot answer its parked calls; the driver never sees
|
|
102
|
-
// ToolRoundTrips, so the provider bridges the two here.
|
|
103
|
-
|
|
104
|
-
|
|
163
|
+
// ToolRoundTrips, so the provider bridges the two here (both engines).
|
|
164
|
+
const onTurnEnd = () => roundTrips.failAll("antigravity turn ended with an unresolved pi tool call");
|
|
165
|
+
legacyDriver.onTurnEnd = onTurnEnd;
|
|
166
|
+
acpDriver.onTurnEnd = onTurnEnd;
|
|
167
|
+
const streamSimple = createStreamSimple({
|
|
168
|
+
entries,
|
|
169
|
+
store,
|
|
170
|
+
driver,
|
|
171
|
+
acpDriver,
|
|
172
|
+
roundTrips,
|
|
173
|
+
replay,
|
|
174
|
+
nativeActive,
|
|
175
|
+
engine,
|
|
176
|
+
});
|
|
105
177
|
|
|
106
178
|
pi.registerProvider("antigravity", {
|
|
107
179
|
name: "Antigravity (agy)",
|
|
@@ -122,7 +194,15 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
122
194
|
streamSimple,
|
|
123
195
|
});
|
|
124
196
|
|
|
125
|
-
registerAgyCommand(pi, {
|
|
197
|
+
registerAgyCommand(pi, {
|
|
198
|
+
entries,
|
|
199
|
+
store,
|
|
200
|
+
usingFallback,
|
|
201
|
+
driver,
|
|
202
|
+
acpDriver,
|
|
203
|
+
engine,
|
|
204
|
+
getMcpPort: () => mcpHandle?.port ?? null,
|
|
205
|
+
});
|
|
126
206
|
|
|
127
207
|
// AskAntigravity tool: one-shot delegation to agy (ported from
|
|
128
208
|
// pi-ask-antigravity). When both extensions are installed, the bridge wins
|
|
@@ -153,7 +233,6 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
153
233
|
// Calls park in the provider's round-trip store and complete through pi's
|
|
154
234
|
// normal toolUse loop (native cards, permissions, hooks) - no patch, no
|
|
155
235
|
// privileged API. Started on session_start, torn down on session_shutdown.
|
|
156
|
-
let mcpHandle: McpServerHandle | null = null;
|
|
157
236
|
pi.on("session_start", async (_event, ctx) => {
|
|
158
237
|
// Legacy cleanup: users who ran the old consent-gated patcher still
|
|
159
238
|
// carry pi.invokeTool in their installed pi. Inert, but tell them once
|
|
@@ -172,29 +251,52 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
172
251
|
} catch {
|
|
173
252
|
/* detection is best-effort */
|
|
174
253
|
}
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
254
|
+
// ACP self-heal: engine=acp needs a server binary + auth. Silent when
|
|
255
|
+
// everything is ready; installs from the registry and bootstraps auth
|
|
256
|
+
// otherwise; manual instructions only on failure. Fire-and-forget: it
|
|
257
|
+
// must not delay session start (and nothing spawns until the first turn).
|
|
258
|
+
if (engine === "acp" && !acpSelfHealRan) {
|
|
259
|
+
acpSelfHealRan = true;
|
|
260
|
+
void ensureAcpReady({ configBin: loadConfig().acp.bin }).then((status) => {
|
|
261
|
+
if (status.ok) {
|
|
262
|
+
if (status.binarySource === "installed" || status.binarySource === "existing") {
|
|
263
|
+
saveConfig({ acp: { bin: status.bin, permissions: loadConfig().acp.permissions } });
|
|
264
|
+
}
|
|
265
|
+
if (status.needsLogin) {
|
|
266
|
+
const msg =
|
|
267
|
+
"ACP: one-time Google login pending. Your next antigravity message opens the browser; sign in with your Antigravity subscription account (the same login as the agy CLI). Tokens stay on your machine.";
|
|
268
|
+
if (ctx.hasUI) ctx.ui.notify(msg, "info");
|
|
269
|
+
else console.error(`[antigravity-bridge] ${msg}`);
|
|
270
|
+
}
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const msg = `ACP auto-setup failed (${status.error}).\n${status.manual}`;
|
|
274
|
+
if (ctx.hasUI) ctx.ui.notify(msg, "warning");
|
|
275
|
+
else console.error(`[antigravity-bridge] ${msg}`);
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
// Bridge failure logger. Routine lifecycle (listening,
|
|
279
|
+
// bridge-config-written/removed, closed) is normal startup/teardown
|
|
280
|
+
// traffic: toasting it every session, or pinning it via stderr in
|
|
281
|
+
// headless mode, was noise. Only genuine failures surface - as a
|
|
282
|
+
// warning toast (ctx.ui.notify, ephemeral) or stderr when headless.
|
|
283
|
+
// Per-turn success events (list-tools / call-tool) stay silent.
|
|
182
284
|
const mcpLog = (s: string, d?: unknown) => {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
"
|
|
285
|
+
// Routine abort traffic: failAll fires on turn end / session shutdown
|
|
286
|
+
// and the bridge answers every parked call with an error. Not a fault.
|
|
287
|
+
if (s === "call-tool-fail") {
|
|
288
|
+
const detail = (d as { msg?: string } | undefined)?.msg ?? "";
|
|
289
|
+
if (detail.includes("unresolved pi tool call") || detail.includes("session shut down")) return;
|
|
290
|
+
}
|
|
291
|
+
const failures = new Set([
|
|
292
|
+
"http-error", "bridge-config-write-failed", "call-tool-fail",
|
|
293
|
+
"transport-error", "handleRequest-error", "request-error",
|
|
294
|
+
"request-handler-error", "unauthorized",
|
|
188
295
|
]);
|
|
189
|
-
if (!
|
|
296
|
+
if (!failures.has(s)) return;
|
|
190
297
|
const msg = `[antigravity-bridge mcp] ${s}${d !== undefined ? " " + JSON.stringify(d) : ""}`;
|
|
191
|
-
if (ctx.hasUI)
|
|
192
|
-
|
|
193
|
-
|| s === "bridge-config-removed" || s === "closed";
|
|
194
|
-
ctx.ui.notify(msg, ok ? "info" : "warning");
|
|
195
|
-
} else {
|
|
196
|
-
console.error(msg);
|
|
197
|
-
}
|
|
298
|
+
if (ctx.hasUI) ctx.ui.notify(msg, "warning");
|
|
299
|
+
else console.error(msg);
|
|
198
300
|
};
|
|
199
301
|
// Start the bridge unless the user turned it off. No patch gate, no
|
|
200
302
|
// consent flow: calls route through pi's normal toolUse loop.
|
|
@@ -266,7 +368,8 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
266
368
|
mcpHandle = null;
|
|
267
369
|
await h?.close();
|
|
268
370
|
roundTrips.failAll("antigravity session shut down");
|
|
269
|
-
await
|
|
371
|
+
await legacyDriver.close("shutdown");
|
|
372
|
+
await acpDriver.close("shutdown");
|
|
270
373
|
});
|
|
271
374
|
}
|
|
272
375
|
|
|
@@ -276,7 +379,10 @@ interface AgyCommandCtx {
|
|
|
276
379
|
entries: AgyModelEntry[];
|
|
277
380
|
store: SessionStore;
|
|
278
381
|
usingFallback: boolean;
|
|
279
|
-
driver:
|
|
382
|
+
driver: TurnDriver;
|
|
383
|
+
acpDriver: AcpDriver;
|
|
384
|
+
/** Engine latched at extension load (see the provider wiring note). */
|
|
385
|
+
engine: Engine;
|
|
280
386
|
getMcpPort: () => number | null;
|
|
281
387
|
}
|
|
282
388
|
|
|
@@ -293,6 +399,7 @@ function statusText(ctx: AgyCommandCtx): string {
|
|
|
293
399
|
const perm = config.skipPermissions ? "auto-approved (DANGEROUS)" : "prompt (hangs in -p)";
|
|
294
400
|
return [
|
|
295
401
|
"Antigravity bridge",
|
|
402
|
+
` engine: ${config.engine}${config.engine === "acp" ? " (official server, opt-in)" : ""}`,
|
|
296
403
|
` models: ${ctx.entries.length} ${source}`,
|
|
297
404
|
` mode: ${config.mode}`,
|
|
298
405
|
` permissions: ${perm}`,
|
|
@@ -304,7 +411,7 @@ function statusText(ctx: AgyCommandCtx): string {
|
|
|
304
411
|
` digest: ${config.digest ? "on" : "off"}`,
|
|
305
412
|
` system prompt: ${config.systemPrompt ? "on" : "off"}`,
|
|
306
413
|
"",
|
|
307
|
-
"Subcommands: /agy mode plan|accept-edits, /agy permissions on|off, /agy model flash|pro|gemini, /agy thinking low|medium|high, /agy digest on|off, /agy system-prompt on|off, /agy clear",
|
|
414
|
+
"Subcommands: /agy engine stream-json|acp, /agy mode plan|accept-edits, /agy permissions on|off, /agy model flash|pro|gemini, /agy thinking low|medium|high, /agy digest on|off, /agy system-prompt on|off, /agy acp-auth, /agy patch-cleanup, /agy clear",
|
|
308
415
|
].join("\n");
|
|
309
416
|
}
|
|
310
417
|
|
|
@@ -312,7 +419,7 @@ function statusText(ctx: AgyCommandCtx): string {
|
|
|
312
419
|
function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
313
420
|
pi.registerCommand("agy", {
|
|
314
421
|
description:
|
|
315
|
-
"Antigravity provider: status, doctor, mode picker, clear sessions. Usage: /agy [status|doctor|mode [plan|accept-edits]|digest on|off|system-prompt on|off|patch-cleanup|clear]",
|
|
422
|
+
"Antigravity provider: status, doctor, engine picker, mode picker, clear sessions. Usage: /agy [status|doctor|engine stream-json|acp|mode [plan|accept-edits]|permissions on|off|digest on|off|system-prompt on|off|acp-auth|patch-cleanup|clear]",
|
|
316
423
|
handler: async (args, cmdCtx: ExtensionCommandContext) => {
|
|
317
424
|
const ui = cmdCtx.ui;
|
|
318
425
|
const mode = cmdCtx.mode;
|
|
@@ -345,28 +452,112 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
345
452
|
);
|
|
346
453
|
return;
|
|
347
454
|
}
|
|
455
|
+
if (sub === "engine") {
|
|
456
|
+
if (val === "acp" || val === "stream-json") {
|
|
457
|
+
if (val === "acp" && loadConfig().mode === "plan") {
|
|
458
|
+
ui?.notify("mode is plan; the ACP engine has no plan mode. /agy mode accept-edits first.", "warning");
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
const next = saveConfig({ engine: val });
|
|
462
|
+
if (next.engine !== "acp") {
|
|
463
|
+
ui?.notify("engine set to stream-json. Takes effect on the next pi start (or /reload).", "info");
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
// Self-service setup: install the server from the official
|
|
467
|
+
// registry and bootstrap auth now, so the restart just works.
|
|
468
|
+
// Manual instructions only when a step fails.
|
|
469
|
+
ui?.notify("engine set to acp. Preparing the server (binary + auth)…", "info");
|
|
470
|
+
const status = await ensureAcpReady({
|
|
471
|
+
configBin: loadConfig().acp.bin,
|
|
472
|
+
onProgress: (m) => ui?.notify(m, "info"),
|
|
473
|
+
});
|
|
474
|
+
if (!status.ok) {
|
|
475
|
+
ui?.notify(`ACP auto-setup failed (${status.error}).\n${status.manual}`, "warning");
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
saveConfig({ acp: { bin: status.bin, permissions: loadConfig().acp.permissions } });
|
|
479
|
+
ui?.notify(
|
|
480
|
+
status.needsLogin
|
|
481
|
+
? "ACP engine ready. Your first ACP message opens the Google login in your browser: sign in with the account of your Antigravity subscription (the same login as the Antigravity CLI, agy). One-time; tokens stay on your machine and this extension never sees them. Takes effect on the next pi start (or /reload)."
|
|
482
|
+
: `ACP engine ready (auth: ${status.auth}). Takes effect on the next pi start (or /reload).`,
|
|
483
|
+
"info",
|
|
484
|
+
);
|
|
485
|
+
} else {
|
|
486
|
+
ui?.notify(`current engine: ${loadConfig().engine}\nusage: /agy engine stream-json|acp`, "info");
|
|
487
|
+
}
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
if (sub === "acp-auth") {
|
|
491
|
+
ui?.notify(
|
|
492
|
+
[
|
|
493
|
+
"ACP engine authentication (one-time; usually automatic -",
|
|
494
|
+
"/agy engine acp and session start set this up for you):",
|
|
495
|
+
"",
|
|
496
|
+
"The server is Google's official Antigravity ACP, installed from Google's",
|
|
497
|
+
"own registry. Logging in uses your Antigravity subscription: the same",
|
|
498
|
+
"Google account and plan as the Antigravity CLI (agy). It is no different",
|
|
499
|
+
"from logging into the CLI; the server just keeps its own token file on",
|
|
500
|
+
"your machine, like any Google tool. This extension never sees your",
|
|
501
|
+
"credentials.",
|
|
502
|
+
"",
|
|
503
|
+
"1. Server binary: auto-setup installs it. Manual: agy_acp_server.par from",
|
|
504
|
+
" the antigravity-acp registry; point acp.bin or AGY_ACP_BIN at it.",
|
|
505
|
+
'2. Default: put {"auth":{"type":"oauth-personal"}} in',
|
|
506
|
+
" ~/.gemini/antigravity-acp/settings.json, run one turn, and complete the",
|
|
507
|
+
" Google login that opens in your browser (headless: tunnel 127.0.0.1:<port>",
|
|
508
|
+
" over ssh, then open the URL on your machine).",
|
|
509
|
+
' Headless alternative: GEMINI_API_KEY + {"auth":{"type":"gemini-api-key"}}',
|
|
510
|
+
" (metered paid API - not your Antigravity plan). The key is used only",
|
|
511
|
+
" when that type is selected; with the default oauth-personal in place,",
|
|
512
|
+
" an exported key is ignored.",
|
|
513
|
+
"3. Run one turn; /agy doctor shows the server version when auth is OK.",
|
|
514
|
+
].join("\n"),
|
|
515
|
+
"info",
|
|
516
|
+
);
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
348
519
|
if (sub === "doctor") {
|
|
349
520
|
const config = loadConfig();
|
|
350
|
-
const
|
|
521
|
+
const engine = ctx.engine;
|
|
522
|
+
const snap = (engine === "acp" ? ctx.acpDriver : ctx.driver).snapshot();
|
|
351
523
|
const port = ctx.getMcpPort();
|
|
352
524
|
const lines = [
|
|
353
525
|
"Antigravity doctor (no tokens spent)",
|
|
526
|
+
` engine: ${engine}`,
|
|
354
527
|
` bridge: ${config.bridgeTools}${port ? ` (port ${port})` : " (not running)"}`,
|
|
355
|
-
` driver: ${snap.state}${snap.pid ? ` pid=${snap.pid}` : ""}${snap.conversationId ? `
|
|
528
|
+
` driver: ${snap.state}${snap.pid ? ` pid=${snap.pid}` : ""}${snap.conversationId ? ` session=${snap.conversationId.slice(0, 8)}` : ""}`,
|
|
356
529
|
` driver stats: spawns=${snap.stats.spawns} turns=${snap.stats.turns} reused=${snap.stats.reused} recycles=${snap.stats.recycles}${snap.stats.lastRecycleReason ? ` (last: ${snap.stats.lastRecycleReason})` : ""}`,
|
|
357
530
|
` sessions: ${ctx.store.size} bound`,
|
|
358
531
|
` models: ${ctx.entries.length} ${ctx.usingFallback ? "FALLBACK (agy models failed)" : "discovered"}`,
|
|
359
532
|
` config: ${CONFIG_PATH}`,
|
|
360
533
|
];
|
|
534
|
+
if (snap.engine === "acp" && snap.acp) {
|
|
535
|
+
lines.push(
|
|
536
|
+
` acp session: ${snap.acp.sessionId ?? "(none)"}`,
|
|
537
|
+
` acp server: ${snap.acp.serverVersion ?? "unknown"}${snap.acp.agentTitle ? ` (${snap.acp.agentTitle})` : ""}`,
|
|
538
|
+
` acp stats: prompts=${snap.acp.prompts} created=${snap.acp.sessionsCreated} loaded=${snap.acp.sessionsLoaded} kills=${snap.acp.kills} reconnects=${snap.acp.reconnects} cancel=${snap.acp.cancelSupported === null ? "unprobed" : snap.acp.cancelSupported ? "supported" : "unsupported (kill+reload)"}`,
|
|
539
|
+
);
|
|
540
|
+
}
|
|
361
541
|
if (snap.lifecycle.length > 0) {
|
|
362
542
|
lines.push(" lifecycle (last 5):");
|
|
363
543
|
for (const entry of snap.lifecycle.slice(-5)) lines.push(` ${entry}`);
|
|
364
544
|
}
|
|
545
|
+
if (engine === "acp") {
|
|
546
|
+
const setup = inspectAcpSetup({ configBin: config.acp.bin });
|
|
547
|
+
lines.push(
|
|
548
|
+
` acp binary: ${setup.bin ?? "not found (auto-setup offers install)"}${setup.source ? ` (${setup.source})` : ""}`,
|
|
549
|
+
` acp auth: ${setup.auth ?? "not configured (auto-setup bootstraps)"}`,
|
|
550
|
+
);
|
|
551
|
+
}
|
|
365
552
|
ui?.notify(lines.join("\n"), "info");
|
|
366
553
|
return;
|
|
367
554
|
}
|
|
368
555
|
if (sub === "mode") {
|
|
369
556
|
if (val === "plan" || val === "accept-edits") {
|
|
557
|
+
if (val === "plan" && ctx.engine === "acp") {
|
|
558
|
+
ui?.notify("the ACP engine has no plan mode (RC01). /agy engine stream-json first, or /agy mode accept-edits.", "warning");
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
370
561
|
const next = saveConfig({ mode: val as AgyMode });
|
|
371
562
|
ui?.notify(`mode set to ${next.mode}`, "info");
|
|
372
563
|
} else {
|
|
@@ -531,6 +722,11 @@ async function openAgyPicker(ui: ExtensionUIContext, ctx: AgyCommandCtx): Promis
|
|
|
531
722
|
)
|
|
532
723
|
return;
|
|
533
724
|
|
|
725
|
+
if (pending.mode === "plan" && ctx.engine === "acp") {
|
|
726
|
+
ui.notify("the ACP engine has no plan mode (RC01). Switch /agy engine stream-json first.", "warning");
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
|
|
534
730
|
try {
|
|
535
731
|
const next = saveConfig(pending);
|
|
536
732
|
const changed = [
|
package/package.json
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@estebanforge/pi-antigravity-bridge",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.4.1",
|
|
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",
|
|
7
7
|
"pi-extension",
|
|
8
8
|
"antigravity",
|
|
9
|
+
"antigravity-acp",
|
|
9
10
|
"agy",
|
|
10
11
|
"gemini",
|
|
11
12
|
"google",
|
|
13
|
+
"google-acp",
|
|
14
|
+
"acp",
|
|
15
|
+
"agent-client-protocol",
|
|
16
|
+
"mcp",
|
|
17
|
+
"mcp-server",
|
|
12
18
|
"provider",
|
|
13
19
|
"streaming"
|
|
14
20
|
],
|
|
@@ -53,9 +59,10 @@
|
|
|
53
59
|
}
|
|
54
60
|
},
|
|
55
61
|
"devDependencies": {
|
|
56
|
-
"@earendil-works/pi-ai": "^0.
|
|
57
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
58
|
-
"@earendil-works/pi-
|
|
62
|
+
"@earendil-works/pi-ai": "^0.85.0",
|
|
63
|
+
"@earendil-works/pi-coding-agent": "^0.85.0",
|
|
64
|
+
"@earendil-works/pi-server": "^0.85.0",
|
|
65
|
+
"@earendil-works/pi-tui": "^0.85.0",
|
|
59
66
|
"@types/node": "^22.0.0",
|
|
60
67
|
"tsx": "^4.19.0",
|
|
61
68
|
"typebox": "^1.1.38",
|