@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.
@@ -0,0 +1,85 @@
1
+ import { spawn } from "node:child_process";
2
+ import { isAgyInstalled } from "./engine-picker.js";
3
+
4
+ export interface AgyInstallCommand {
5
+ command: string;
6
+ args: string[];
7
+ }
8
+
9
+ export function agyInstallCommand(platform: NodeJS.Platform = process.platform): AgyInstallCommand {
10
+ if (platform === "win32") {
11
+ return {
12
+ command: "powershell.exe",
13
+ args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", "irm https://antigravity.google/cli/install.ps1 | iex"],
14
+ };
15
+ }
16
+ return {
17
+ command: "bash",
18
+ args: ["-c", "set -o pipefail; curl -fsSL https://antigravity.google/cli/install.sh | bash"],
19
+ };
20
+ }
21
+
22
+ export function agyAuthenticationMessage(): string {
23
+ return "Run `agy` in your terminal and complete the Google sign-in. Restart pi afterwards.";
24
+ }
25
+
26
+ type InstallRunner = (command: AgyInstallCommand, timeoutMs: number) => Promise<{ code: number | null; output: string }>;
27
+
28
+ const defaultRunner: InstallRunner = ({ command, args }, timeoutMs) =>
29
+ new Promise((resolve, reject) => {
30
+ const child = spawn(command, args, {
31
+ stdio: ["ignore", "pipe", "pipe"],
32
+ detached: process.platform !== "win32",
33
+ windowsHide: true,
34
+ });
35
+ let output = "";
36
+ let settled = false;
37
+ const append = (chunk: Buffer) => {
38
+ output = (output + chunk.toString("utf8")).slice(-64_000);
39
+ };
40
+ const fail = (err: Error) => {
41
+ if (settled) return;
42
+ settled = true;
43
+ clearTimeout(watchdog);
44
+ reject(err);
45
+ };
46
+ child.stdout?.on("data", append);
47
+ child.stderr?.on("data", append);
48
+ child.on("error", fail);
49
+ child.on("close", (code) => {
50
+ if (settled) return;
51
+ settled = true;
52
+ clearTimeout(watchdog);
53
+ resolve({ code, output: output.trim() });
54
+ });
55
+ const watchdog = setTimeout(() => {
56
+ try {
57
+ if (child.pid && process.platform === "win32") {
58
+ const killer = spawn("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
59
+ killer.on("error", () => {});
60
+ killer.unref();
61
+ } else if (child.pid) process.kill(-child.pid, "SIGKILL");
62
+ else child.kill("SIGKILL");
63
+ } catch {
64
+ // The process may have exited between the deadline and kill.
65
+ }
66
+ fail(new Error("Antigravity CLI installer exceeded 10 minutes"));
67
+ }, timeoutMs);
68
+ });
69
+
70
+ export async function installAgyCli(opts: {
71
+ platform?: NodeJS.Platform;
72
+ run?: InstallRunner;
73
+ verify?: () => boolean | Promise<boolean>;
74
+ timeoutMs?: number;
75
+ } = {}): Promise<{ ok: true; output: string } | { ok: false; error: string }> {
76
+ try {
77
+ const result = await (opts.run ?? defaultRunner)(agyInstallCommand(opts.platform), opts.timeoutMs ?? 10 * 60_000);
78
+ if (result.code !== 0) return { ok: false, error: result.output || `installer exited with code ${result.code ?? "unknown"}` };
79
+ const verify = opts.verify ?? (() => isAgyInstalled(process.env.AGY_BIN || "agy"));
80
+ if (!(await verify())) return { ok: false, error: "installer finished, but agy is not available in this pi process; open a new terminal, run `agy --version`, then restart pi" };
81
+ return { ok: true, output: result.output };
82
+ } catch (err) {
83
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
84
+ }
85
+ }
package/src/agy-paths.ts CHANGED
@@ -1,9 +1,7 @@
1
1
  // agy per-engine state dirs ("brain" trees).
2
2
  //
3
- // Both engines keep per-conversation state on disk; the roots differ
4
- // (live-probed 2026-09-24, this machine):
5
- // stream-json CLI: ~/.gemini/antigravity-cli/brain/<conversationId>/
6
- // ACP server: ~/.gemini/antigravity-acp/brain/<conversationId>/
3
+ // Both engines keep per-conversation state in the native CLI tree:
4
+ // ~/.gemini/antigravity-cli/brain/<conversationId>/
7
5
  // The ACP tree mirrors the CLI layout (.system_generated/tasks/,
8
6
  // .system_generated/steps/), so downstream scans (tasks, artifacts) work
9
7
  // unchanged once the right root is picked. A third tree exists —
@@ -18,8 +16,7 @@ import type { Engine } from "./config.js";
18
16
 
19
17
  /** Brain root for one engine. `home` injectable for tests. */
20
18
  export function agyBrainRoot(engine: Engine, home: string = os.homedir()): string {
21
- const scope = engine === "acp" ? "antigravity-acp" : "antigravity-cli";
22
- return path.join(home, ".gemini", scope, "brain");
19
+ return path.join(home, ".gemini", "antigravity-cli", "brain");
23
20
  }
24
21
 
25
22
  /** Per-conversation state dir. `conversationId` is the raw id agy reports
@@ -0,0 +1,72 @@
1
+ import { agyAuthenticationMessage, agyInstallCommand, installAgyCli } from "./agy-install.js";
2
+
3
+ export interface AgySetupUi {
4
+ confirm: (title: string, body: string) => Promise<boolean>;
5
+ notify: (message: string, level: "info" | "warning") => void;
6
+ setStatus: (id: string, text?: string) => void;
7
+ }
8
+
9
+ export function createAgySetupOffer(opts: {
10
+ ui: AgySetupUi;
11
+ binaryOverride?: string;
12
+ install?: typeof installAgyCli;
13
+ missingMessage: () => string;
14
+ log: (event: string, data: unknown, level: "warn" | "info") => void;
15
+ }): () => Promise<void> {
16
+ let offered = false;
17
+ let inFlight: Promise<void> | null = null;
18
+ const install = opts.install ?? installAgyCli;
19
+ const notify = (message: string, level: "info" | "warning") => {
20
+ try {
21
+ opts.ui.notify(message, level);
22
+ } catch (err) {
23
+ opts.log("agy-install", { error: String(err) }, "warn");
24
+ }
25
+ };
26
+
27
+ return () => {
28
+ if (inFlight) return inFlight;
29
+ if (offered) return Promise.resolve();
30
+ offered = true;
31
+ inFlight = (async () => {
32
+ try {
33
+ if (opts.binaryOverride) {
34
+ notify(
35
+ `AGY_BIN points to ${opts.binaryOverride}, which is missing or unsupported. Fix or unset AGY_BIN. The official installer cannot change this override.`,
36
+ "warning",
37
+ );
38
+ return;
39
+ }
40
+ const spec = agyInstallCommand();
41
+ const command = [spec.command, ...spec.args].join(" ");
42
+ const approved = await opts.ui.confirm(
43
+ "Antigravity CLI required",
44
+ `Both engines require the agy CLI. Run Google's official installer now?\n\n${command}`,
45
+ );
46
+ if (!approved) {
47
+ notify(opts.missingMessage(), "warning");
48
+ return;
49
+ }
50
+ opts.ui.setStatus("agy-install", "Installing Antigravity CLI…");
51
+ const result = await install();
52
+ if (!result.ok) {
53
+ opts.log("agy-install", { error: result.error }, "warn");
54
+ notify(`Antigravity CLI installation failed: ${result.error}\n${opts.missingMessage()}`, "warning");
55
+ return;
56
+ }
57
+ notify(`Antigravity CLI installed. ${agyAuthenticationMessage()}`, "info");
58
+ } catch (err) {
59
+ opts.log("agy-install", { error: String(err) }, "warn");
60
+ notify(`Antigravity CLI setup failed: ${String(err)}\n${opts.missingMessage()}`, "warning");
61
+ } finally {
62
+ try {
63
+ opts.ui.setStatus("agy-install", undefined);
64
+ } catch {
65
+ // UI teardown must not reject a shared setup promise.
66
+ }
67
+ inFlight = null;
68
+ }
69
+ })();
70
+ return inFlight;
71
+ };
72
+ }
@@ -7,16 +7,15 @@
7
7
  // `agy --version`, memoized per binary because discovery already spawns agy
8
8
  // once per load and the version will not change mid-process.
9
9
  //
10
- // The ACP server reports its own version at initialize (agentInfo.version,
11
- // e.g. "agy_acp_server_1.1.1") and /agy doctor already prints it. ACP build
12
- // numbering is a different lineage from the CLI, so no floor is enforced
13
- // there: the one known-good build must not be locked out by a CLI number.
10
+ // The ACP server reports the CLI version at initialize. agy acp has its own
11
+ // floor because older CLI releases do not ship the subcommand.
14
12
 
15
13
  import { spawn } from "node:child_process";
16
14
 
17
15
  /** Floor for the stream-json CLI. Matches the protocol grammar our driver
18
16
  * and the event mapping rely on; older agy predates verified fields. */
19
17
  export const MIN_AGY_VERSION = "1.1.22";
18
+ export const MIN_AGY_ACP_VERSION = "1.5.3";
20
19
 
21
20
  const VERSION_TIMEOUT_MS = 5_000;
22
21
 
package/src/ask-tool.ts CHANGED
@@ -747,7 +747,7 @@ export async function registerAskAntigravityTool(
747
747
  if (args.includeContext) tags.push("context=full");
748
748
  if (args.background) tags.push("bg");
749
749
 
750
- let text = theme.fg("mdLink", theme.bold("AskAntigravity "));
750
+ let text = theme.fg("success", theme.bold("AskAntigravity "));
751
751
  text += `${theme.fg("accent", `[${tags.join(", ")}]`)} `;
752
752
 
753
753
  const prompt = String(args.prompt ?? "");
@@ -763,7 +763,7 @@ export async function registerAskAntigravityTool(
763
763
  const d = result.details as AgyDetails | undefined;
764
764
  if (isPartial) {
765
765
  const status = result.content[0]?.type === "text" ? result.content[0].text : "working...";
766
- return new Text(theme.fg("mdLink", "◉ AskAntigravity ") + theme.fg("muted", status), 0, 0);
766
+ return new Text(theme.fg("success", "◉ AskAntigravity ") + theme.fg("muted", status), 0, 0);
767
767
  }
768
768
 
769
769
  const body = result.content[0]?.type === "text" ? result.content[0].text : "";
@@ -772,7 +772,7 @@ export async function registerAskAntigravityTool(
772
772
 
773
773
  let text = errored
774
774
  ? theme.fg("error", "✗ AskAntigravity error")
775
- : theme.fg("mdLink", "✓ AskAntigravity");
775
+ : theme.fg("success", "✓ AskAntigravity");
776
776
 
777
777
  const rTags: string[] = [];
778
778
  if (d?.resolvedModel || d?.model) rTags.push(`model=${d?.resolvedModel ?? d?.model}`);
package/src/config.ts CHANGED
@@ -38,15 +38,13 @@ export function logsDir(): string {
38
38
  );
39
39
  }
40
40
 
41
- /** Which turn engine drives turns. "acp" (the official server) is the
42
- * default for new installs; "stream-json" (the `agy` CLI) remains a fully
43
- * supported fallback. */
41
+ /** Native `agy acp` is the default for new installs; stream-json remains
42
+ * a fully supported fallback. */
44
43
  export type Engine = "stream-json" | "acp";
45
44
 
46
45
  /** The engine a fresh install runs when nothing is configured (no file key,
47
46
  * no AGY_ENGINE): the official ACP server. Unknown/stale stored values also
48
- * fall back here; the first startup's acp self-heal installs the binary and
49
- * chains the sign-in. */
47
+ * fall back here. Startup checks that the required agy CLI is available. */
50
48
  export const DEFAULT_ENGINE: Engine = "acp";
51
49
  export type AgyMode = "accept-edits" | "plan";
52
50
  export type ThinkingTier = "low" | "medium" | "high";
@@ -82,8 +80,6 @@ export interface GateConfig {
82
80
  }
83
81
 
84
82
  export interface AcpConfig {
85
- /** Path to agy_acp_server.par. Empty = env AGY_ACP_BIN > PATH. */
86
- bin: string;
87
83
  /** Gate B stopgap: client-side token estimates for ACP turns so pi's
88
84
  * usage surfaces show nonzero numbers. Estimates are labeled as such in
89
85
  * /agy doctor and auto-disable when the server sends real usage. */
@@ -100,7 +96,7 @@ export interface AgyConfig {
100
96
  * is refused visibly: the agy CLI has no review-only enforcement on any
101
97
  * path (probed 2026-10-07, upstream #1181: plan mode stages a plan and
102
98
  * executes the write in the same turn on -p and stream-json alike; the
103
- * ACP engine has no plan mode at all). Built for callers like
99
+ * provider plan turns are refused on ACP too). Built for callers like
104
100
  * `pi -p --no-tools` health checks that must never mutate a workspace
105
101
  * and prefer a visible refusal over a write-capable turn. Wins over
106
102
  * `mode`; the AskAntigravity delegation tool is refused while this is on
@@ -215,7 +211,7 @@ const DEFAULTS: AgyConfig = {
215
211
  turnTimeoutMin: 0, // placeholder: the real default is resolved per loadConfig call (defaultTurnTimeoutMin)
216
212
  inactivityTimeoutMin: 5,
217
213
  approvals: { gateMode: "auto", mode: "ask" },
218
- acp: { bin: "", usageEstimate: "estimate" },
214
+ acp: { usageEstimate: "estimate" },
219
215
  };
220
216
 
221
217
  /** Hard ceiling for a free-typed turn cap: one day, in minutes. */
@@ -255,6 +251,14 @@ function readConfigFile(configPath: string): Partial<AgyConfig> {
255
251
  return {};
256
252
  }
257
253
 
254
+ export function legacyAcpBinConfigured(configPath: string = CONFIG_PATH): boolean {
255
+ if (process.env.AGY_ACP_BIN?.trim()) return true;
256
+ const file = readConfigFile(configPath) as Record<string, unknown>;
257
+ if (!file.acp || typeof file.acp !== "object" || Array.isArray(file.acp)) return false;
258
+ const bin = (file.acp as Record<string, unknown>).bin;
259
+ return typeof bin === "string" && bin.trim().length > 0;
260
+ }
261
+
258
262
  /** Load config merged over defaults. Env vars override the file when set. */
259
263
  export function loadConfig(configPath: string = CONFIG_PATH): AgyConfig {
260
264
  let file: Partial<AgyConfig> = readConfigFile(configPath);
@@ -268,8 +272,7 @@ export function loadConfig(configPath: string = CONFIG_PATH): AgyConfig {
268
272
  // One absence exception: a config file WITHOUT an engine key belongs to an
269
273
  // existing install (saveConfig persists the resolved legacy engine), and
270
274
  // the acp default is for new installs only - an existing install keeps
271
- // the legacy stream-json engine instead of silently starting the ~1.5 GB
272
- // acp download. existsSync is false on EACCES too, which reads as fresh
275
+ // the legacy stream-json engine. existsSync is false on EACCES too, which reads as fresh
273
276
  // and picks acp; an unreadable config dir is a broken install either way.
274
277
  const freshInstall = !fs.existsSync(configPath);
275
278
  const engineFallback = freshInstall || file.engine !== undefined ? DEFAULT_ENGINE : "stream-json";
@@ -379,12 +382,7 @@ export function loadConfig(configPath: string = CONFIG_PATH): AgyConfig {
379
382
  ).toLowerCase();
380
383
  const usageEstimate: UsageEstimate =
381
384
  usageRaw === "direct" || usageRaw === "off" ? usageRaw : "estimate";
382
- const acp: AcpConfig = {
383
- bin:
384
- process.env.AGY_ACP_BIN ??
385
- (typeof fileAcp.bin === "string" ? fileAcp.bin : DEFAULTS.acp.bin),
386
- usageEstimate,
387
- };
385
+ const acp: AcpConfig = { usageEstimate };
388
386
 
389
387
  // Custom agent for stream-json turns (--agent). Env wins over the file
390
388
  // (same pattern as mode). Normalized (trim/cap/control-strip); an empty
@@ -438,14 +436,14 @@ export function saveConfig(patch: Partial<AgyConfig>, configPath: string = CONFI
438
436
  // must never be baked into the file). A legacy file without an engine key
439
437
  // persists "stream-json": the bare DEFAULTS spread below would otherwise
440
438
  // write the acp default into an existing install's file on any unrelated
441
- // save, arming the ~1.5 GB download on the next start.
439
+ // save, switching an existing install to ACP on the next start.
442
440
  const persistedEngine = patch.engine ?? file.engine
443
441
  ?? (fs.existsSync(configPath) ? "stream-json" : DEFAULT_ENGINE);
444
442
  const next: AgyConfig = {
445
443
  ...DEFAULTS,
446
444
  ...file,
447
445
  ...(file.acp !== undefined
448
- ? { acp: { bin: fileAcp.bin ?? DEFAULTS.acp.bin, usageEstimate: fileAcp.usageEstimate ?? DEFAULTS.acp.usageEstimate } }
446
+ ? { acp: { usageEstimate: fileAcp.usageEstimate ?? DEFAULTS.acp.usageEstimate } }
449
447
  : {}),
450
448
  ...(file.approvals !== undefined
451
449
  ? { approvals: { gateMode: fileApprovals.gateMode ?? DEFAULTS.approvals.gateMode, mode: fileApprovals.mode ?? DEFAULTS.approvals.mode } }
@@ -7,8 +7,8 @@
7
7
  // deleted (phase 4) without breaking imports.
8
8
  //
9
9
  // The ACP driver implements the same surface with protocol-native mechanics:
10
- // no process recycle on profile drift, teardown-based abort (Gate D), and
11
- // `session/load` resume. See docs/ACP-ADOPTION-PLAN.md section 9.
10
+ // no process recycle on profile drift, notification cancel with teardown
11
+ // fallback, and `session/load` resume. See docs/ACP-ADOPTION-PLAN.md section 9.
12
12
 
13
13
  export type DriverState = "idle" | "starting" | "ready" | "running" | "dead";
14
14
 
@@ -19,7 +19,7 @@ export interface DriverProfile {
19
19
  mode: string;
20
20
  skipPermissions: boolean;
21
21
  /** Custom agy agent (stream-json `--agent`). ACP has no agent slot in the
22
- * protocol (RC01) and ignores it; the /agy agent command refuses to set
22
+ * per-turn agent selection and ignores it; the /agy agent command refuses to set
23
23
  * one while the ACP engine is active. */
24
24
  agent?: string;
25
25
  }
@@ -138,8 +138,6 @@ export interface DriverSnapshot {
138
138
  sessionsCreated: number;
139
139
  sessionsLoaded: number;
140
140
  kills: number;
141
- /** null = never probed on this server process. */
142
- cancelSupported: boolean | null;
143
141
  serverVersion?: string;
144
142
  /** Connections beyond the first this driver process made = server
145
143
  * restarts (Gate D kills + stale-exit replacements). */
@@ -1,11 +1,9 @@
1
1
  // First-run onboarding: the engine picker plus the agy-presence warning.
2
2
  //
3
3
  // On the first interactive start (no config file yet, no AGY_ENGINE env) pi
4
- // asks which turn engine to use: the stream-json `agy` CLI or Google's
5
- // official ACP server. The choice persists via saveConfig({ engine }) and,
4
+ // asks which native agy engine to use: stream-json or `agy acp`. The choice persists via saveConfig({ engine }) and,
6
5
  // like /agy engine, takes effect on the next pi start (drivers wire at load).
7
- // Every start with the stream-json engine active also re-checks that the
8
- // `agy` binary exists and warns until it does (re-auth is out of scope).
6
+ // Startup checks the CLI once per process and offers setup when needed.
9
7
  //
10
8
  // UI: the shared framed choice modal from @estebanforge/pi-tui-dialogs
11
9
  // (dialogChoice): one decision statement, a closed option list, esc cancels.
@@ -21,8 +19,8 @@ import { DEFAULT_ENGINE, type Engine } from "./config.js";
21
19
  export const ENGINE_PICKER_ITEMS: ChoiceOption[] = [
22
20
  {
23
21
  value: "acp",
24
- label: "ACP server (official Google)",
25
- description: "default · official Google server",
22
+ label: "ACP (agy acp)",
23
+ description: "default · ACP server in the agy CLI",
26
24
  },
27
25
  {
28
26
  value: "stream-json",
@@ -36,7 +34,7 @@ export const ENGINE_PICKER_ITEMS: ChoiceOption[] = [
36
34
  export const ENGINE_PICKER_INTRO = [
37
35
  "Pick the engine that runs your Antigravity turns. Switch anytime with /agy engine (restart applies it).",
38
36
  "",
39
- "ACP (default): Google's official server (agy_acp_server.par). Needs a second Google sign-in and a ~1.5 GB server binary downloaded from Google (automatic, one-time, unavoidable: the server is not part of the agy CLI).",
37
+ "ACP (default): `agy acp`, the ACP server built into the agy CLI. Uses the same installation and Google sign-in as the CLI.",
40
38
  "stream-json (fallback): the `agy` CLI you already installed and authenticated. Persistent process, streamed output.",
41
39
  ].join("\n");
42
40
 
@@ -63,12 +61,10 @@ export function toEngine(value: string): Engine {
63
61
  return value === "acp" || value === "stream-json" ? value : DEFAULT_ENGINE;
64
62
  }
65
63
 
66
- /** Toast copy shown after the choice is saved. ACP names the promise that
67
- * matters: the binary download starts NOW (not on restart), sign-in follows
68
- * when it lands, restart applies the engine. */
64
+ /** Toast copy shown after the choice is saved. */
69
65
  export function savedEngineMessage(engine: Engine): string {
70
66
  return engine === "acp"
71
- ? "Engine saved: acp. The ~1.5 GB server binary downloads now; the Google sign-in opens when it lands. Restart applies the engine."
67
+ ? "Engine saved: acp. Turns will use `agy acp` and your existing CLI authentication. Restart applies the engine."
72
68
  : "Engine saved: stream-json. Restart pi to apply.";
73
69
  }
74
70
 
@@ -84,7 +80,7 @@ export function engineSettingsRow(engine: Engine): DialogRow {
84
80
  id: ENGINE_SETTINGS_ROW_ID,
85
81
  label: "Switch engine",
86
82
  description:
87
- "Open the engine picker (stream-json CLI vs the official ACP server). Enter opens it and closes settings; esc in the picker changes nothing. Restart applies a switch.",
83
+ "Open the engine picker (stream-json CLI vs agy acp). Enter opens it and closes settings; esc in the picker changes nothing. Restart applies a switch.",
88
84
  currentValue: engine,
89
85
  };
90
86
  }
@@ -8,7 +8,7 @@
8
8
  // { "mcpServers": { "<name>": { "disabled": false,
9
9
  // "headers": { "x-bridge-token": "..." }, "serverUrl": "http://..." } } }
10
10
  //
11
- // The ACP engine does not use this file (mcpServers ride session/new).
11
+ // Native ACP gets a sanitized private copy; stream-json reads this file.
12
12
  //
13
13
  // Merge rules: foreign servers are preserved; corrupt JSON is refused (the
14
14
  // file is shared user config - never clobber); writes are atomic.
package/src/mcp-server.ts CHANGED
@@ -155,7 +155,7 @@ export function bridgeMcpConfigExists(): boolean {
155
155
  return fs.existsSync(bridgeMcpConfigPath());
156
156
  }
157
157
 
158
- function isPidAlive(pid: number): boolean {
158
+ export function isPidAlive(pid: number): boolean {
159
159
  try {
160
160
  process.kill(pid, 0);
161
161
  return true;
@@ -166,10 +166,10 @@ function isPidAlive(pid: number): boolean {
166
166
  }
167
167
 
168
168
  /** Best-effort cleanup of stale per-pid dirs left by crashed sessions. */
169
- function sweepStaleBridgeDirs(): void {
169
+ export function sweepStaleBridgeDirs(baseDir: string = BRIDGE_BASE, alive: (pid: number) => boolean = isPidAlive): void {
170
170
  let entries: string[];
171
171
  try {
172
- entries = fs.readdirSync(BRIDGE_BASE);
172
+ entries = fs.readdirSync(baseDir);
173
173
  } catch {
174
174
  return;
175
175
  }
@@ -177,9 +177,9 @@ function sweepStaleBridgeDirs(): void {
177
177
  if (!name.startsWith("agy-mcp-")) continue;
178
178
  const pid = Number(name.slice("agy-mcp-".length));
179
179
  if (!Number.isInteger(pid) || pid === process.pid) continue;
180
- if (isPidAlive(pid)) continue;
180
+ if (alive(pid)) continue;
181
181
  try {
182
- fs.rmSync(path.join(BRIDGE_BASE, name), { recursive: true, force: true });
182
+ fs.rmSync(path.join(baseDir, name), { recursive: true, force: true });
183
183
  } catch {
184
184
  /* best effort */
185
185
  }
package/src/provider.ts CHANGED
@@ -1213,7 +1213,7 @@ function acpEditFileArg(args: Record<string, unknown>): string | undefined {
1213
1213
  // Edit display has TWO paths on ACP:
1214
1214
  // a) native diff in tool_call content[] (future builds / the
1215
1215
  // permission-request flow carries it; phase-2 probe shape);
1216
- // b) RC01 with the auto policy sends NO content: the file simply
1216
+ // b) auto-approved edits can send NO content: the file simply
1217
1217
  // lands on disk. Read it and diff against git HEAD - the same
1218
1218
  // output the stream-json engine produces, one readFileSync.
1219
1219
  if (feats.engine === "acp") {
@@ -1802,8 +1802,8 @@ export function createStreamSimple(
1802
1802
  const config = loadConfig();
1803
1803
  const engine = deps.engine ?? config.engine;
1804
1804
  const selected = engine === "acp" && deps.acpDriver ? deps.acpDriver : deps.driver;
1805
- // Plan-shaped provider turns are fail-closed on BOTH engines. ACP has
1806
- // no plan mode at all (RC01). Stream-json is worse and probed
1805
+ // Plan-shaped provider turns are fail-closed on BOTH engines because
1806
+ // agy does not enforce review-only writes. Stream-json, probed
1807
1807
  // 2026-10-07: the engine WARNS that --mode plan is a no-op while
1808
1808
  // --disable-slash-commands is set, executes file writes even with
1809
1809
  // slash expansion left on (it stages a plan.md and still writes in the
@@ -1828,7 +1828,7 @@ export function createStreamSimple(
1828
1828
  stream,
1829
1829
  blocks,
1830
1830
  "error",
1831
- "Plan mode cannot be enforced on provider turns: the agy CLI stages a plan and executes writes in the same turn (probed 2026-10-07, google-antigravity/antigravity-cli#1181), and the ACP engine has no plan mode (RC01). /agy mode accept-edits, and enforce review-only in the host.",
1831
+ "Plan mode cannot be enforced on provider turns: the agy CLI stages a plan and executes writes in the same turn (probed 2026-10-07, google-antigravity/antigravity-cli#1181), and provider plan turns are disabled on ACP too. /agy mode accept-edits, and enforce review-only in the host.",
1832
1832
  );
1833
1833
  return stream;
1834
1834
  }
@@ -5,9 +5,8 @@
5
5
  // - "send-immediately": interrupt the agent with follow-up / steering messages immediately
6
6
  //
7
7
  // Pi expects immediate message delivery / steering during interactive chat.
8
- // This module ensures that both the stream-json engine (~/.gemini/antigravity-cli/settings.json)
9
- // and ACP engine (~/.gemini/antigravity-acp/settings.json) default to "send-immediately"
10
- // so user steering works as expected in Pi without manual user configuration.
8
+ // Both engines share ~/.gemini/antigravity-cli/settings.json. Keep immediate
9
+ // steering as the default without changing an explicit user choice.
11
10
 
12
11
  import fs from "node:fs";
13
12
  import os from "node:os";
@@ -19,10 +18,6 @@ export function defaultCliSettingsPath(): string {
19
18
  return path.join(os.homedir(), ".gemini", "antigravity-cli", "settings.json");
20
19
  }
21
20
 
22
- export function defaultAcpSettingsPath(): string {
23
- return path.join(os.homedir(), ".gemini", "antigravity-acp", "settings.json");
24
- }
25
-
26
21
  export interface EnsureFileResult {
27
22
  path: string;
28
23
  updated: boolean;
@@ -143,29 +138,15 @@ export function ensureQueuedMessagesInFile(
143
138
 
144
139
  export interface EnsureQueuedMessagesOptions {
145
140
  cliSettingsPath?: string;
146
- acpSettingsPath?: string;
147
141
  defaultValue?: string;
148
142
  onlyIfDirExists?: boolean;
149
143
  }
150
144
 
151
- export interface EnsureQueuedMessagesReport {
152
- cli: EnsureFileResult;
153
- acp: EnsureFileResult;
154
- }
155
-
156
- /**
157
- * Ensure both stream-json (CLI) and ACP engines default to "send-immediately".
158
- */
159
145
  export function ensureQueuedMessagesDefaults(
160
146
  opts: EnsureQueuedMessagesOptions = {},
161
- ): EnsureQueuedMessagesReport {
162
- const cliPath = opts.cliSettingsPath ?? defaultCliSettingsPath();
163
- const acpPath = opts.acpSettingsPath ?? defaultAcpSettingsPath();
164
- const defVal = opts.defaultValue ?? QUEUED_MESSAGES_SEND_IMMEDIATELY;
165
- const onlyIfDirExists = opts.onlyIfDirExists ?? false;
166
-
167
- const cli = ensureQueuedMessagesInFile(cliPath, { defaultValue: defVal, onlyIfDirExists });
168
- const acp = ensureQueuedMessagesInFile(acpPath, { defaultValue: defVal, onlyIfDirExists });
169
-
170
- return { cli, acp };
147
+ ): EnsureFileResult {
148
+ return ensureQueuedMessagesInFile(opts.cliSettingsPath ?? defaultCliSettingsPath(), {
149
+ defaultValue: opts.defaultValue ?? QUEUED_MESSAGES_SEND_IMMEDIATELY,
150
+ onlyIfDirExists: opts.onlyIfDirExists ?? false,
151
+ });
171
152
  }