@estebanforge/pi-antigravity-bridge 1.9.3 → 1.10.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 +16 -0
- package/README.md +12 -14
- package/extensions/index.ts +115 -375
- package/package.json +1 -1
- package/src/acp/connection.ts +47 -105
- package/src/acp/driver.ts +25 -59
- package/src/acp/events.ts +17 -11
- package/src/acp/home.ts +95 -0
- package/src/acp/jsonrpc.ts +6 -1
- package/src/acp/setup.ts +71 -437
- package/src/agents.ts +1 -1
- package/src/agy-install.ts +85 -0
- package/src/agy-paths.ts +3 -6
- package/src/agy-setup.ts +72 -0
- package/src/agy-version.ts +3 -4
- package/src/ask-tool.ts +3 -3
- package/src/config.ts +17 -19
- package/src/driver-types.ts +3 -5
- package/src/engine-picker.ts +8 -12
- package/src/mcp-registration.ts +1 -1
- package/src/mcp-server.ts +5 -5
- package/src/provider.ts +4 -4
- package/src/queued-messages.ts +7 -26
- package/src/acp/auth.ts +0 -174
- package/src/acp/browser-capture.ts +0 -104
package/extensions/index.ts
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
// on disk and pi's inline diff review does not engage.
|
|
13
13
|
//
|
|
14
14
|
// /agy command: full runtime config surface (engine, mode, permissions,
|
|
15
|
-
// bridge tools, model, thinking, digest, system prompt
|
|
16
|
-
//
|
|
15
|
+
// bridge tools, model, thinking, digest, system prompt) plus doctor,
|
|
16
|
+
// authentication instructions, patch-cleanup, and session clear. Config persists to
|
|
17
17
|
// ~/.pi/agent/antigravity-bridge/config.json so toggles survive restarts.
|
|
18
18
|
|
|
19
19
|
import os from "node:os";
|
|
@@ -59,15 +59,15 @@ import { detectPermissionGateExtensions, resolveGateMode } from "../src/approval
|
|
|
59
59
|
import { hookScriptSource, removeGateHooks, stageGateHooks, sweepWorkspaceGateGroups } from "../src/approval-hook.js";
|
|
60
60
|
import { StreamDriver } from "../src/driver.js";
|
|
61
61
|
import { AcpDriver } from "../src/acp/driver.js";
|
|
62
|
-
import {
|
|
63
|
-
import {
|
|
64
|
-
import {
|
|
65
|
-
import {
|
|
62
|
+
import { sweepStaleAcpHomes } from "../src/acp/home.js";
|
|
63
|
+
import { cleanupManagedAcpInstall } from "../src/acp/setup.js";
|
|
64
|
+
import { agyAuthenticationMessage } from "../src/agy-install.js";
|
|
65
|
+
import { createAgySetupOffer } from "../src/agy-setup.js";
|
|
66
66
|
import { ensureQueuedMessagesDefaults } from "../src/queued-messages.js";
|
|
67
67
|
import type { TurnDriver, TurnOutcome } from "../src/driver-types.js";
|
|
68
|
-
import { CONFIG_PATH, loadConfig, logsDir, MAX_TURN_CAP_MIN, parseCapMinutes, saveConfig, type AgyMode, type BridgeTools, type Engine, type ThinkingTier } from "../src/config.js";
|
|
68
|
+
import { CONFIG_PATH, legacyAcpBinConfigured, loadConfig, logsDir, MAX_TURN_CAP_MIN, parseCapMinutes, saveConfig, type AgyMode, type BridgeTools, type Engine, type ThinkingTier } from "../src/config.js";
|
|
69
69
|
import { agyMissingMessage, ENGINE_SETTINGS_ROW_ID, engineSettingsRow, isAgyInstalled, savedEngineMessage, showEnginePicker, shouldOfferEnginePicker } from "../src/engine-picker.js";
|
|
70
|
-
import { checkAgyCliVersion, describeAgyVersionCheck, MIN_AGY_VERSION } from "../src/agy-version.js";
|
|
70
|
+
import { agyVersionVerdict, checkAgyCliVersion, describeAgyVersionCheck, MIN_AGY_ACP_VERSION, MIN_AGY_VERSION } from "../src/agy-version.js";
|
|
71
71
|
import { isValidAgyAgentName, listAgyAgents } from "../src/agents.js";
|
|
72
72
|
import { formatSubagentRoster, SubagentRoster } from "../src/subagent-roster.js";
|
|
73
73
|
import { spawn } from "node:child_process";
|
|
@@ -80,7 +80,7 @@ import { agyConversationDir } from "../src/agy-paths.js";
|
|
|
80
80
|
import { createDailyLogger, type DailyLogger } from "../src/daily-log.js";
|
|
81
81
|
import { registerAskAntigravityTool, toolModelsFromRaw } from "../src/ask-tool.js";
|
|
82
82
|
import { registerWebTools } from "../src/web-tools.js";
|
|
83
|
-
import { bridgeMcpConfigDir, startMcpServer, TOKEN_HEADER, type McpServerHandle } from "../src/mcp-server.js";
|
|
83
|
+
import { bridgeMcpConfigDir, startMcpServer, sweepStaleBridgeDirs, TOKEN_HEADER, type McpServerHandle } from "../src/mcp-server.js";
|
|
84
84
|
import {
|
|
85
85
|
registerBridgeServer,
|
|
86
86
|
healBridgeSuppression,
|
|
@@ -102,15 +102,8 @@ import { Type } from "typebox";
|
|
|
102
102
|
import { patchStatus, restorePatch } from "../src/patch-cleanup.js";
|
|
103
103
|
import { withDialogLock } from "../src/dialog-lock.js";
|
|
104
104
|
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
// that no longer exists. Wire once at load; the hook is a no-op until set.
|
|
108
|
-
setAuthInvalidatedHook(() => clearAcpAuthVerified());
|
|
109
|
-
|
|
110
|
-
// Last UI seen (session_start / /agy commands). The ACP login URL arrives
|
|
111
|
-
// via the driver log sink, which has no command context; the stash lets that
|
|
112
|
-
// sink toast instead of only logging to stderr. Module scope: both the
|
|
113
|
-
// default export (session_start, log sink) and registerAgyCommand assign it.
|
|
105
|
+
// Last UI seen (session_start / /agy commands). Module scope: both the
|
|
106
|
+
// default export and registerAgyCommand assign it.
|
|
114
107
|
let activeUi: ExtensionUIContext | null = null;
|
|
115
108
|
|
|
116
109
|
function resolveAgyBinary(): string {
|
|
@@ -211,26 +204,13 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
211
204
|
// Approval-gate hook script (per-pid, token embedded). Written at session
|
|
212
205
|
// start when the gate is active; removed at session_shutdown.
|
|
213
206
|
let gateScriptPath: string | null = null;
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
let
|
|
217
|
-
// Warn-once-per-process flags for the missing-agy-CLI toast and the
|
|
218
|
-
// too-old-agy version gate (both stream-json).
|
|
207
|
+
let managedAcpCleanupRan = false;
|
|
208
|
+
let legacyAcpWarningShown = false;
|
|
209
|
+
let agySetupOffer: (() => Promise<void>) | null = null;
|
|
219
210
|
let agyMissingWarned = false;
|
|
220
211
|
let agyVersionWarned = false;
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
// and the driver logs it as "auth-url". Local users keep the automatic
|
|
224
|
-
// browser open; over SSH the URL surfaces for copy-paste with the
|
|
225
|
-
// port-forward command.
|
|
226
|
-
const authCapture = setupAuthUrlCapture();
|
|
227
|
-
if (!authCapture && process.platform !== "win32") {
|
|
228
|
-
// Rare (unwritable data dir). Surfacing the diagnostic: without it,
|
|
229
|
-
// login URLs would silently stop appearing on headless boxes.
|
|
230
|
-
console.error("[antigravity-bridge] OAuth URL capture unavailable; login URL surfacing is off (setup failed).");
|
|
231
|
-
}
|
|
232
|
-
// Two turn engines behind one contract (plan §9): the official ACP
|
|
233
|
-
// server (default for new installs) and the stream-json `agy` CLI
|
|
212
|
+
// Two turn engines behind one contract: `agy acp` (default for new
|
|
213
|
+
// installs) and the stream-json `agy` CLI
|
|
234
214
|
// (fallback). Neither spawns anything until its first turn.
|
|
235
215
|
// ACP log routing: only genuine failures reach stderr. Routine lifecycle
|
|
236
216
|
// (driver-created, spawn, session-new, ...) stays in the driver's
|
|
@@ -260,63 +240,17 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
260
240
|
const level = failed ? "warn" : msg === "turn-start" ? "info" : "debug";
|
|
261
241
|
fileLog.log(msg, data, level);
|
|
262
242
|
};
|
|
263
|
-
// Shared ACP log sink (driver turns AND /agy auth): the login URL event
|
|
264
|
-
// toasts so SSH users can copy it; genuine failures reach stderr.
|
|
265
243
|
const acpLog = (msg: string, data?: unknown): void => {
|
|
266
|
-
// The daily file log gets EVERY driver event (auth-url stripped of its
|
|
267
|
-
// query string - it carries one-time login state); the filters below
|
|
268
|
-
// only decide what reaches the user.
|
|
269
|
-
const fileData =
|
|
270
|
-
msg === "auth-url"
|
|
271
|
-
? { port: (data as { port?: number | null } | undefined)?.port ?? null, url: String((data as { url?: string } | undefined)?.url ?? "").split("?")[0] }
|
|
272
|
-
: data;
|
|
273
|
-
// Failures warn; turn-start + auth-url are the always-on skeleton;
|
|
274
|
-
// routine per-event lifecycle (spawn, session-load, unparked, ...) is
|
|
275
|
-
// verbose-only. Deliberate teardown exits (Esc abort kill, shutdown,
|
|
276
|
-
// idle recycle) demote to debug: routine, and the warn tier stays the
|
|
277
|
-
// "what broke" grep.
|
|
278
244
|
const expectedExit =
|
|
279
245
|
msg === "connection-exited" &&
|
|
280
246
|
(data as { expected?: boolean } | undefined)?.expected === true;
|
|
281
|
-
const level = expectedExit
|
|
282
|
-
|
|
283
|
-
: acpFailures.has(msg)
|
|
284
|
-
? "warn"
|
|
285
|
-
: msg === "turn-start" || msg === "auth-url"
|
|
286
|
-
? "info"
|
|
287
|
-
: "debug";
|
|
288
|
-
fileLog.log(msg, fileData, level);
|
|
289
|
-
if (msg === "auth-url") {
|
|
290
|
-
const { url, port } = (data ?? {}) as { url?: string; port?: number | null };
|
|
291
|
-
if (!url) return;
|
|
292
|
-
// Blank lines fence the URL off from the rest of the text: it is long
|
|
293
|
-
// and wraps, so separation keeps it readable and copyable.
|
|
294
|
-
const parts = ["Google sign-in URL for the ACP engine:", "", url];
|
|
295
|
-
if (port) {
|
|
296
|
-
parts.push(
|
|
297
|
-
"",
|
|
298
|
-
"SSH session? Forward the port on your machine first:",
|
|
299
|
-
` ssh -N -L ${port}:127.0.0.1:${port} <user@host>`,
|
|
300
|
-
);
|
|
301
|
-
}
|
|
302
|
-
const text = parts.join("\n");
|
|
303
|
-
if (activeUi) activeUi.notify(text, "warning");
|
|
304
|
-
else console.error(`[antigravity-bridge acp] ${text}`);
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
if (!acpFailures.has(msg)) return;
|
|
308
|
-
// Console noise is gone: warns toast through the fileLog wrapper (with
|
|
309
|
-
// a stderr fallback when no UI exists), and raw tails stay in the file
|
|
310
|
-
// log behind AGY_DEBUG.
|
|
247
|
+
const level = expectedExit ? "debug" : acpFailures.has(msg) ? "warn" : msg === "turn-start" ? "info" : "debug";
|
|
248
|
+
fileLog.log(msg, data, level);
|
|
311
249
|
};
|
|
312
250
|
const acpDriver = new AcpDriver({
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
// Registry-declared argv for the managed binary (e.g. linux --uid=).
|
|
317
|
-
// Empty for env/config overrides, which own their argv. Resolved per
|
|
318
|
-
// connection alongside bin so a mid-session install picks them up.
|
|
319
|
-
binArgs: () => resolveInstalledArgs({ configBin: loadConfig().acp.bin }),
|
|
251
|
+
bin: binary,
|
|
252
|
+
binArgs: ["acp"],
|
|
253
|
+
isolateHome: true,
|
|
320
254
|
// Resolved per turn: /agy and AGY_USAGE_ESTIMATE changes apply without
|
|
321
255
|
// a restart. Without this the driver defaults to "estimate" and the
|
|
322
256
|
// config knob (incl. "off") is dead.
|
|
@@ -338,7 +272,6 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
338
272
|
return idx >= 0 ? (req.options[idx]?.optionId ?? undefined) : undefined;
|
|
339
273
|
});
|
|
340
274
|
},
|
|
341
|
-
...(authCapture ? { extraEnv: authCapture.browserEnv, authUrlFile: authCapture.file } : {}),
|
|
342
275
|
log: acpLog,
|
|
343
276
|
mcpServers: () => {
|
|
344
277
|
const handle = mcpHandle;
|
|
@@ -445,7 +378,7 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
445
378
|
return;
|
|
446
379
|
}
|
|
447
380
|
closePollRun();
|
|
448
|
-
if (event.mcpServer
|
|
381
|
+
if (/^pi-bridge(?:-\d+)?$/.test(event.mcpServer ?? "")) return;
|
|
449
382
|
// MCP calls through our own pi-bridge already have genuine Pi tool cards;
|
|
450
383
|
// only native Antigravity / other MCP events need display-only entries.
|
|
451
384
|
if (event.status === "started") {
|
|
@@ -567,11 +500,9 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
567
500
|
getMcpPort: () => mcpHandle?.port ?? null,
|
|
568
501
|
bridgeTools: getBridgeTools,
|
|
569
502
|
hiddenBridgeTools,
|
|
570
|
-
acpLog,
|
|
571
503
|
fileLog,
|
|
572
504
|
roster,
|
|
573
|
-
|
|
574
|
-
runAcpPickSetup,
|
|
505
|
+
checkAgySetup,
|
|
575
506
|
});
|
|
576
507
|
|
|
577
508
|
// AskAntigravity tool: one-shot delegation to agy (ported from
|
|
@@ -607,74 +538,48 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
607
538
|
},
|
|
608
539
|
});
|
|
609
540
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
541
|
+
function offerSetup(ui: ExtensionUIContext, explicit = false): void {
|
|
542
|
+
if (agySetupOffer && explicit) {
|
|
543
|
+
ui.notify(`CLI setup was already offered in this session. ${agyMissingMessage()}`, "info");
|
|
544
|
+
}
|
|
545
|
+
agySetupOffer ??= createAgySetupOffer({
|
|
546
|
+
ui,
|
|
547
|
+
binaryOverride: process.env.AGY_BIN || undefined,
|
|
548
|
+
missingMessage: agyMissingMessage,
|
|
549
|
+
log: (event, data, level) => fileLog.log(event, data, level),
|
|
550
|
+
});
|
|
551
|
+
void agySetupOffer();
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Availability checks never block bridge startup. The setup offer owns
|
|
555
|
+
// prompting, deduplication, and failure containment.
|
|
556
|
+
async function checkAgySetup(ctx: { ui: ExtensionUIContext; mode: string }, selectedEngine: Engine, explicit = false): Promise<void> {
|
|
622
557
|
try {
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
// percent variant updates every chunk and would spam the chat.
|
|
631
|
-
ctx.ui.setStatus("agy-acp", m);
|
|
632
|
-
if (m !== lastPhase && !/\d+%/.test(m)) {
|
|
633
|
-
ctx.ui.notify(m, "info");
|
|
634
|
-
lastPhase = m;
|
|
635
|
-
}
|
|
636
|
-
},
|
|
637
|
-
});
|
|
638
|
-
ctx.ui.setStatus("agy-acp", undefined);
|
|
639
|
-
fileLog.log(
|
|
640
|
-
"acp-setup",
|
|
641
|
-
status.ok
|
|
642
|
-
? { ok: true, binarySource: status.binarySource, needsLogin: status.needsLogin }
|
|
643
|
-
: { ok: false, error: status.error },
|
|
644
|
-
status.ok ? "info" : "warn",
|
|
645
|
-
);
|
|
646
|
-
if (!status.ok) {
|
|
647
|
-
ctx.ui.notify(`ACP auto-setup failed (${status.error}).\n${status.manual}`, "warning");
|
|
558
|
+
if (!isAgyInstalled(binary)) {
|
|
559
|
+
if (ctx.mode === "tui") {
|
|
560
|
+
offerSetup(ctx.ui, explicit);
|
|
561
|
+
} else if (!agyMissingWarned) {
|
|
562
|
+
agyMissingWarned = true;
|
|
563
|
+
console.error(`[antigravity-bridge] ${agyMissingMessage()}`);
|
|
564
|
+
}
|
|
648
565
|
return;
|
|
649
566
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
saveConfig({ acp: { ...loadConfig().acp, bin: status.bin } });
|
|
653
|
-
if (!status.needsLogin) {
|
|
654
|
-
ctx.ui.notify(`ACP server ready (auth: ${status.auth}). Restart applies the engine.`, "info");
|
|
567
|
+
if (agyVersionWarned) {
|
|
568
|
+
if (explicit && ctx.mode === "tui") offerSetup(ctx.ui, true);
|
|
655
569
|
return;
|
|
656
570
|
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
);
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
});
|
|
667
|
-
fileLog.log("acp-auth", r.ok ? { ok: true } : { ok: false, error: r.error }, r.ok ? "info" : "warn");
|
|
668
|
-
if (r.ok) {
|
|
669
|
-
ctx.ui.notify(
|
|
670
|
-
r.note ? `Signed in (${r.note}). The ACP engine is ready; restart applies it.` : "Signed in. The ACP engine is ready; restart applies it.",
|
|
671
|
-
"info",
|
|
672
|
-
);
|
|
673
|
-
} else ctx.ui.notify(`ACP sign-in failed (${r.error}).\nRun /agy auth to retry; /agy auth-manual has manual steps.`, "warning");
|
|
571
|
+
const rawCheck = await checkAgyCliVersion(binary);
|
|
572
|
+
const floor = selectedEngine === "acp" ? MIN_AGY_ACP_VERSION : MIN_AGY_VERSION;
|
|
573
|
+
const check = agyVersionVerdict(rawCheck.raw, floor);
|
|
574
|
+
if (check.status !== "unsupported") return;
|
|
575
|
+
agyVersionWarned = true;
|
|
576
|
+
const msg = `agy ${check.version} is too old for ${selectedEngine}; install ${floor} or newer.`;
|
|
577
|
+
if (ctx.mode === "tui") {
|
|
578
|
+
ctx.ui.notify(msg, "warning");
|
|
579
|
+
offerSetup(ctx.ui, explicit);
|
|
580
|
+
} else console.error(`[antigravity-bridge] ${msg}`);
|
|
674
581
|
} catch (err) {
|
|
675
|
-
|
|
676
|
-
fileLog.log("acp-setup", { error: String(err) }, "warn");
|
|
677
|
-
ctx.ui.notify(`ACP setup failed (${String(err)}). /agy auth retries; /agy doctor inspects.`, "warning");
|
|
582
|
+
fileLog.log("agy-version-check", { error: String(err) }, "warn");
|
|
678
583
|
}
|
|
679
584
|
}
|
|
680
585
|
|
|
@@ -693,6 +598,7 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
693
598
|
// (drivers wire at load). The await intentionally runs before the
|
|
694
599
|
// bridge startup below: on a genuine first run the modal blocks input
|
|
695
600
|
// anyway, so the delay is invisible.
|
|
601
|
+
let setupEngine = engine;
|
|
696
602
|
if (event.reason === "startup" && ctx.mode === "tui" && shouldOfferEnginePicker(CONFIG_PATH)) {
|
|
697
603
|
// Best-effort, like the legacy-patch notice below: a picker failure
|
|
698
604
|
// (mid-prompt TUI teardown, resize races) must never take down the
|
|
@@ -701,40 +607,31 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
701
607
|
try {
|
|
702
608
|
const picked = await showEnginePicker(ctx.ui);
|
|
703
609
|
if (picked) {
|
|
610
|
+
setupEngine = picked;
|
|
704
611
|
saveConfig({ engine: picked });
|
|
705
612
|
ctx.ui.notify(savedEngineMessage(picked), "info");
|
|
706
|
-
if (picked === "acp") void runAcpPickSetup(ctx);
|
|
707
613
|
}
|
|
708
614
|
} catch (err) {
|
|
709
615
|
fileLog.log("engine-picker", { error: String(err) }, "warn");
|
|
710
616
|
console.error(`[antigravity-bridge] engine picker failed: ${String(err)}`);
|
|
711
617
|
}
|
|
712
618
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
// /resume and /reload re-fires do not nag mid-session). Runs after the
|
|
717
|
-
// picker above, so a first-run stream-json pick warns immediately.
|
|
718
|
-
if (engine === "stream-json" && !agyMissingWarned && !isAgyInstalled(binary)) {
|
|
719
|
-
agyMissingWarned = true;
|
|
720
|
-
const msg = agyMissingMessage();
|
|
619
|
+
if (!legacyAcpWarningShown && legacyAcpBinConfigured()) {
|
|
620
|
+
legacyAcpWarningShown = true;
|
|
621
|
+
const msg = "Legacy AGY_ACP_BIN/acp.bin settings are ignored. Turns use `agy acp`; remove the stale override.";
|
|
721
622
|
if (ctx.hasUI) ctx.ui.notify(msg, "warning");
|
|
722
623
|
else console.error(`[antigravity-bridge] ${msg}`);
|
|
723
624
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
agyVersionWarned = true;
|
|
733
|
-
const msg = `agy ${check.version} is too old; install ${MIN_AGY_VERSION} or newer. Stream-json turns may fail in confusing ways until then (/agy doctor inspects).`;
|
|
734
|
-
if (ctx.hasUI) ctx.ui.notify(msg, "warning");
|
|
735
|
-
else console.error(`[antigravity-bridge] ${msg}`);
|
|
736
|
-
});
|
|
625
|
+
if (!managedAcpCleanupRan) {
|
|
626
|
+
managedAcpCleanupRan = true;
|
|
627
|
+
try {
|
|
628
|
+
const cleanup = cleanupManagedAcpInstall();
|
|
629
|
+
if (cleanup.removedBuilds.length > 0) fileLog.log("legacy-acp-cleanup", cleanup, "info");
|
|
630
|
+
} catch (err) {
|
|
631
|
+
fileLog.log("legacy-acp-cleanup", { error: String(err) }, "warn");
|
|
632
|
+
}
|
|
737
633
|
}
|
|
634
|
+
void checkAgySetup(ctx, setupEngine);
|
|
738
635
|
// Legacy cleanup: users who ran the old consent-gated patcher still
|
|
739
636
|
// carry pi.invokeTool in their installed pi. Inert, but tell them once
|
|
740
637
|
// and offer /agy patch-cleanup. Never auto-edits the install.
|
|
@@ -752,36 +649,6 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
752
649
|
} catch {
|
|
753
650
|
/* detection is best-effort */
|
|
754
651
|
}
|
|
755
|
-
// ACP self-heal: engine=acp needs a server binary + auth. Silent when
|
|
756
|
-
// everything is ready; installs from the registry and bootstraps auth
|
|
757
|
-
// otherwise; manual instructions only on failure. Fire-and-forget: it
|
|
758
|
-
// must not delay session start (and nothing spawns until the first turn).
|
|
759
|
-
if (engine === "acp" && !acpSelfHealRan) {
|
|
760
|
-
acpSelfHealRan = true;
|
|
761
|
-
void ensureAcpReady({ configBin: loadConfig().acp.bin }).then((status) => {
|
|
762
|
-
fileLog.log(
|
|
763
|
-
"acp-self-heal",
|
|
764
|
-
status.ok
|
|
765
|
-
? { ok: true, binarySource: status.binarySource, needsLogin: status.needsLogin }
|
|
766
|
-
: { ok: false, error: status.error },
|
|
767
|
-
status.ok ? "info" : "warn",
|
|
768
|
-
);
|
|
769
|
-
if (status.ok) {
|
|
770
|
-
if (status.binarySource === "installed" || status.binarySource === "existing") {
|
|
771
|
-
saveConfig({ acp: { ...loadConfig().acp, bin: status.bin } });
|
|
772
|
-
}
|
|
773
|
-
if (status.needsLogin) {
|
|
774
|
-
const msg = acpLoginPending();
|
|
775
|
-
if (ctx.hasUI) ctx.ui.notify(msg, "warning");
|
|
776
|
-
else console.error(`[antigravity-bridge] ${msg}`);
|
|
777
|
-
}
|
|
778
|
-
return;
|
|
779
|
-
}
|
|
780
|
-
const msg = `ACP auto-setup failed (${status.error}).\n${status.manual}`;
|
|
781
|
-
if (ctx.hasUI) ctx.ui.notify(msg, "warning");
|
|
782
|
-
else console.error(`[antigravity-bridge] ${msg}`);
|
|
783
|
-
});
|
|
784
|
-
}
|
|
785
652
|
// Bridge failure logger. Routine lifecycle (listening,
|
|
786
653
|
// bridge-config-written/removed, closed) is normal startup/teardown
|
|
787
654
|
// traffic: toasting it every session, or pinning it via stderr in
|
|
@@ -825,6 +692,10 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
825
692
|
if (ctx.hasUI) ctx.ui.notify(msg, "warning");
|
|
826
693
|
else console.error(msg);
|
|
827
694
|
};
|
|
695
|
+
// Sweep crashed sessions even when the bridge is disabled.
|
|
696
|
+
sweepStaleBridgeServers();
|
|
697
|
+
sweepStaleBridgeDirs();
|
|
698
|
+
sweepStaleAcpHomes();
|
|
828
699
|
// Start the bridge unless the user turned it off. No patch gate, no
|
|
829
700
|
// consent flow: calls route through pi's normal toolUse loop.
|
|
830
701
|
const bridgeMode: BridgeTools = loadConfig().bridgeTools;
|
|
@@ -927,14 +798,13 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
927
798
|
);
|
|
928
799
|
if (r.ok && r.handle) {
|
|
929
800
|
mcpHandle = r.handle;
|
|
930
|
-
//
|
|
931
|
-
//
|
|
801
|
+
// Entries a crashed delegation left suppressed are healed here,
|
|
802
|
+
// but only when no live delegation is in
|
|
932
803
|
// flight anywhere (marker-aware heal). A blind re-enable used to
|
|
933
804
|
// un-hide the bridge during another session's active delegation.
|
|
934
805
|
// Global-config hygiene runs on BOTH engines: a crashed stream-json
|
|
935
806
|
// session must not leave dead-pid entries or stuck suppression flags
|
|
936
807
|
// for the next one.
|
|
937
|
-
sweepStaleBridgeServers();
|
|
938
808
|
healBridgeSuppression();
|
|
939
809
|
// ACP supplies the bridge per-session (session/new | session/load);
|
|
940
810
|
// only the stream-json CLI reads the global MCP config.
|
|
@@ -1058,8 +928,6 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
1058
928
|
activeUi = null;
|
|
1059
929
|
const h = mcpHandle;
|
|
1060
930
|
mcpHandle = null;
|
|
1061
|
-
await h?.close();
|
|
1062
|
-
roundTrips.failAll(FAIL_REASON_SHUTDOWN);
|
|
1063
931
|
// "recycle", NOT "shutdown": pi fires session_shutdown on /new, /resume
|
|
1064
932
|
// and /fork (docs/extensions.md session lifecycle), not only on process
|
|
1065
933
|
// exit. The drivers are process-lifetime singletons; closing them with
|
|
@@ -1070,6 +938,8 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
1070
938
|
// ("quit") - the connection kill is identical and nothing runs after.
|
|
1071
939
|
await streamDriver.close("recycle", "session shutdown");
|
|
1072
940
|
await acpDriver.close("recycle", "session shutdown");
|
|
941
|
+
roundTrips.failAll(FAIL_REASON_SHUTDOWN);
|
|
942
|
+
await h?.close();
|
|
1073
943
|
if (engine === "stream-json") unregisterBridgeServer(process.pid);
|
|
1074
944
|
hiddenBridgeTools.clear();
|
|
1075
945
|
// Approval gate: unstage hooks and remove the per-pid script. Pending
|
|
@@ -1089,14 +959,6 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
1089
959
|
|
|
1090
960
|
// --- /agy command -----------------------------------------------------------
|
|
1091
961
|
|
|
1092
|
-
/** Shared body for every ACP-login-pending moment (session_start self-heal,
|
|
1093
|
-
* /agy engine acp). End-user simple: what to run, what happens, which
|
|
1094
|
-
* account. Sign-in is explicit (/agy auth); it never rides the first
|
|
1095
|
-
* message. */
|
|
1096
|
-
function acpLoginPending(): string {
|
|
1097
|
-
return `One-time sign-in needed to finish ACP setup. Run /agy auth: the Google sign-in opens in your browser. Use the Google account of your Antigravity subscription (the same account as your agy CLI login). If no browser opens, pi shows the sign-in URL to copy. The token stays on your machine; this extension never sees it.`;
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
962
|
interface AgyCommandCtx {
|
|
1101
963
|
entries: AgyModelEntry[];
|
|
1102
964
|
store: SessionStore;
|
|
@@ -1108,18 +970,11 @@ interface AgyCommandCtx {
|
|
|
1108
970
|
getMcpPort: () => number | null;
|
|
1109
971
|
bridgeTools: () => Array<{ name: string }>;
|
|
1110
972
|
hiddenBridgeTools: Set<string>;
|
|
1111
|
-
/** Shared ACP log sink (login URL surfacing + failure events). */
|
|
1112
|
-
acpLog: (msg: string, data?: unknown) => void;
|
|
1113
973
|
/** Daily file logger (src/daily-log.ts); command + doctor surfacing. */
|
|
1114
974
|
fileLog: DailyLogger;
|
|
1115
975
|
/** Live subagent roster (folded in the provider); /agy subagents. */
|
|
1116
976
|
roster: SubagentRoster;
|
|
1117
|
-
|
|
1118
|
-
* by /agy engine's no-args modal so both entry points behave alike. */
|
|
1119
|
-
runAcpPickSetup: (cmdCtx: { ui: ExtensionUIContext }) => Promise<void>;
|
|
1120
|
-
/** BROWSER-capture handles; null when unavailable (Windows, unwritable
|
|
1121
|
-
* data dir). /agy auth passes them to the sign-in process. */
|
|
1122
|
-
authCapture: { browserEnv: Record<string, string>; file: string } | null;
|
|
977
|
+
checkAgySetup: (ctx: { ui: ExtensionUIContext; mode: string }, engine: Engine, explicit?: boolean) => Promise<void>;
|
|
1123
978
|
}
|
|
1124
979
|
|
|
1125
980
|
interface PendingConfig {
|
|
@@ -1166,13 +1021,13 @@ function statusText(ctx: AgyCommandCtx): string {
|
|
|
1166
1021
|
const source = ctx.usingFallback ? "fallback (agy models failed)" : "discovered";
|
|
1167
1022
|
return [
|
|
1168
1023
|
"Antigravity bridge",
|
|
1169
|
-
` engine: ${config.engine}${config.engine === "stream-json" ? " (agy CLI fallback)" : " (
|
|
1024
|
+
` engine: ${config.engine}${config.engine === "stream-json" ? " (agy CLI fallback)" : " (agy acp)"}`,
|
|
1170
1025
|
` models: ${ctx.entries.length} ${source}`,
|
|
1171
1026
|
...settingsRows(ctx),
|
|
1172
1027
|
` sessions: ${ctx.store.size} bound`,
|
|
1173
1028
|
` config: ${CONFIG_PATH}`,
|
|
1174
1029
|
"",
|
|
1175
|
-
"Subcommands: /agy auth, /agy auth-manual, /agy engine stream-json|acp, /agy mode plan|accept-edits, /agy readonly on|off, /agy permissions on|off, /agy ask on|off, /agy model <alias>, /agy thinking low|medium|high, /agy agent <name|off>, /agy subagents, /agy quota, /agy artifacts [open <n|name>], /agy tasks [tail <id>], /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 timeout <1-1440|off>, /agy
|
|
1030
|
+
"Subcommands: /agy auth, /agy auth-manual, /agy engine stream-json|acp, /agy mode plan|accept-edits, /agy readonly on|off, /agy permissions on|off, /agy ask on|off, /agy model <alias>, /agy thinking low|medium|high, /agy agent <name|off>, /agy subagents, /agy quota, /agy artifacts [open <n|name>], /agy tasks [tail <id>], /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 timeout <1-1440|off>, /agy patch-cleanup, /agy clear, /agy doctor",
|
|
1176
1031
|
].join("\n");
|
|
1177
1032
|
}
|
|
1178
1033
|
|
|
@@ -1180,7 +1035,7 @@ function statusText(ctx: AgyCommandCtx): string {
|
|
|
1180
1035
|
function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
1181
1036
|
pi.registerCommand("agy", {
|
|
1182
1037
|
description:
|
|
1183
|
-
"Antigravity provider: doctor (health + settings), settings picker, clear sessions. Usage: /agy [doctor|auth|auth-manual|engine stream-json|acp|mode plan|accept-edits|readonly on|off|permissions on|off|ask on|off|model <alias>|thinking low|medium|high|agent <name|off>|subagents|quota|artifacts [open <n|name>]|tasks [tail <id>]|bridge all|mcp|none|tools [hide|show <name>|reset]|web on|off|digest on|off|system-prompt on|off|timeout <1-1440|off>|
|
|
1038
|
+
"Antigravity provider: doctor (health + settings), settings picker, clear sessions. Usage: /agy [doctor|auth|auth-manual|engine stream-json|acp|mode plan|accept-edits|readonly on|off|permissions on|off|ask on|off|model <alias>|thinking low|medium|high|agent <name|off>|subagents|quota|artifacts [open <n|name>]|tasks [tail <id>]|bridge all|mcp|none|tools [hide|show <name>|reset]|web on|off|digest on|off|system-prompt on|off|timeout <1-1440|off>|patch-cleanup|clear]",
|
|
1184
1039
|
handler: async (args, cmdCtx: ExtensionCommandContext) => {
|
|
1185
1040
|
const ui = cmdCtx.ui;
|
|
1186
1041
|
if (ui) activeUi = ui;
|
|
@@ -1245,7 +1100,7 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1245
1100
|
if (sub === "engine") {
|
|
1246
1101
|
if (val === "acp" || val === "stream-json") {
|
|
1247
1102
|
if (val === "acp" && loadConfig().mode === "plan") {
|
|
1248
|
-
ui?.notify("mode is plan;
|
|
1103
|
+
ui?.notify("mode is plan; provider plan turns are disabled on ACP. /agy mode accept-edits first.", "warning");
|
|
1249
1104
|
return;
|
|
1250
1105
|
}
|
|
1251
1106
|
const next = saveConfig({ engine: val });
|
|
@@ -1253,39 +1108,12 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1253
1108
|
ui?.notify("engine set to stream-json. Takes effect on the next pi start (or /reload).", "info");
|
|
1254
1109
|
return;
|
|
1255
1110
|
}
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
// Manual instructions only when a step fails.
|
|
1259
|
-
ui?.notify("engine set to acp. Preparing the server (binary + auth)…", "info");
|
|
1260
|
-
const status = await ensureAcpReady({
|
|
1261
|
-
configBin: loadConfig().acp.bin,
|
|
1262
|
-
onProgress: (m) => ui?.notify(m, "info"),
|
|
1263
|
-
});
|
|
1264
|
-
ctx.fileLog.log(
|
|
1265
|
-
"acp-setup",
|
|
1266
|
-
status.ok
|
|
1267
|
-
? { ok: true, binarySource: status.binarySource, needsLogin: status.needsLogin }
|
|
1268
|
-
: { ok: false, error: status.error },
|
|
1269
|
-
status.ok ? "info" : "warn",
|
|
1270
|
-
);
|
|
1271
|
-
if (!status.ok) {
|
|
1272
|
-
ui?.notify(`ACP auto-setup failed (${status.error}).\n${status.manual}`, "warning");
|
|
1273
|
-
return;
|
|
1274
|
-
}
|
|
1275
|
-
saveConfig({ acp: { ...loadConfig().acp, bin: status.bin } });
|
|
1276
|
-
if (status.needsLogin) {
|
|
1277
|
-
ui?.notify(
|
|
1278
|
-
`ACP engine set. ${acpLoginPending()}`,
|
|
1279
|
-
"warning",
|
|
1280
|
-
);
|
|
1281
|
-
} else {
|
|
1282
|
-
ui?.notify(`ACP engine ready (auth: ${status.auth}). Takes effect on the next pi start (or /reload).`, "info");
|
|
1283
|
-
}
|
|
1111
|
+
ui?.notify("engine set to acp. Turns will use `agy acp` and the CLI's existing authentication. Takes effect on the next pi start (or /reload).", "info");
|
|
1112
|
+
void ctx.checkAgySetup(cmdCtx, "acp", true);
|
|
1284
1113
|
} else if (!val && mode === "tui" && ui) {
|
|
1285
1114
|
// Same modal as the first-run wizard: switching engines deserves
|
|
1286
1115
|
// the explanations, not a bare usage line. Semantics match the
|
|
1287
|
-
// direct path above: plan blocks acp
|
|
1288
|
-
// + sign-in immediately, restart applies the switch.
|
|
1116
|
+
// direct path above: plan blocks acp and restart applies the switch.
|
|
1289
1117
|
await runEngineModal(ui, ctx);
|
|
1290
1118
|
} else {
|
|
1291
1119
|
ui?.notify(`current engine: ${loadConfig().engine}\nusage: /agy engine stream-json|acp`, "info");
|
|
@@ -1293,99 +1121,20 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1293
1121
|
return;
|
|
1294
1122
|
}
|
|
1295
1123
|
if (sub === "auth") {
|
|
1296
|
-
|
|
1297
|
-
// /agy engine acp followed by /agy auth in the same session works,
|
|
1298
|
-
// no restart needed before signing in.
|
|
1299
|
-
if (loadConfig().engine !== "acp") {
|
|
1300
|
-
ui?.notify(`the selected engine is ${loadConfig().engine}. /agy engine acp first, then /agy auth.`, "warning");
|
|
1301
|
-
return;
|
|
1302
|
-
}
|
|
1303
|
-
ui?.notify("Preparing the ACP server (binary + auth settings)…", "info");
|
|
1304
|
-
const status = await ensureAcpReady({ configBin: loadConfig().acp.bin, onProgress: (m) => ui?.notify(m, "info") });
|
|
1305
|
-
ctx.fileLog.log(
|
|
1306
|
-
"acp-setup",
|
|
1307
|
-
status.ok
|
|
1308
|
-
? { ok: true, binarySource: status.binarySource, needsLogin: status.needsLogin }
|
|
1309
|
-
: { ok: false, error: status.error },
|
|
1310
|
-
status.ok ? "info" : "warn",
|
|
1311
|
-
);
|
|
1312
|
-
if (!status.ok) {
|
|
1313
|
-
ui?.notify(`ACP auto-setup failed (${status.error}).\n${status.manual}`, "warning");
|
|
1314
|
-
return;
|
|
1315
|
-
}
|
|
1316
|
-
saveConfig({ acp: { ...loadConfig().acp, bin: status.bin } });
|
|
1317
|
-
if (!status.needsLogin) {
|
|
1318
|
-
ui?.notify(`Already signed in (auth: ${status.auth}). Nothing to do.`, "info");
|
|
1319
|
-
return;
|
|
1320
|
-
}
|
|
1321
|
-
ui?.notify(
|
|
1322
|
-
"Signing in: the Google sign-in opens in your browser and completes when you finish it (minutes-scale). If no browser opens, pi shows the sign-in URL to copy.",
|
|
1323
|
-
"info",
|
|
1324
|
-
);
|
|
1325
|
-
const r = await runAcpAuth({
|
|
1326
|
-
bin: status.bin,
|
|
1327
|
-
binArgs: status.args,
|
|
1328
|
-
extraEnv: ctx.authCapture?.browserEnv,
|
|
1329
|
-
authUrlFile: ctx.authCapture?.file,
|
|
1330
|
-
log: ctx.acpLog,
|
|
1331
|
-
});
|
|
1332
|
-
ctx.fileLog.log("acp-auth", r.ok ? { ok: true } : { ok: false, error: r.error }, r.ok ? "info" : "warn");
|
|
1333
|
-
if (r.ok) {
|
|
1334
|
-
ui?.notify(
|
|
1335
|
-
r.note ? `Signed in (${r.note}). The ACP engine is ready; takes effect on the next pi start (or /reload).` : "Signed in. The ACP engine is ready; takes effect on the next pi start (or /reload).",
|
|
1336
|
-
"info",
|
|
1337
|
-
);
|
|
1338
|
-
} else {
|
|
1339
|
-
ui?.notify(`ACP sign-in failed (${r.error}).\nRun /agy auth to retry; /agy auth-manual has manual steps.`, "warning");
|
|
1340
|
-
}
|
|
1341
|
-
return;
|
|
1342
|
-
}
|
|
1343
|
-
if (sub === "acp-bin") {
|
|
1344
|
-
const rest = (args ?? "").trim().split(/\s+/).slice(1).join(" ");
|
|
1345
|
-
if (rest.length > 0) {
|
|
1346
|
-
// Only the keyword compares case-insensitively; the path keeps its case.
|
|
1347
|
-
const bin = rest.toLowerCase() === "auto" ? "" : rest.replace(/^~(?=\/|$)/, os.homedir());
|
|
1348
|
-
// Spread, not a bare acp patch: a bare {bin} object
|
|
1349
|
-
// would drop sibling keys (usageEstimate) from the file.
|
|
1350
|
-
saveConfig({ acp: { ...loadConfig().acp, bin } });
|
|
1351
|
-
ui?.notify(
|
|
1352
|
-
bin
|
|
1353
|
-
? `acp.bin set to ${bin}. The next ACP turn (re)connects with it.`
|
|
1354
|
-
: "acp.bin cleared. Auto-setup (or AGY_ACP_BIN) picks the binary on the next ACP turn.",
|
|
1355
|
-
"info",
|
|
1356
|
-
);
|
|
1357
|
-
} else {
|
|
1358
|
-
const cur = loadConfig().acp.bin;
|
|
1359
|
-
ui?.notify(`acp.bin: ${cur || "(auto: setup installs, or AGY_ACP_BIN)"}\nusage: /agy acp-bin <path|auto>`, "info");
|
|
1360
|
-
}
|
|
1124
|
+
ui?.notify(agyAuthenticationMessage(), "info");
|
|
1361
1125
|
return;
|
|
1362
1126
|
}
|
|
1363
1127
|
if (sub === "auth-manual") {
|
|
1364
1128
|
ui?.notify(
|
|
1365
1129
|
[
|
|
1366
|
-
"
|
|
1367
|
-
"/agy engine acp and session start set this up for you):",
|
|
1130
|
+
"Both engines use the agy CLI and its cached Google credentials.",
|
|
1368
1131
|
"",
|
|
1369
|
-
"
|
|
1370
|
-
"
|
|
1371
|
-
"
|
|
1372
|
-
"
|
|
1373
|
-
"your machine, like any Google tool. This extension never sees your",
|
|
1374
|
-
"credentials.",
|
|
1132
|
+
"Install on Linux or macOS:",
|
|
1133
|
+
" curl -fsSL https://antigravity.google/cli/install.sh | bash",
|
|
1134
|
+
"Install on Windows PowerShell:",
|
|
1135
|
+
" irm https://antigravity.google/cli/install.ps1 | iex",
|
|
1375
1136
|
"",
|
|
1376
|
-
|
|
1377
|
-
" the antigravity-acp registry; point acp.bin or AGY_ACP_BIN at it.",
|
|
1378
|
-
'2. Default: put {"auth":{"type":"oauth-personal"}} in',
|
|
1379
|
-
" ~/.gemini/antigravity-acp/settings.json, run /agy auth, and complete",
|
|
1380
|
-
" the Google login that opens in your browser. No browser (SSH",
|
|
1381
|
-
" session)? pi shows the sign-in URL to copy; forward the redirect",
|
|
1382
|
-
" port over ssh (ssh -N -L <port>:127.0.0.1:<port> <user@host>), then",
|
|
1383
|
-
" open the URL on your machine.",
|
|
1384
|
-
' Headless alternative: GEMINI_API_KEY + {"auth":{"type":"gemini-api-key"}}',
|
|
1385
|
-
" (metered paid API - not your Antigravity plan). The key is used only",
|
|
1386
|
-
" when that type is selected; with the default oauth-personal in place,",
|
|
1387
|
-
" an exported key is ignored.",
|
|
1388
|
-
"3. /agy doctor shows the server version when auth is OK.",
|
|
1137
|
+
agyAuthenticationMessage(),
|
|
1389
1138
|
].join("\n"),
|
|
1390
1139
|
"info",
|
|
1391
1140
|
);
|
|
@@ -1396,15 +1145,11 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1396
1145
|
const engine = ctx.engine;
|
|
1397
1146
|
const snap = (engine === "acp" ? ctx.acpDriver : ctx.driver).snapshot();
|
|
1398
1147
|
const port = ctx.getMcpPort();
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
const cliSuffix =
|
|
1405
|
-
cliCheck.status === "unavailable" && engine === "acp"
|
|
1406
|
-
? " (optional here; AskAntigravity delegations use the CLI)"
|
|
1407
|
-
: "";
|
|
1148
|
+
const rawCliCheck = await checkAgyCliVersion(resolveAgyBinary());
|
|
1149
|
+
const cliFloor = engine === "acp" ? MIN_AGY_ACP_VERSION : MIN_AGY_VERSION;
|
|
1150
|
+
const cliCheck = rawCliCheck.status === "ok" || rawCliCheck.status === "unsupported"
|
|
1151
|
+
? agyVersionVerdict(rawCliCheck.raw, cliFloor)
|
|
1152
|
+
: rawCliCheck;
|
|
1408
1153
|
const lines = [
|
|
1409
1154
|
"Antigravity doctor (no tokens spent)",
|
|
1410
1155
|
` engine: ${engine}`,
|
|
@@ -1413,7 +1158,7 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1413
1158
|
` 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})` : ""}`,
|
|
1414
1159
|
` sessions: ${ctx.store.size} bound`,
|
|
1415
1160
|
` models: ${ctx.entries.length} ${ctx.usingFallback ? "FALLBACK (agy models failed)" : "discovered"}`,
|
|
1416
|
-
` agy cli: ${describeAgyVersionCheck(cliCheck)} (min ${
|
|
1161
|
+
` agy cli: ${describeAgyVersionCheck(cliCheck)} (min ${cliFloor})`,
|
|
1417
1162
|
` config: ${CONFIG_PATH}`,
|
|
1418
1163
|
` logs: ${logsDir()} (attach recent days' files when reporting issues; set AGY_DEBUG=1 to capture details)`,
|
|
1419
1164
|
];
|
|
@@ -1421,12 +1166,12 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1421
1166
|
lines.push(
|
|
1422
1167
|
` acp session: ${snap.acp.sessionId ?? "(none)"}`,
|
|
1423
1168
|
` acp server: ${snap.acp.serverVersion ?? "unknown"}${snap.acp.agentTitle ? ` (${snap.acp.agentTitle})` : ""}`,
|
|
1424
|
-
` acp stats: prompts=${snap.acp.prompts} created=${snap.acp.sessionsCreated} loaded=${snap.acp.sessionsLoaded} kills=${snap.acp.kills} reconnects=${snap.acp.reconnects}
|
|
1169
|
+
` acp stats: prompts=${snap.acp.prompts} created=${snap.acp.sessionsCreated} loaded=${snap.acp.sessionsLoaded} kills=${snap.acp.kills} reconnects=${snap.acp.reconnects}`,
|
|
1425
1170
|
);
|
|
1426
1171
|
// Gate B watch: silent while the server offers no token counts; one
|
|
1427
1172
|
// line the day it starts (then real usage mapping is worth wiring).
|
|
1428
1173
|
if (snap.acp.usageSeen) {
|
|
1429
|
-
lines.push(" acp tokens:
|
|
1174
|
+
lines.push(" acp tokens: NATIVE exact usage in PromptResponse");
|
|
1430
1175
|
} else if (config.acp.usageEstimate !== "off") {
|
|
1431
1176
|
lines.push(` acp tokens: ESTIMATED client-side (mode: ${config.acp.usageEstimate}; auto-off once the server sends real usage)`);
|
|
1432
1177
|
}
|
|
@@ -1435,13 +1180,9 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1435
1180
|
lines.push(" lifecycle (last 5):");
|
|
1436
1181
|
for (const entry of snap.lifecycle.slice(-5)) lines.push(` ${entry}`);
|
|
1437
1182
|
}
|
|
1438
|
-
if (engine === "acp")
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
` acp binary: ${setup.bin ?? "not found (auto-setup offers install)"}${setup.source ? ` (${setup.source})` : ""}`,
|
|
1442
|
-
` acp auth: ${setup.auth ?? "not configured (auto-setup bootstraps)"}`,
|
|
1443
|
-
);
|
|
1444
|
-
}
|
|
1183
|
+
if (engine === "acp") lines.push(" acp transport: agy acp (shared CLI authentication, isolated MCP home)");
|
|
1184
|
+
const retiredAuth = path.join(os.homedir(), ".gemini", "antigravity-acp");
|
|
1185
|
+
if (fs.existsSync(retiredAuth)) lines.push(` retired auth: ${retiredAuth} is unused; remove it manually when no longer needed`);
|
|
1445
1186
|
// The config surface lives here too, so doctor is the ONE status
|
|
1446
1187
|
// surface (former /agy status is gone).
|
|
1447
1188
|
lines.push("", "settings:", ...settingsRows(ctx));
|
|
@@ -1451,7 +1192,7 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1451
1192
|
if (sub === "mode") {
|
|
1452
1193
|
if (val === "plan" || val === "accept-edits") {
|
|
1453
1194
|
if (val === "plan" && ctx.engine === "acp") {
|
|
1454
|
-
ui?.notify("plan cannot be enforced on provider turns
|
|
1195
|
+
ui?.notify("plan cannot be enforced on provider turns: the CLI does not gate writes under plan (google-antigravity/antigravity-cli#1181), and provider plan turns are disabled on ACP. /agy mode accept-edits.", "warning");
|
|
1455
1196
|
return;
|
|
1456
1197
|
}
|
|
1457
1198
|
const next = saveConfig({ mode: val as AgyMode });
|
|
@@ -1678,10 +1419,10 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1678
1419
|
return;
|
|
1679
1420
|
}
|
|
1680
1421
|
if (sub === "agent") {
|
|
1681
|
-
// ACP has no agent slot in the protocol
|
|
1422
|
+
// ACP has no per-turn agent slot in the protocol: refuse instead of
|
|
1682
1423
|
// silently ignoring a configured agent.
|
|
1683
1424
|
if (ctx.engine === "acp") {
|
|
1684
|
-
ui?.notify("the ACP engine has no agent selection
|
|
1425
|
+
ui?.notify("the ACP engine has no per-turn agent selection. /agy engine stream-json first.", "warning");
|
|
1685
1426
|
return;
|
|
1686
1427
|
}
|
|
1687
1428
|
const name = (args ?? "").trim().split(/\s+/).slice(1).join(" ").trim();
|
|
@@ -2006,11 +1747,11 @@ async function openAgyPicker(ui: ExtensionUIContext, ctx: AgyCommandCtx): Promis
|
|
|
2006
1747
|
// mode row can still produce plan while the engine is acp - either the
|
|
2007
1748
|
// session-latched engine or the on-disk one, which the Switch engine row
|
|
2008
1749
|
// can change mid-session without a restart. Plan cannot be enforced on
|
|
2009
|
-
// provider turns on either engine
|
|
1750
|
+
// provider turns on either engine; refuse the combination.
|
|
2010
1751
|
const nextMode = pending.mode ?? config.mode;
|
|
2011
1752
|
if (nextMode === "plan" && (ctx.engine === "acp" || config.engine === "acp")) {
|
|
2012
1753
|
ui.notify(
|
|
2013
|
-
"plan cannot be enforced on provider turns
|
|
1754
|
+
"plan cannot be enforced on provider turns (google-antigravity/antigravity-cli#1181); provider plan turns are disabled on ACP too. /agy mode accept-edits.",
|
|
2014
1755
|
"warning",
|
|
2015
1756
|
);
|
|
2016
1757
|
return;
|
|
@@ -2048,8 +1789,7 @@ async function openAgyPicker(ui: ExtensionUIContext, ctx: AgyCommandCtx): Promis
|
|
|
2048
1789
|
|
|
2049
1790
|
/** The engine-pick modal shared by /agy engine (no args) and the settings
|
|
2050
1791
|
* picker's engine row: the same explanations as the first-run wizard and the
|
|
2051
|
-
* same semantics
|
|
2052
|
-
* immediately, restart applies the switch. */
|
|
1792
|
+
* same semantics: plan blocks acp and restart applies the switch. */
|
|
2053
1793
|
async function runEngineModal(ui: ExtensionUIContext, ctx: AgyCommandCtx): Promise<void> {
|
|
2054
1794
|
const current = loadConfig().engine;
|
|
2055
1795
|
const picked = await showEnginePicker(ui);
|
|
@@ -2062,10 +1802,10 @@ async function runEngineModal(ui: ExtensionUIContext, ctx: AgyCommandCtx): Promi
|
|
|
2062
1802
|
return;
|
|
2063
1803
|
}
|
|
2064
1804
|
if (picked === "acp" && loadConfig().mode === "plan") {
|
|
2065
|
-
ui.notify("mode is plan;
|
|
1805
|
+
ui.notify("mode is plan; provider plan turns are disabled on ACP. /agy mode accept-edits first.", "warning");
|
|
2066
1806
|
return;
|
|
2067
1807
|
}
|
|
2068
1808
|
saveConfig({ engine: picked });
|
|
2069
1809
|
ui.notify(savedEngineMessage(picked), "info");
|
|
2070
|
-
if (picked === "acp") void ctx.
|
|
1810
|
+
if (picked === "acp") void ctx.checkAgySetup({ ui, mode: "tui" }, "acp", true);
|
|
2071
1811
|
}
|