@estebanforge/pi-antigravity-bridge 1.9.2 → 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 +26 -0
- package/README.md +12 -14
- package/extensions/index.ts +115 -369
- package/package.json +1 -1
- package/src/acp/connection.ts +56 -109
- package/src/acp/driver.ts +38 -62
- 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 -390
- 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 -119
- 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,59 +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
|
-
|
|
251
|
+
bin: binary,
|
|
252
|
+
binArgs: ["acp"],
|
|
253
|
+
isolateHome: true,
|
|
316
254
|
// Resolved per turn: /agy and AGY_USAGE_ESTIMATE changes apply without
|
|
317
255
|
// a restart. Without this the driver defaults to "estimate" and the
|
|
318
256
|
// config knob (incl. "off") is dead.
|
|
@@ -334,7 +272,6 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
334
272
|
return idx >= 0 ? (req.options[idx]?.optionId ?? undefined) : undefined;
|
|
335
273
|
});
|
|
336
274
|
},
|
|
337
|
-
...(authCapture ? { extraEnv: authCapture.browserEnv, authUrlFile: authCapture.file } : {}),
|
|
338
275
|
log: acpLog,
|
|
339
276
|
mcpServers: () => {
|
|
340
277
|
const handle = mcpHandle;
|
|
@@ -441,7 +378,7 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
441
378
|
return;
|
|
442
379
|
}
|
|
443
380
|
closePollRun();
|
|
444
|
-
if (event.mcpServer
|
|
381
|
+
if (/^pi-bridge(?:-\d+)?$/.test(event.mcpServer ?? "")) return;
|
|
445
382
|
// MCP calls through our own pi-bridge already have genuine Pi tool cards;
|
|
446
383
|
// only native Antigravity / other MCP events need display-only entries.
|
|
447
384
|
if (event.status === "started") {
|
|
@@ -563,11 +500,9 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
563
500
|
getMcpPort: () => mcpHandle?.port ?? null,
|
|
564
501
|
bridgeTools: getBridgeTools,
|
|
565
502
|
hiddenBridgeTools,
|
|
566
|
-
acpLog,
|
|
567
503
|
fileLog,
|
|
568
504
|
roster,
|
|
569
|
-
|
|
570
|
-
runAcpPickSetup,
|
|
505
|
+
checkAgySetup,
|
|
571
506
|
});
|
|
572
507
|
|
|
573
508
|
// AskAntigravity tool: one-shot delegation to agy (ported from
|
|
@@ -603,73 +538,48 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
603
538
|
},
|
|
604
539
|
});
|
|
605
540
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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> {
|
|
618
557
|
try {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
// percent variant updates every chunk and would spam the chat.
|
|
627
|
-
ctx.ui.setStatus("agy-acp", m);
|
|
628
|
-
if (m !== lastPhase && !/\d+%/.test(m)) {
|
|
629
|
-
ctx.ui.notify(m, "info");
|
|
630
|
-
lastPhase = m;
|
|
631
|
-
}
|
|
632
|
-
},
|
|
633
|
-
});
|
|
634
|
-
ctx.ui.setStatus("agy-acp", undefined);
|
|
635
|
-
fileLog.log(
|
|
636
|
-
"acp-setup",
|
|
637
|
-
status.ok
|
|
638
|
-
? { ok: true, binarySource: status.binarySource, needsLogin: status.needsLogin }
|
|
639
|
-
: { ok: false, error: status.error },
|
|
640
|
-
status.ok ? "info" : "warn",
|
|
641
|
-
);
|
|
642
|
-
if (!status.ok) {
|
|
643
|
-
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
|
+
}
|
|
644
565
|
return;
|
|
645
566
|
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
saveConfig({ acp: { ...loadConfig().acp, bin: status.bin } });
|
|
649
|
-
if (!status.needsLogin) {
|
|
650
|
-
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);
|
|
651
569
|
return;
|
|
652
570
|
}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
);
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
if (r.ok) {
|
|
664
|
-
ctx.ui.notify(
|
|
665
|
-
r.note ? `Signed in (${r.note}). The ACP engine is ready; restart applies it.` : "Signed in. The ACP engine is ready; restart applies it.",
|
|
666
|
-
"info",
|
|
667
|
-
);
|
|
668
|
-
} 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}`);
|
|
669
581
|
} catch (err) {
|
|
670
|
-
|
|
671
|
-
fileLog.log("acp-setup", { error: String(err) }, "warn");
|
|
672
|
-
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");
|
|
673
583
|
}
|
|
674
584
|
}
|
|
675
585
|
|
|
@@ -688,6 +598,7 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
688
598
|
// (drivers wire at load). The await intentionally runs before the
|
|
689
599
|
// bridge startup below: on a genuine first run the modal blocks input
|
|
690
600
|
// anyway, so the delay is invisible.
|
|
601
|
+
let setupEngine = engine;
|
|
691
602
|
if (event.reason === "startup" && ctx.mode === "tui" && shouldOfferEnginePicker(CONFIG_PATH)) {
|
|
692
603
|
// Best-effort, like the legacy-patch notice below: a picker failure
|
|
693
604
|
// (mid-prompt TUI teardown, resize races) must never take down the
|
|
@@ -696,40 +607,31 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
696
607
|
try {
|
|
697
608
|
const picked = await showEnginePicker(ctx.ui);
|
|
698
609
|
if (picked) {
|
|
610
|
+
setupEngine = picked;
|
|
699
611
|
saveConfig({ engine: picked });
|
|
700
612
|
ctx.ui.notify(savedEngineMessage(picked), "info");
|
|
701
|
-
if (picked === "acp") void runAcpPickSetup(ctx);
|
|
702
613
|
}
|
|
703
614
|
} catch (err) {
|
|
704
615
|
fileLog.log("engine-picker", { error: String(err) }, "warn");
|
|
705
616
|
console.error(`[antigravity-bridge] engine picker failed: ${String(err)}`);
|
|
706
617
|
}
|
|
707
618
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
// /resume and /reload re-fires do not nag mid-session). Runs after the
|
|
712
|
-
// picker above, so a first-run stream-json pick warns immediately.
|
|
713
|
-
if (engine === "stream-json" && !agyMissingWarned && !isAgyInstalled(binary)) {
|
|
714
|
-
agyMissingWarned = true;
|
|
715
|
-
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.";
|
|
716
622
|
if (ctx.hasUI) ctx.ui.notify(msg, "warning");
|
|
717
623
|
else console.error(`[antigravity-bridge] ${msg}`);
|
|
718
624
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
agyVersionWarned = true;
|
|
728
|
-
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).`;
|
|
729
|
-
if (ctx.hasUI) ctx.ui.notify(msg, "warning");
|
|
730
|
-
else console.error(`[antigravity-bridge] ${msg}`);
|
|
731
|
-
});
|
|
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
|
+
}
|
|
732
633
|
}
|
|
634
|
+
void checkAgySetup(ctx, setupEngine);
|
|
733
635
|
// Legacy cleanup: users who ran the old consent-gated patcher still
|
|
734
636
|
// carry pi.invokeTool in their installed pi. Inert, but tell them once
|
|
735
637
|
// and offer /agy patch-cleanup. Never auto-edits the install.
|
|
@@ -747,36 +649,6 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
747
649
|
} catch {
|
|
748
650
|
/* detection is best-effort */
|
|
749
651
|
}
|
|
750
|
-
// ACP self-heal: engine=acp needs a server binary + auth. Silent when
|
|
751
|
-
// everything is ready; installs from the registry and bootstraps auth
|
|
752
|
-
// otherwise; manual instructions only on failure. Fire-and-forget: it
|
|
753
|
-
// must not delay session start (and nothing spawns until the first turn).
|
|
754
|
-
if (engine === "acp" && !acpSelfHealRan) {
|
|
755
|
-
acpSelfHealRan = true;
|
|
756
|
-
void ensureAcpReady({ configBin: loadConfig().acp.bin }).then((status) => {
|
|
757
|
-
fileLog.log(
|
|
758
|
-
"acp-self-heal",
|
|
759
|
-
status.ok
|
|
760
|
-
? { ok: true, binarySource: status.binarySource, needsLogin: status.needsLogin }
|
|
761
|
-
: { ok: false, error: status.error },
|
|
762
|
-
status.ok ? "info" : "warn",
|
|
763
|
-
);
|
|
764
|
-
if (status.ok) {
|
|
765
|
-
if (status.binarySource === "installed" || status.binarySource === "existing") {
|
|
766
|
-
saveConfig({ acp: { ...loadConfig().acp, bin: status.bin } });
|
|
767
|
-
}
|
|
768
|
-
if (status.needsLogin) {
|
|
769
|
-
const msg = acpLoginPending();
|
|
770
|
-
if (ctx.hasUI) ctx.ui.notify(msg, "warning");
|
|
771
|
-
else console.error(`[antigravity-bridge] ${msg}`);
|
|
772
|
-
}
|
|
773
|
-
return;
|
|
774
|
-
}
|
|
775
|
-
const msg = `ACP auto-setup failed (${status.error}).\n${status.manual}`;
|
|
776
|
-
if (ctx.hasUI) ctx.ui.notify(msg, "warning");
|
|
777
|
-
else console.error(`[antigravity-bridge] ${msg}`);
|
|
778
|
-
});
|
|
779
|
-
}
|
|
780
652
|
// Bridge failure logger. Routine lifecycle (listening,
|
|
781
653
|
// bridge-config-written/removed, closed) is normal startup/teardown
|
|
782
654
|
// traffic: toasting it every session, or pinning it via stderr in
|
|
@@ -820,6 +692,10 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
820
692
|
if (ctx.hasUI) ctx.ui.notify(msg, "warning");
|
|
821
693
|
else console.error(msg);
|
|
822
694
|
};
|
|
695
|
+
// Sweep crashed sessions even when the bridge is disabled.
|
|
696
|
+
sweepStaleBridgeServers();
|
|
697
|
+
sweepStaleBridgeDirs();
|
|
698
|
+
sweepStaleAcpHomes();
|
|
823
699
|
// Start the bridge unless the user turned it off. No patch gate, no
|
|
824
700
|
// consent flow: calls route through pi's normal toolUse loop.
|
|
825
701
|
const bridgeMode: BridgeTools = loadConfig().bridgeTools;
|
|
@@ -922,14 +798,13 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
922
798
|
);
|
|
923
799
|
if (r.ok && r.handle) {
|
|
924
800
|
mcpHandle = r.handle;
|
|
925
|
-
//
|
|
926
|
-
//
|
|
801
|
+
// Entries a crashed delegation left suppressed are healed here,
|
|
802
|
+
// but only when no live delegation is in
|
|
927
803
|
// flight anywhere (marker-aware heal). A blind re-enable used to
|
|
928
804
|
// un-hide the bridge during another session's active delegation.
|
|
929
805
|
// Global-config hygiene runs on BOTH engines: a crashed stream-json
|
|
930
806
|
// session must not leave dead-pid entries or stuck suppression flags
|
|
931
807
|
// for the next one.
|
|
932
|
-
sweepStaleBridgeServers();
|
|
933
808
|
healBridgeSuppression();
|
|
934
809
|
// ACP supplies the bridge per-session (session/new | session/load);
|
|
935
810
|
// only the stream-json CLI reads the global MCP config.
|
|
@@ -1053,8 +928,6 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
1053
928
|
activeUi = null;
|
|
1054
929
|
const h = mcpHandle;
|
|
1055
930
|
mcpHandle = null;
|
|
1056
|
-
await h?.close();
|
|
1057
|
-
roundTrips.failAll(FAIL_REASON_SHUTDOWN);
|
|
1058
931
|
// "recycle", NOT "shutdown": pi fires session_shutdown on /new, /resume
|
|
1059
932
|
// and /fork (docs/extensions.md session lifecycle), not only on process
|
|
1060
933
|
// exit. The drivers are process-lifetime singletons; closing them with
|
|
@@ -1065,6 +938,8 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
1065
938
|
// ("quit") - the connection kill is identical and nothing runs after.
|
|
1066
939
|
await streamDriver.close("recycle", "session shutdown");
|
|
1067
940
|
await acpDriver.close("recycle", "session shutdown");
|
|
941
|
+
roundTrips.failAll(FAIL_REASON_SHUTDOWN);
|
|
942
|
+
await h?.close();
|
|
1068
943
|
if (engine === "stream-json") unregisterBridgeServer(process.pid);
|
|
1069
944
|
hiddenBridgeTools.clear();
|
|
1070
945
|
// Approval gate: unstage hooks and remove the per-pid script. Pending
|
|
@@ -1084,14 +959,6 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
1084
959
|
|
|
1085
960
|
// --- /agy command -----------------------------------------------------------
|
|
1086
961
|
|
|
1087
|
-
/** Shared body for every ACP-login-pending moment (session_start self-heal,
|
|
1088
|
-
* /agy engine acp). End-user simple: what to run, what happens, which
|
|
1089
|
-
* account. Sign-in is explicit (/agy auth); it never rides the first
|
|
1090
|
-
* message. */
|
|
1091
|
-
function acpLoginPending(): string {
|
|
1092
|
-
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.`;
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
962
|
interface AgyCommandCtx {
|
|
1096
963
|
entries: AgyModelEntry[];
|
|
1097
964
|
store: SessionStore;
|
|
@@ -1103,18 +970,11 @@ interface AgyCommandCtx {
|
|
|
1103
970
|
getMcpPort: () => number | null;
|
|
1104
971
|
bridgeTools: () => Array<{ name: string }>;
|
|
1105
972
|
hiddenBridgeTools: Set<string>;
|
|
1106
|
-
/** Shared ACP log sink (login URL surfacing + failure events). */
|
|
1107
|
-
acpLog: (msg: string, data?: unknown) => void;
|
|
1108
973
|
/** Daily file logger (src/daily-log.ts); command + doctor surfacing. */
|
|
1109
974
|
fileLog: DailyLogger;
|
|
1110
975
|
/** Live subagent roster (folded in the provider); /agy subagents. */
|
|
1111
976
|
roster: SubagentRoster;
|
|
1112
|
-
|
|
1113
|
-
* by /agy engine's no-args modal so both entry points behave alike. */
|
|
1114
|
-
runAcpPickSetup: (cmdCtx: { ui: ExtensionUIContext }) => Promise<void>;
|
|
1115
|
-
/** BROWSER-capture handles; null when unavailable (Windows, unwritable
|
|
1116
|
-
* data dir). /agy auth passes them to the sign-in process. */
|
|
1117
|
-
authCapture: { browserEnv: Record<string, string>; file: string } | null;
|
|
977
|
+
checkAgySetup: (ctx: { ui: ExtensionUIContext; mode: string }, engine: Engine, explicit?: boolean) => Promise<void>;
|
|
1118
978
|
}
|
|
1119
979
|
|
|
1120
980
|
interface PendingConfig {
|
|
@@ -1161,13 +1021,13 @@ function statusText(ctx: AgyCommandCtx): string {
|
|
|
1161
1021
|
const source = ctx.usingFallback ? "fallback (agy models failed)" : "discovered";
|
|
1162
1022
|
return [
|
|
1163
1023
|
"Antigravity bridge",
|
|
1164
|
-
` engine: ${config.engine}${config.engine === "stream-json" ? " (agy CLI fallback)" : " (
|
|
1024
|
+
` engine: ${config.engine}${config.engine === "stream-json" ? " (agy CLI fallback)" : " (agy acp)"}`,
|
|
1165
1025
|
` models: ${ctx.entries.length} ${source}`,
|
|
1166
1026
|
...settingsRows(ctx),
|
|
1167
1027
|
` sessions: ${ctx.store.size} bound`,
|
|
1168
1028
|
` config: ${CONFIG_PATH}`,
|
|
1169
1029
|
"",
|
|
1170
|
-
"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",
|
|
1171
1031
|
].join("\n");
|
|
1172
1032
|
}
|
|
1173
1033
|
|
|
@@ -1175,7 +1035,7 @@ function statusText(ctx: AgyCommandCtx): string {
|
|
|
1175
1035
|
function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
1176
1036
|
pi.registerCommand("agy", {
|
|
1177
1037
|
description:
|
|
1178
|
-
"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]",
|
|
1179
1039
|
handler: async (args, cmdCtx: ExtensionCommandContext) => {
|
|
1180
1040
|
const ui = cmdCtx.ui;
|
|
1181
1041
|
if (ui) activeUi = ui;
|
|
@@ -1240,7 +1100,7 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1240
1100
|
if (sub === "engine") {
|
|
1241
1101
|
if (val === "acp" || val === "stream-json") {
|
|
1242
1102
|
if (val === "acp" && loadConfig().mode === "plan") {
|
|
1243
|
-
ui?.notify("mode is plan;
|
|
1103
|
+
ui?.notify("mode is plan; provider plan turns are disabled on ACP. /agy mode accept-edits first.", "warning");
|
|
1244
1104
|
return;
|
|
1245
1105
|
}
|
|
1246
1106
|
const next = saveConfig({ engine: val });
|
|
@@ -1248,39 +1108,12 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1248
1108
|
ui?.notify("engine set to stream-json. Takes effect on the next pi start (or /reload).", "info");
|
|
1249
1109
|
return;
|
|
1250
1110
|
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
// Manual instructions only when a step fails.
|
|
1254
|
-
ui?.notify("engine set to acp. Preparing the server (binary + auth)…", "info");
|
|
1255
|
-
const status = await ensureAcpReady({
|
|
1256
|
-
configBin: loadConfig().acp.bin,
|
|
1257
|
-
onProgress: (m) => ui?.notify(m, "info"),
|
|
1258
|
-
});
|
|
1259
|
-
ctx.fileLog.log(
|
|
1260
|
-
"acp-setup",
|
|
1261
|
-
status.ok
|
|
1262
|
-
? { ok: true, binarySource: status.binarySource, needsLogin: status.needsLogin }
|
|
1263
|
-
: { ok: false, error: status.error },
|
|
1264
|
-
status.ok ? "info" : "warn",
|
|
1265
|
-
);
|
|
1266
|
-
if (!status.ok) {
|
|
1267
|
-
ui?.notify(`ACP auto-setup failed (${status.error}).\n${status.manual}`, "warning");
|
|
1268
|
-
return;
|
|
1269
|
-
}
|
|
1270
|
-
saveConfig({ acp: { ...loadConfig().acp, bin: status.bin } });
|
|
1271
|
-
if (status.needsLogin) {
|
|
1272
|
-
ui?.notify(
|
|
1273
|
-
`ACP engine set. ${acpLoginPending()}`,
|
|
1274
|
-
"warning",
|
|
1275
|
-
);
|
|
1276
|
-
} else {
|
|
1277
|
-
ui?.notify(`ACP engine ready (auth: ${status.auth}). Takes effect on the next pi start (or /reload).`, "info");
|
|
1278
|
-
}
|
|
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);
|
|
1279
1113
|
} else if (!val && mode === "tui" && ui) {
|
|
1280
1114
|
// Same modal as the first-run wizard: switching engines deserves
|
|
1281
1115
|
// the explanations, not a bare usage line. Semantics match the
|
|
1282
|
-
// direct path above: plan blocks acp
|
|
1283
|
-
// + sign-in immediately, restart applies the switch.
|
|
1116
|
+
// direct path above: plan blocks acp and restart applies the switch.
|
|
1284
1117
|
await runEngineModal(ui, ctx);
|
|
1285
1118
|
} else {
|
|
1286
1119
|
ui?.notify(`current engine: ${loadConfig().engine}\nusage: /agy engine stream-json|acp`, "info");
|
|
@@ -1288,98 +1121,20 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1288
1121
|
return;
|
|
1289
1122
|
}
|
|
1290
1123
|
if (sub === "auth") {
|
|
1291
|
-
|
|
1292
|
-
// /agy engine acp followed by /agy auth in the same session works,
|
|
1293
|
-
// no restart needed before signing in.
|
|
1294
|
-
if (loadConfig().engine !== "acp") {
|
|
1295
|
-
ui?.notify(`the selected engine is ${loadConfig().engine}. /agy engine acp first, then /agy auth.`, "warning");
|
|
1296
|
-
return;
|
|
1297
|
-
}
|
|
1298
|
-
ui?.notify("Preparing the ACP server (binary + auth settings)…", "info");
|
|
1299
|
-
const status = await ensureAcpReady({ configBin: loadConfig().acp.bin, onProgress: (m) => ui?.notify(m, "info") });
|
|
1300
|
-
ctx.fileLog.log(
|
|
1301
|
-
"acp-setup",
|
|
1302
|
-
status.ok
|
|
1303
|
-
? { ok: true, binarySource: status.binarySource, needsLogin: status.needsLogin }
|
|
1304
|
-
: { ok: false, error: status.error },
|
|
1305
|
-
status.ok ? "info" : "warn",
|
|
1306
|
-
);
|
|
1307
|
-
if (!status.ok) {
|
|
1308
|
-
ui?.notify(`ACP auto-setup failed (${status.error}).\n${status.manual}`, "warning");
|
|
1309
|
-
return;
|
|
1310
|
-
}
|
|
1311
|
-
saveConfig({ acp: { ...loadConfig().acp, bin: status.bin } });
|
|
1312
|
-
if (!status.needsLogin) {
|
|
1313
|
-
ui?.notify(`Already signed in (auth: ${status.auth}). Nothing to do.`, "info");
|
|
1314
|
-
return;
|
|
1315
|
-
}
|
|
1316
|
-
ui?.notify(
|
|
1317
|
-
"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.",
|
|
1318
|
-
"info",
|
|
1319
|
-
);
|
|
1320
|
-
const r = await runAcpAuth({
|
|
1321
|
-
bin: status.bin,
|
|
1322
|
-
extraEnv: ctx.authCapture?.browserEnv,
|
|
1323
|
-
authUrlFile: ctx.authCapture?.file,
|
|
1324
|
-
log: ctx.acpLog,
|
|
1325
|
-
});
|
|
1326
|
-
ctx.fileLog.log("acp-auth", r.ok ? { ok: true } : { ok: false, error: r.error }, r.ok ? "info" : "warn");
|
|
1327
|
-
if (r.ok) {
|
|
1328
|
-
ui?.notify(
|
|
1329
|
-
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).",
|
|
1330
|
-
"info",
|
|
1331
|
-
);
|
|
1332
|
-
} else {
|
|
1333
|
-
ui?.notify(`ACP sign-in failed (${r.error}).\nRun /agy auth to retry; /agy auth-manual has manual steps.`, "warning");
|
|
1334
|
-
}
|
|
1335
|
-
return;
|
|
1336
|
-
}
|
|
1337
|
-
if (sub === "acp-bin") {
|
|
1338
|
-
const rest = (args ?? "").trim().split(/\s+/).slice(1).join(" ");
|
|
1339
|
-
if (rest.length > 0) {
|
|
1340
|
-
// Only the keyword compares case-insensitively; the path keeps its case.
|
|
1341
|
-
const bin = rest.toLowerCase() === "auto" ? "" : rest.replace(/^~(?=\/|$)/, os.homedir());
|
|
1342
|
-
// Spread, not a bare acp patch: a bare {bin} object
|
|
1343
|
-
// would drop sibling keys (usageEstimate) from the file.
|
|
1344
|
-
saveConfig({ acp: { ...loadConfig().acp, bin } });
|
|
1345
|
-
ui?.notify(
|
|
1346
|
-
bin
|
|
1347
|
-
? `acp.bin set to ${bin}. The next ACP turn (re)connects with it.`
|
|
1348
|
-
: "acp.bin cleared. Auto-setup (or AGY_ACP_BIN) picks the binary on the next ACP turn.",
|
|
1349
|
-
"info",
|
|
1350
|
-
);
|
|
1351
|
-
} else {
|
|
1352
|
-
const cur = loadConfig().acp.bin;
|
|
1353
|
-
ui?.notify(`acp.bin: ${cur || "(auto: setup installs, or AGY_ACP_BIN)"}\nusage: /agy acp-bin <path|auto>`, "info");
|
|
1354
|
-
}
|
|
1124
|
+
ui?.notify(agyAuthenticationMessage(), "info");
|
|
1355
1125
|
return;
|
|
1356
1126
|
}
|
|
1357
1127
|
if (sub === "auth-manual") {
|
|
1358
1128
|
ui?.notify(
|
|
1359
1129
|
[
|
|
1360
|
-
"
|
|
1361
|
-
"/agy engine acp and session start set this up for you):",
|
|
1130
|
+
"Both engines use the agy CLI and its cached Google credentials.",
|
|
1362
1131
|
"",
|
|
1363
|
-
"
|
|
1364
|
-
"
|
|
1365
|
-
"
|
|
1366
|
-
"
|
|
1367
|
-
"your machine, like any Google tool. This extension never sees your",
|
|
1368
|
-
"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",
|
|
1369
1136
|
"",
|
|
1370
|
-
|
|
1371
|
-
" the antigravity-acp registry; point acp.bin or AGY_ACP_BIN at it.",
|
|
1372
|
-
'2. Default: put {"auth":{"type":"oauth-personal"}} in',
|
|
1373
|
-
" ~/.gemini/antigravity-acp/settings.json, run /agy auth, and complete",
|
|
1374
|
-
" the Google login that opens in your browser. No browser (SSH",
|
|
1375
|
-
" session)? pi shows the sign-in URL to copy; forward the redirect",
|
|
1376
|
-
" port over ssh (ssh -N -L <port>:127.0.0.1:<port> <user@host>), then",
|
|
1377
|
-
" open the URL on your machine.",
|
|
1378
|
-
' Headless alternative: GEMINI_API_KEY + {"auth":{"type":"gemini-api-key"}}',
|
|
1379
|
-
" (metered paid API - not your Antigravity plan). The key is used only",
|
|
1380
|
-
" when that type is selected; with the default oauth-personal in place,",
|
|
1381
|
-
" an exported key is ignored.",
|
|
1382
|
-
"3. /agy doctor shows the server version when auth is OK.",
|
|
1137
|
+
agyAuthenticationMessage(),
|
|
1383
1138
|
].join("\n"),
|
|
1384
1139
|
"info",
|
|
1385
1140
|
);
|
|
@@ -1390,15 +1145,11 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1390
1145
|
const engine = ctx.engine;
|
|
1391
1146
|
const snap = (engine === "acp" ? ctx.acpDriver : ctx.driver).snapshot();
|
|
1392
1147
|
const port = ctx.getMcpPort();
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
const cliSuffix =
|
|
1399
|
-
cliCheck.status === "unavailable" && engine === "acp"
|
|
1400
|
-
? " (optional here; AskAntigravity delegations use the CLI)"
|
|
1401
|
-
: "";
|
|
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;
|
|
1402
1153
|
const lines = [
|
|
1403
1154
|
"Antigravity doctor (no tokens spent)",
|
|
1404
1155
|
` engine: ${engine}`,
|
|
@@ -1407,7 +1158,7 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1407
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})` : ""}`,
|
|
1408
1159
|
` sessions: ${ctx.store.size} bound`,
|
|
1409
1160
|
` models: ${ctx.entries.length} ${ctx.usingFallback ? "FALLBACK (agy models failed)" : "discovered"}`,
|
|
1410
|
-
` agy cli: ${describeAgyVersionCheck(cliCheck)} (min ${
|
|
1161
|
+
` agy cli: ${describeAgyVersionCheck(cliCheck)} (min ${cliFloor})`,
|
|
1411
1162
|
` config: ${CONFIG_PATH}`,
|
|
1412
1163
|
` logs: ${logsDir()} (attach recent days' files when reporting issues; set AGY_DEBUG=1 to capture details)`,
|
|
1413
1164
|
];
|
|
@@ -1415,12 +1166,12 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1415
1166
|
lines.push(
|
|
1416
1167
|
` acp session: ${snap.acp.sessionId ?? "(none)"}`,
|
|
1417
1168
|
` acp server: ${snap.acp.serverVersion ?? "unknown"}${snap.acp.agentTitle ? ` (${snap.acp.agentTitle})` : ""}`,
|
|
1418
|
-
` 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}`,
|
|
1419
1170
|
);
|
|
1420
1171
|
// Gate B watch: silent while the server offers no token counts; one
|
|
1421
1172
|
// line the day it starts (then real usage mapping is worth wiring).
|
|
1422
1173
|
if (snap.acp.usageSeen) {
|
|
1423
|
-
lines.push(" acp tokens:
|
|
1174
|
+
lines.push(" acp tokens: NATIVE exact usage in PromptResponse");
|
|
1424
1175
|
} else if (config.acp.usageEstimate !== "off") {
|
|
1425
1176
|
lines.push(` acp tokens: ESTIMATED client-side (mode: ${config.acp.usageEstimate}; auto-off once the server sends real usage)`);
|
|
1426
1177
|
}
|
|
@@ -1429,13 +1180,9 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1429
1180
|
lines.push(" lifecycle (last 5):");
|
|
1430
1181
|
for (const entry of snap.lifecycle.slice(-5)) lines.push(` ${entry}`);
|
|
1431
1182
|
}
|
|
1432
|
-
if (engine === "acp")
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
` acp binary: ${setup.bin ?? "not found (auto-setup offers install)"}${setup.source ? ` (${setup.source})` : ""}`,
|
|
1436
|
-
` acp auth: ${setup.auth ?? "not configured (auto-setup bootstraps)"}`,
|
|
1437
|
-
);
|
|
1438
|
-
}
|
|
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`);
|
|
1439
1186
|
// The config surface lives here too, so doctor is the ONE status
|
|
1440
1187
|
// surface (former /agy status is gone).
|
|
1441
1188
|
lines.push("", "settings:", ...settingsRows(ctx));
|
|
@@ -1445,7 +1192,7 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1445
1192
|
if (sub === "mode") {
|
|
1446
1193
|
if (val === "plan" || val === "accept-edits") {
|
|
1447
1194
|
if (val === "plan" && ctx.engine === "acp") {
|
|
1448
|
-
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");
|
|
1449
1196
|
return;
|
|
1450
1197
|
}
|
|
1451
1198
|
const next = saveConfig({ mode: val as AgyMode });
|
|
@@ -1672,10 +1419,10 @@ function registerAgyCommand(pi: ExtensionAPI, ctx: AgyCommandCtx): void {
|
|
|
1672
1419
|
return;
|
|
1673
1420
|
}
|
|
1674
1421
|
if (sub === "agent") {
|
|
1675
|
-
// ACP has no agent slot in the protocol
|
|
1422
|
+
// ACP has no per-turn agent slot in the protocol: refuse instead of
|
|
1676
1423
|
// silently ignoring a configured agent.
|
|
1677
1424
|
if (ctx.engine === "acp") {
|
|
1678
|
-
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");
|
|
1679
1426
|
return;
|
|
1680
1427
|
}
|
|
1681
1428
|
const name = (args ?? "").trim().split(/\s+/).slice(1).join(" ").trim();
|
|
@@ -2000,11 +1747,11 @@ async function openAgyPicker(ui: ExtensionUIContext, ctx: AgyCommandCtx): Promis
|
|
|
2000
1747
|
// mode row can still produce plan while the engine is acp - either the
|
|
2001
1748
|
// session-latched engine or the on-disk one, which the Switch engine row
|
|
2002
1749
|
// can change mid-session without a restart. Plan cannot be enforced on
|
|
2003
|
-
// provider turns on either engine
|
|
1750
|
+
// provider turns on either engine; refuse the combination.
|
|
2004
1751
|
const nextMode = pending.mode ?? config.mode;
|
|
2005
1752
|
if (nextMode === "plan" && (ctx.engine === "acp" || config.engine === "acp")) {
|
|
2006
1753
|
ui.notify(
|
|
2007
|
-
"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.",
|
|
2008
1755
|
"warning",
|
|
2009
1756
|
);
|
|
2010
1757
|
return;
|
|
@@ -2042,8 +1789,7 @@ async function openAgyPicker(ui: ExtensionUIContext, ctx: AgyCommandCtx): Promis
|
|
|
2042
1789
|
|
|
2043
1790
|
/** The engine-pick modal shared by /agy engine (no args) and the settings
|
|
2044
1791
|
* picker's engine row: the same explanations as the first-run wizard and the
|
|
2045
|
-
* same semantics
|
|
2046
|
-
* immediately, restart applies the switch. */
|
|
1792
|
+
* same semantics: plan blocks acp and restart applies the switch. */
|
|
2047
1793
|
async function runEngineModal(ui: ExtensionUIContext, ctx: AgyCommandCtx): Promise<void> {
|
|
2048
1794
|
const current = loadConfig().engine;
|
|
2049
1795
|
const picked = await showEnginePicker(ui);
|
|
@@ -2056,10 +1802,10 @@ async function runEngineModal(ui: ExtensionUIContext, ctx: AgyCommandCtx): Promi
|
|
|
2056
1802
|
return;
|
|
2057
1803
|
}
|
|
2058
1804
|
if (picked === "acp" && loadConfig().mode === "plan") {
|
|
2059
|
-
ui.notify("mode is plan;
|
|
1805
|
+
ui.notify("mode is plan; provider plan turns are disabled on ACP. /agy mode accept-edits first.", "warning");
|
|
2060
1806
|
return;
|
|
2061
1807
|
}
|
|
2062
1808
|
saveConfig({ engine: picked });
|
|
2063
1809
|
ui.notify(savedEngineMessage(picked), "info");
|
|
2064
|
-
if (picked === "acp") void ctx.
|
|
1810
|
+
if (picked === "acp") void ctx.checkAgySetup({ ui, mode: "tui" }, "acp", true);
|
|
2065
1811
|
}
|