@estebanforge/pi-antigravity-bridge 1.9.3 → 1.10.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 +27 -0
- package/README.md +12 -14
- package/extensions/index.ts +137 -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 +98 -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 +43 -33
- package/src/driver-types.ts +3 -5
- package/src/engine-picker.ts +44 -25
- package/src/mcp-registration.ts +17 -7
- 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, loadConfig, logsDir, MAX_TURN_CAP_MIN, parseCapMinutes, removeLegacyAcpBin, 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,16 @@ 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
|
-
|
|
218
|
-
|
|
207
|
+
let managedAcpCleanupRan = false;
|
|
208
|
+
let legacyConfigCleanupRan = false;
|
|
209
|
+
let legacyConfigCleanupFailed = false;
|
|
210
|
+
let legacyEnvAcpWarningShown = false;
|
|
211
|
+
let legacyFileAcpWarningShown = false;
|
|
212
|
+
let agySetupOffer: (() => Promise<void>) | null = null;
|
|
219
213
|
let agyMissingWarned = false;
|
|
220
214
|
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
|
|
215
|
+
// Two turn engines behind one contract: `agy acp` (default for new
|
|
216
|
+
// installs) and the stream-json `agy` CLI
|
|
234
217
|
// (fallback). Neither spawns anything until its first turn.
|
|
235
218
|
// ACP log routing: only genuine failures reach stderr. Routine lifecycle
|
|
236
219
|
// (driver-created, spawn, session-new, ...) stays in the driver's
|
|
@@ -260,63 +243,17 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
260
243
|
const level = failed ? "warn" : msg === "turn-start" ? "info" : "debug";
|
|
261
244
|
fileLog.log(msg, data, level);
|
|
262
245
|
};
|
|
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
246
|
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
247
|
const expectedExit =
|
|
279
248
|
msg === "connection-exited" &&
|
|
280
249
|
(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.
|
|
250
|
+
const level = expectedExit ? "debug" : acpFailures.has(msg) ? "warn" : msg === "turn-start" ? "info" : "debug";
|
|
251
|
+
fileLog.log(msg, data, level);
|
|
311
252
|
};
|
|
312
253
|
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 }),
|
|
254
|
+
bin: binary,
|
|
255
|
+
binArgs: ["acp"],
|
|
256
|
+
isolateHome: true,
|
|
320
257
|
// Resolved per turn: /agy and AGY_USAGE_ESTIMATE changes apply without
|
|
321
258
|
// a restart. Without this the driver defaults to "estimate" and the
|
|
322
259
|
// config knob (incl. "off") is dead.
|
|
@@ -338,7 +275,6 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
338
275
|
return idx >= 0 ? (req.options[idx]?.optionId ?? undefined) : undefined;
|
|
339
276
|
});
|
|
340
277
|
},
|
|
341
|
-
...(authCapture ? { extraEnv: authCapture.browserEnv, authUrlFile: authCapture.file } : {}),
|
|
342
278
|
log: acpLog,
|
|
343
279
|
mcpServers: () => {
|
|
344
280
|
const handle = mcpHandle;
|
|
@@ -445,7 +381,7 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
445
381
|
return;
|
|
446
382
|
}
|
|
447
383
|
closePollRun();
|
|
448
|
-
if (event.mcpServer
|
|
384
|
+
if (/^pi-bridge(?:-\d+)?$/.test(event.mcpServer ?? "")) return;
|
|
449
385
|
// MCP calls through our own pi-bridge already have genuine Pi tool cards;
|
|
450
386
|
// only native Antigravity / other MCP events need display-only entries.
|
|
451
387
|
if (event.status === "started") {
|
|
@@ -567,11 +503,9 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
567
503
|
getMcpPort: () => mcpHandle?.port ?? null,
|
|
568
504
|
bridgeTools: getBridgeTools,
|
|
569
505
|
hiddenBridgeTools,
|
|
570
|
-
acpLog,
|
|
571
506
|
fileLog,
|
|
572
507
|
roster,
|
|
573
|
-
|
|
574
|
-
runAcpPickSetup,
|
|
508
|
+
checkAgySetup,
|
|
575
509
|
});
|
|
576
510
|
|
|
577
511
|
// AskAntigravity tool: one-shot delegation to agy (ported from
|
|
@@ -607,74 +541,48 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
607
541
|
},
|
|
608
542
|
});
|
|
609
543
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
544
|
+
function offerSetup(ui: ExtensionUIContext, explicit = false): void {
|
|
545
|
+
if (agySetupOffer && explicit) {
|
|
546
|
+
ui.notify(`CLI setup was already offered in this session. ${agyMissingMessage()}`, "info");
|
|
547
|
+
}
|
|
548
|
+
agySetupOffer ??= createAgySetupOffer({
|
|
549
|
+
ui,
|
|
550
|
+
binaryOverride: process.env.AGY_BIN || undefined,
|
|
551
|
+
missingMessage: agyMissingMessage,
|
|
552
|
+
log: (event, data, level) => fileLog.log(event, data, level),
|
|
553
|
+
});
|
|
554
|
+
void agySetupOffer();
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Availability checks never block bridge startup. The setup offer owns
|
|
558
|
+
// prompting, deduplication, and failure containment.
|
|
559
|
+
async function checkAgySetup(ctx: { ui: ExtensionUIContext; mode: string }, selectedEngine: Engine, explicit = false): Promise<void> {
|
|
622
560
|
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");
|
|
561
|
+
if (!isAgyInstalled(binary)) {
|
|
562
|
+
if (ctx.mode === "tui") {
|
|
563
|
+
offerSetup(ctx.ui, explicit);
|
|
564
|
+
} else if (!agyMissingWarned) {
|
|
565
|
+
agyMissingWarned = true;
|
|
566
|
+
console.error(`[antigravity-bridge] ${agyMissingMessage()}`);
|
|
567
|
+
}
|
|
648
568
|
return;
|
|
649
569
|
}
|
|
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");
|
|
570
|
+
if (agyVersionWarned) {
|
|
571
|
+
if (explicit && ctx.mode === "tui") offerSetup(ctx.ui, true);
|
|
655
572
|
return;
|
|
656
573
|
}
|
|
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");
|
|
574
|
+
const rawCheck = await checkAgyCliVersion(binary);
|
|
575
|
+
const floor = selectedEngine === "acp" ? MIN_AGY_ACP_VERSION : MIN_AGY_VERSION;
|
|
576
|
+
const check = agyVersionVerdict(rawCheck.raw, floor);
|
|
577
|
+
if (check.status !== "unsupported") return;
|
|
578
|
+
agyVersionWarned = true;
|
|
579
|
+
const msg = `agy ${check.version} is too old for ${selectedEngine}; install ${floor} or newer.`;
|
|
580
|
+
if (ctx.mode === "tui") {
|
|
581
|
+
ctx.ui.notify(msg, "warning");
|
|
582
|
+
offerSetup(ctx.ui, explicit);
|
|
583
|
+
} else console.error(`[antigravity-bridge] ${msg}`);
|
|
674
584
|
} 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");
|
|
585
|
+
fileLog.log("agy-version-check", { error: String(err) }, "warn");
|
|
678
586
|
}
|
|
679
587
|
}
|
|
680
588
|
|
|
@@ -693,6 +601,7 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
693
601
|
// (drivers wire at load). The await intentionally runs before the
|
|
694
602
|
// bridge startup below: on a genuine first run the modal blocks input
|
|
695
603
|
// anyway, so the delay is invisible.
|
|
604
|
+
let setupEngine = engine;
|
|
696
605
|
if (event.reason === "startup" && ctx.mode === "tui" && shouldOfferEnginePicker(CONFIG_PATH)) {
|
|
697
606
|
// Best-effort, like the legacy-patch notice below: a picker failure
|
|
698
607
|
// (mid-prompt TUI teardown, resize races) must never take down the
|
|
@@ -701,40 +610,50 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
701
610
|
try {
|
|
702
611
|
const picked = await showEnginePicker(ctx.ui);
|
|
703
612
|
if (picked) {
|
|
613
|
+
setupEngine = picked;
|
|
704
614
|
saveConfig({ engine: picked });
|
|
705
615
|
ctx.ui.notify(savedEngineMessage(picked), "info");
|
|
706
|
-
if (picked === "acp") void runAcpPickSetup(ctx);
|
|
707
616
|
}
|
|
708
617
|
} catch (err) {
|
|
709
618
|
fileLog.log("engine-picker", { error: String(err) }, "warn");
|
|
710
619
|
console.error(`[antigravity-bridge] engine picker failed: ${String(err)}`);
|
|
711
620
|
}
|
|
712
621
|
}
|
|
713
|
-
//
|
|
714
|
-
//
|
|
715
|
-
//
|
|
716
|
-
//
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
622
|
+
// The retired acp.bin key heals itself in the config file; the env var
|
|
623
|
+
// lives outside our reach, so it is the only case that still warns.
|
|
624
|
+
// Independent once-flags: a failed cleanup must never suppress the
|
|
625
|
+
// env-var warning (or vice versa) for the rest of the process.
|
|
626
|
+
if (!legacyConfigCleanupRan) {
|
|
627
|
+
legacyConfigCleanupRan = true;
|
|
628
|
+
try {
|
|
629
|
+
removeLegacyAcpBin();
|
|
630
|
+
} catch (err) {
|
|
631
|
+
legacyConfigCleanupFailed = true;
|
|
632
|
+
fileLog.log("legacy-acp-config-cleanup", { error: String(err) }, "error");
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
if (!legacyEnvAcpWarningShown && process.env.AGY_ACP_BIN?.trim()) {
|
|
636
|
+
legacyEnvAcpWarningShown = true;
|
|
637
|
+
const msg = "AGY_ACP_BIN is set but no longer used; turns run \"agy acp\" from the CLI. Remove it from your shell config, then start a new terminal.";
|
|
721
638
|
if (ctx.hasUI) ctx.ui.notify(msg, "warning");
|
|
722
639
|
else console.error(`[antigravity-bridge] ${msg}`);
|
|
723
640
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
const
|
|
734
|
-
if (
|
|
735
|
-
|
|
736
|
-
|
|
641
|
+
if (!legacyFileAcpWarningShown && legacyConfigCleanupFailed) {
|
|
642
|
+
legacyFileAcpWarningShown = true;
|
|
643
|
+
const msg = `Couldn't remove the old "bin" setting from ${CONFIG_PATH} (details in the bridge log). Delete the "bin" line under "acp" by hand; turns run "agy acp" from the CLI.`;
|
|
644
|
+
if (ctx.hasUI) ctx.ui.notify(msg, "warning");
|
|
645
|
+
else console.error(`[antigravity-bridge] ${msg}`);
|
|
646
|
+
}
|
|
647
|
+
if (!managedAcpCleanupRan) {
|
|
648
|
+
managedAcpCleanupRan = true;
|
|
649
|
+
try {
|
|
650
|
+
const cleanup = cleanupManagedAcpInstall();
|
|
651
|
+
if (cleanup.removedBuilds.length > 0) fileLog.log("legacy-acp-cleanup", cleanup, "info");
|
|
652
|
+
} catch (err) {
|
|
653
|
+
fileLog.log("legacy-acp-cleanup", { error: String(err) }, "warn");
|
|
654
|
+
}
|
|
737
655
|
}
|
|
656
|
+
void checkAgySetup(ctx, setupEngine);
|
|
738
657
|
// Legacy cleanup: users who ran the old consent-gated patcher still
|
|
739
658
|
// carry pi.invokeTool in their installed pi. Inert, but tell them once
|
|
740
659
|
// and offer /agy patch-cleanup. Never auto-edits the install.
|
|
@@ -752,36 +671,6 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
752
671
|
} catch {
|
|
753
672
|
/* detection is best-effort */
|
|
754
673
|
}
|
|
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
674
|
// Bridge failure logger. Routine lifecycle (listening,
|
|
786
675
|
// bridge-config-written/removed, closed) is normal startup/teardown
|
|
787
676
|
// traffic: toasting it every session, or pinning it via stderr in
|
|
@@ -825,6 +714,10 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
825
714
|
if (ctx.hasUI) ctx.ui.notify(msg, "warning");
|
|
826
715
|
else console.error(msg);
|
|
827
716
|
};
|
|
717
|
+
// Sweep crashed sessions even when the bridge is disabled.
|
|
718
|
+
sweepStaleBridgeServers();
|
|
719
|
+
sweepStaleBridgeDirs();
|
|
720
|
+
sweepStaleAcpHomes();
|
|
828
721
|
// Start the bridge unless the user turned it off. No patch gate, no
|
|
829
722
|
// consent flow: calls route through pi's normal toolUse loop.
|
|
830
723
|
const bridgeMode: BridgeTools = loadConfig().bridgeTools;
|
|
@@ -927,14 +820,13 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
927
820
|
);
|
|
928
821
|
if (r.ok && r.handle) {
|
|
929
822
|
mcpHandle = r.handle;
|
|
930
|
-
//
|
|
931
|
-
//
|
|
823
|
+
// Entries a crashed delegation left suppressed are healed here,
|
|
824
|
+
// but only when no live delegation is in
|
|
932
825
|
// flight anywhere (marker-aware heal). A blind re-enable used to
|
|
933
826
|
// un-hide the bridge during another session's active delegation.
|
|
934
827
|
// Global-config hygiene runs on BOTH engines: a crashed stream-json
|
|
935
828
|
// session must not leave dead-pid entries or stuck suppression flags
|
|
936
829
|
// for the next one.
|
|
937
|
-
sweepStaleBridgeServers();
|
|
938
830
|
healBridgeSuppression();
|
|
939
831
|
// ACP supplies the bridge per-session (session/new | session/load);
|
|
940
832
|
// only the stream-json CLI reads the global MCP config.
|
|
@@ -1058,8 +950,6 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
1058
950
|
activeUi = null;
|
|
1059
951
|
const h = mcpHandle;
|
|
1060
952
|
mcpHandle = null;
|
|
1061
|
-
await h?.close();
|
|
1062
|
-
roundTrips.failAll(FAIL_REASON_SHUTDOWN);
|
|
1063
953
|
// "recycle", NOT "shutdown": pi fires session_shutdown on /new, /resume
|
|
1064
954
|
// and /fork (docs/extensions.md session lifecycle), not only on process
|
|
1065
955
|
// exit. The drivers are process-lifetime singletons; closing them with
|
|
@@ -1070,6 +960,8 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
1070
960
|
// ("quit") - the connection kill is identical and nothing runs after.
|
|
1071
961
|
await streamDriver.close("recycle", "session shutdown");
|
|
1072
962
|
await acpDriver.close("recycle", "session shutdown");
|
|
963
|
+
roundTrips.failAll(FAIL_REASON_SHUTDOWN);
|
|
964
|
+
await h?.close();
|
|
1073
965
|
if (engine === "stream-json") unregisterBridgeServer(process.pid);
|
|
1074
966
|
hiddenBridgeTools.clear();
|
|
1075
967
|
// Approval gate: unstage hooks and remove the per-pid script. Pending
|
|
@@ -1089,14 +981,6 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
1089
981
|
|
|
1090
982
|
// --- /agy command -----------------------------------------------------------
|
|
1091
983
|
|
|
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
984
|
interface AgyCommandCtx {
|
|
1101
985
|
entries: AgyModelEntry[];
|
|
1102
986
|
store: SessionStore;
|
|
@@ -1108,18 +992,11 @@ interface AgyCommandCtx {
|
|
|
1108
992
|
getMcpPort: () => number | null;
|
|
1109
993
|
bridgeTools: () => Array<{ name: string }>;
|
|
1110
994
|
hiddenBridgeTools: Set<string>;
|
|
1111
|
-
/** Shared ACP log sink (login URL surfacing + failure events). */
|
|
1112
|
-
acpLog: (msg: string, data?: unknown) => void;
|
|
1113
995
|
/** Daily file logger (src/daily-log.ts); command + doctor surfacing. */
|
|
1114
996
|
fileLog: DailyLogger;
|
|
1115
997
|
/** Live subagent roster (folded in the provider); /agy subagents. */
|
|
1116
998
|
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;
|
|
999
|
+
checkAgySetup: (ctx: { ui: ExtensionUIContext; mode: string }, engine: Engine, explicit?: boolean) => Promise<void>;
|
|
1123
1000
|
}
|
|
1124
1001
|
|
|
1125
1002
|
interface PendingConfig {
|
|
@@ -1166,13 +1043,13 @@ function statusText(ctx: AgyCommandCtx): string {
|
|
|
1166
1043
|
const source = ctx.usingFallback ? "fallback (agy models failed)" : "discovered";
|
|
1167
1044
|
return [
|
|
1168
1045
|
"Antigravity bridge",
|
|
1169
|
-
` engine: ${config.engine}${config.engine === "stream-json" ? " (agy CLI fallback)" : " (
|
|
1046
|
+
` engine: ${config.engine}${config.engine === "stream-json" ? " (agy CLI fallback)" : " (agy acp)"}`,
|
|
1170
1047
|
` models: ${ctx.entries.length} ${source}`,
|
|
1171
1048
|
...settingsRows(ctx),
|
|
1172
1049
|
` sessions: ${ctx.store.size} bound`,
|
|
1173
1050
|
` config: ${CONFIG_PATH}`,
|
|
1174
1051
|
"",
|
|
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
|
|
1052
|
+
"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
1053
|
].join("\n");
|
|
1177
1054
|
}
|
|
1178
1055
|
|
|
@@ -1180,7 +1057,7 @@ function statusText(ctx: AgyCommandCtx): string {
|
|
|
1180
1057
|
function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
1181
1058
|
pi.registerCommand("agy", {
|
|
1182
1059
|
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>|
|
|
1060
|
+
"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
1061
|
handler: async (args, cmdCtx: ExtensionCommandContext) => {
|
|
1185
1062
|
const ui = cmdCtx.ui;
|
|
1186
1063
|
if (ui) activeUi = ui;
|
|
@@ -1245,7 +1122,7 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1245
1122
|
if (sub === "engine") {
|
|
1246
1123
|
if (val === "acp" || val === "stream-json") {
|
|
1247
1124
|
if (val === "acp" && loadConfig().mode === "plan") {
|
|
1248
|
-
ui?.notify("mode is plan;
|
|
1125
|
+
ui?.notify("mode is plan; provider plan turns are disabled on ACP. /agy mode accept-edits first.", "warning");
|
|
1249
1126
|
return;
|
|
1250
1127
|
}
|
|
1251
1128
|
const next = saveConfig({ engine: val });
|
|
@@ -1253,39 +1130,12 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1253
1130
|
ui?.notify("engine set to stream-json. Takes effect on the next pi start (or /reload).", "info");
|
|
1254
1131
|
return;
|
|
1255
1132
|
}
|
|
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
|
-
}
|
|
1133
|
+
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");
|
|
1134
|
+
void ctx.checkAgySetup(cmdCtx, "acp", true);
|
|
1284
1135
|
} else if (!val && mode === "tui" && ui) {
|
|
1285
1136
|
// Same modal as the first-run wizard: switching engines deserves
|
|
1286
1137
|
// 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.
|
|
1138
|
+
// direct path above: plan blocks acp and restart applies the switch.
|
|
1289
1139
|
await runEngineModal(ui, ctx);
|
|
1290
1140
|
} else {
|
|
1291
1141
|
ui?.notify(`current engine: ${loadConfig().engine}\nusage: /agy engine stream-json|acp`, "info");
|
|
@@ -1293,99 +1143,20 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1293
1143
|
return;
|
|
1294
1144
|
}
|
|
1295
1145
|
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
|
-
}
|
|
1146
|
+
ui?.notify(agyAuthenticationMessage(), "info");
|
|
1361
1147
|
return;
|
|
1362
1148
|
}
|
|
1363
1149
|
if (sub === "auth-manual") {
|
|
1364
1150
|
ui?.notify(
|
|
1365
1151
|
[
|
|
1366
|
-
"
|
|
1367
|
-
"/agy engine acp and session start set this up for you):",
|
|
1152
|
+
"Both engines use the agy CLI and its cached Google credentials.",
|
|
1368
1153
|
"",
|
|
1369
|
-
"
|
|
1370
|
-
"
|
|
1371
|
-
"
|
|
1372
|
-
"
|
|
1373
|
-
"your machine, like any Google tool. This extension never sees your",
|
|
1374
|
-
"credentials.",
|
|
1154
|
+
"Install on Linux or macOS:",
|
|
1155
|
+
" curl -fsSL https://antigravity.google/cli/install.sh | bash",
|
|
1156
|
+
"Install on Windows PowerShell:",
|
|
1157
|
+
" irm https://antigravity.google/cli/install.ps1 | iex",
|
|
1375
1158
|
"",
|
|
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.",
|
|
1159
|
+
agyAuthenticationMessage(),
|
|
1389
1160
|
].join("\n"),
|
|
1390
1161
|
"info",
|
|
1391
1162
|
);
|
|
@@ -1396,15 +1167,11 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1396
1167
|
const engine = ctx.engine;
|
|
1397
1168
|
const snap = (engine === "acp" ? ctx.acpDriver : ctx.driver).snapshot();
|
|
1398
1169
|
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
|
-
: "";
|
|
1170
|
+
const rawCliCheck = await checkAgyCliVersion(resolveAgyBinary());
|
|
1171
|
+
const cliFloor = engine === "acp" ? MIN_AGY_ACP_VERSION : MIN_AGY_VERSION;
|
|
1172
|
+
const cliCheck = rawCliCheck.status === "ok" || rawCliCheck.status === "unsupported"
|
|
1173
|
+
? agyVersionVerdict(rawCliCheck.raw, cliFloor)
|
|
1174
|
+
: rawCliCheck;
|
|
1408
1175
|
const lines = [
|
|
1409
1176
|
"Antigravity doctor (no tokens spent)",
|
|
1410
1177
|
` engine: ${engine}`,
|
|
@@ -1413,7 +1180,7 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1413
1180
|
` 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
1181
|
` sessions: ${ctx.store.size} bound`,
|
|
1415
1182
|
` models: ${ctx.entries.length} ${ctx.usingFallback ? "FALLBACK (agy models failed)" : "discovered"}`,
|
|
1416
|
-
` agy cli: ${describeAgyVersionCheck(cliCheck)} (min ${
|
|
1183
|
+
` agy cli: ${describeAgyVersionCheck(cliCheck)} (min ${cliFloor})`,
|
|
1417
1184
|
` config: ${CONFIG_PATH}`,
|
|
1418
1185
|
` logs: ${logsDir()} (attach recent days' files when reporting issues; set AGY_DEBUG=1 to capture details)`,
|
|
1419
1186
|
];
|
|
@@ -1421,12 +1188,12 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1421
1188
|
lines.push(
|
|
1422
1189
|
` acp session: ${snap.acp.sessionId ?? "(none)"}`,
|
|
1423
1190
|
` 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}
|
|
1191
|
+
` acp stats: prompts=${snap.acp.prompts} created=${snap.acp.sessionsCreated} loaded=${snap.acp.sessionsLoaded} kills=${snap.acp.kills} reconnects=${snap.acp.reconnects}`,
|
|
1425
1192
|
);
|
|
1426
1193
|
// Gate B watch: silent while the server offers no token counts; one
|
|
1427
1194
|
// line the day it starts (then real usage mapping is worth wiring).
|
|
1428
1195
|
if (snap.acp.usageSeen) {
|
|
1429
|
-
lines.push(" acp tokens:
|
|
1196
|
+
lines.push(" acp tokens: NATIVE exact usage in PromptResponse");
|
|
1430
1197
|
} else if (config.acp.usageEstimate !== "off") {
|
|
1431
1198
|
lines.push(` acp tokens: ESTIMATED client-side (mode: ${config.acp.usageEstimate}; auto-off once the server sends real usage)`);
|
|
1432
1199
|
}
|
|
@@ -1435,13 +1202,9 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1435
1202
|
lines.push(" lifecycle (last 5):");
|
|
1436
1203
|
for (const entry of snap.lifecycle.slice(-5)) lines.push(` ${entry}`);
|
|
1437
1204
|
}
|
|
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
|
-
}
|
|
1205
|
+
if (engine === "acp") lines.push(" acp transport: agy acp (shared CLI authentication, isolated MCP home)");
|
|
1206
|
+
const retiredAuth = path.join(os.homedir(), ".gemini", "antigravity-acp");
|
|
1207
|
+
if (fs.existsSync(retiredAuth)) lines.push(` retired auth: ${retiredAuth} is unused; remove it manually when no longer needed`);
|
|
1445
1208
|
// The config surface lives here too, so doctor is the ONE status
|
|
1446
1209
|
// surface (former /agy status is gone).
|
|
1447
1210
|
lines.push("", "settings:", ...settingsRows(ctx));
|
|
@@ -1451,7 +1214,7 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1451
1214
|
if (sub === "mode") {
|
|
1452
1215
|
if (val === "plan" || val === "accept-edits") {
|
|
1453
1216
|
if (val === "plan" && ctx.engine === "acp") {
|
|
1454
|
-
ui?.notify("plan cannot be enforced on provider turns
|
|
1217
|
+
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
1218
|
return;
|
|
1456
1219
|
}
|
|
1457
1220
|
const next = saveConfig({ mode: val as AgyMode });
|
|
@@ -1678,10 +1441,10 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1678
1441
|
return;
|
|
1679
1442
|
}
|
|
1680
1443
|
if (sub === "agent") {
|
|
1681
|
-
// ACP has no agent slot in the protocol
|
|
1444
|
+
// ACP has no per-turn agent slot in the protocol: refuse instead of
|
|
1682
1445
|
// silently ignoring a configured agent.
|
|
1683
1446
|
if (ctx.engine === "acp") {
|
|
1684
|
-
ui?.notify("the ACP engine has no agent selection
|
|
1447
|
+
ui?.notify("the ACP engine has no per-turn agent selection. /agy engine stream-json first.", "warning");
|
|
1685
1448
|
return;
|
|
1686
1449
|
}
|
|
1687
1450
|
const name = (args ?? "").trim().split(/\s+/).slice(1).join(" ").trim();
|
|
@@ -2006,11 +1769,11 @@ async function openAgyPicker(ui: ExtensionUIContext, ctx: AgyCommandCtx): Promis
|
|
|
2006
1769
|
// mode row can still produce plan while the engine is acp - either the
|
|
2007
1770
|
// session-latched engine or the on-disk one, which the Switch engine row
|
|
2008
1771
|
// can change mid-session without a restart. Plan cannot be enforced on
|
|
2009
|
-
// provider turns on either engine
|
|
1772
|
+
// provider turns on either engine; refuse the combination.
|
|
2010
1773
|
const nextMode = pending.mode ?? config.mode;
|
|
2011
1774
|
if (nextMode === "plan" && (ctx.engine === "acp" || config.engine === "acp")) {
|
|
2012
1775
|
ui.notify(
|
|
2013
|
-
"plan cannot be enforced on provider turns
|
|
1776
|
+
"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
1777
|
"warning",
|
|
2015
1778
|
);
|
|
2016
1779
|
return;
|
|
@@ -2048,8 +1811,7 @@ async function openAgyPicker(ui: ExtensionUIContext, ctx: AgyCommandCtx): Promis
|
|
|
2048
1811
|
|
|
2049
1812
|
/** The engine-pick modal shared by /agy engine (no args) and the settings
|
|
2050
1813
|
* picker's engine row: the same explanations as the first-run wizard and the
|
|
2051
|
-
* same semantics
|
|
2052
|
-
* immediately, restart applies the switch. */
|
|
1814
|
+
* same semantics: plan blocks acp and restart applies the switch. */
|
|
2053
1815
|
async function runEngineModal(ui: ExtensionUIContext, ctx: AgyCommandCtx): Promise<void> {
|
|
2054
1816
|
const current = loadConfig().engine;
|
|
2055
1817
|
const picked = await showEnginePicker(ui);
|
|
@@ -2062,10 +1824,10 @@ async function runEngineModal(ui: ExtensionUIContext, ctx: AgyCommandCtx): Promi
|
|
|
2062
1824
|
return;
|
|
2063
1825
|
}
|
|
2064
1826
|
if (picked === "acp" && loadConfig().mode === "plan") {
|
|
2065
|
-
ui.notify("mode is plan;
|
|
1827
|
+
ui.notify("mode is plan; provider plan turns are disabled on ACP. /agy mode accept-edits first.", "warning");
|
|
2066
1828
|
return;
|
|
2067
1829
|
}
|
|
2068
1830
|
saveConfig({ engine: picked });
|
|
2069
1831
|
ui.notify(savedEngineMessage(picked), "info");
|
|
2070
|
-
if (picked === "acp") void ctx.
|
|
1832
|
+
if (picked === "acp") void ctx.checkAgySetup({ ui, mode: "tui" }, "acp", true);
|
|
2071
1833
|
}
|