@quantiya/codevibe-antigravity-plugin 2.0.25 → 2.0.26

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.
@@ -59,8 +59,7 @@ async function ensureInstalled(options) {
59
59
  const {
60
60
  pluginJsonSourcePath,
61
61
  installDir,
62
- mcpConfigPath,
63
- serverScriptPath
62
+ mcpConfigPath
64
63
  } = options;
65
64
  const pid = options.pidOverride ?? process.pid;
66
65
  let manifestContent;
@@ -94,7 +93,7 @@ async function ensureInstalled(options) {
94
93
  await crashRecoverySweep(installDir, logger);
95
94
  const currentHash = await tryReadCurrentHash(installDir);
96
95
  if (currentHash === expectedManifestHash) {
97
- await ensureMcpConfigEntry(mcpConfigPath, serverScriptPath, logger);
96
+ await ensureMcpConfigCourtesy(mcpConfigPath, logger);
98
97
  return { installed: false, upToDate: true, installDir, manifestHash: currentHash };
99
98
  }
100
99
  await stageAndInstall({
@@ -105,7 +104,7 @@ async function ensureInstalled(options) {
105
104
  pid,
106
105
  logger
107
106
  });
108
- await ensureMcpConfigEntry(mcpConfigPath, serverScriptPath, logger);
107
+ await ensureMcpConfigCourtesy(mcpConfigPath, logger);
109
108
  logger.info("codevibe-antigravity-plugin installed", {
110
109
  installDir,
111
110
  manifestHash: expectedManifestHash
@@ -200,7 +199,7 @@ async function crashRecoverySweep(installDir, logger) {
200
199
  await rmRecursive(dir);
201
200
  }
202
201
  }
203
- async function ensureMcpConfigEntry(mcpConfigPath, serverScriptPath, logger) {
202
+ async function ensureMcpConfigCourtesy(mcpConfigPath, logger) {
204
203
  try {
205
204
  await fs.promises.mkdir(path.dirname(mcpConfigPath), { recursive: true });
206
205
  } catch (err) {
@@ -215,14 +214,15 @@ async function ensureMcpConfigEntry(mcpConfigPath, serverScriptPath, logger) {
215
214
  const lockPath = `${mcpConfigPath}.lock`;
216
215
  const releaseLock = await acquireLock(lockPath, LOCK_ACQUIRE_TIMEOUT_MS, logger, "mcp-config");
217
216
  try {
218
- await doEnsureMcpConfigEntry(mcpConfigPath, serverScriptPath, logger);
217
+ await doEnsureMcpConfigCourtesy(mcpConfigPath, logger);
219
218
  } finally {
220
219
  await releaseLock();
221
220
  }
222
221
  }
223
- async function doEnsureMcpConfigEntry(mcpConfigPath, serverScriptPath, logger) {
222
+ async function doEnsureMcpConfigCourtesy(mcpConfigPath, logger) {
224
223
  let config = {};
225
224
  let existingMode = null;
225
+ let onDiskWasValid = false;
226
226
  try {
227
227
  const stat = await fs.promises.stat(mcpConfigPath);
228
228
  existingMode = stat.mode & 511;
@@ -232,6 +232,7 @@ async function doEnsureMcpConfigEntry(mcpConfigPath, serverScriptPath, logger) {
232
232
  const parsed = JSON.parse(raw);
233
233
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
234
234
  config = parsed;
235
+ onDiskWasValid = !!config.mcpServers && typeof config.mcpServers === "object" && !Array.isArray(config.mcpServers);
235
236
  } else {
236
237
  logger.warn("mcp_config.json root is not a plain object; overwriting", {
237
238
  mcpConfigPath
@@ -250,23 +251,18 @@ async function doEnsureMcpConfigEntry(mcpConfigPath, serverScriptPath, logger) {
250
251
  if (!config.mcpServers || typeof config.mcpServers !== "object" || Array.isArray(config.mcpServers)) {
251
252
  config.mcpServers = {};
252
253
  }
253
- const expected = {
254
- command: "node",
255
- args: [serverScriptPath]
256
- };
257
- const current = config.mcpServers[MCP_SERVERS_KEY];
258
- if (current?.env && typeof current.env === "object" && !Array.isArray(current.env)) {
259
- expected.env = current.env;
254
+ const hadStaleEntry = Object.prototype.hasOwnProperty.call(config.mcpServers, MCP_SERVERS_KEY);
255
+ if (hadStaleEntry) {
256
+ delete config.mcpServers[MCP_SERVERS_KEY];
260
257
  }
261
- const envEqual = !current?.env && !expected.env ? true : JSON.stringify(current?.env ?? null) === JSON.stringify(expected.env ?? null);
262
- const needsWrite = !current || current.command !== expected.command || !arraysEqual(current.args ?? [], expected.args) || !envEqual;
258
+ const needsWrite = hadStaleEntry || !onDiskWasValid;
263
259
  if (!needsWrite) return;
264
- config.mcpServers[MCP_SERVERS_KEY] = expected;
265
260
  const targetMode = existingMode ?? 420;
266
261
  const randSuffix = `${process.pid}.${Date.now()}.${crypto.randomBytes(6).toString("hex")}`;
267
262
  const tmpPath = `${mcpConfigPath}.tmp.${randSuffix}`;
268
263
  const tmpFd = await fs.promises.open(tmpPath, "wx", targetMode);
269
264
  try {
265
+ if (existingMode !== null) await tmpFd.chmod(existingMode);
270
266
  await tmpFd.writeFile(JSON.stringify(config, null, 2) + "\n", "utf8");
271
267
  await tmpFd.close();
272
268
  } catch (writeErr) {
@@ -289,10 +285,10 @@ async function doEnsureMcpConfigEntry(mcpConfigPath, serverScriptPath, logger) {
289
285
  }
290
286
  throw err;
291
287
  }
292
- logger.info("Updated mcp_config.json with codevibe-antigravity entry", {
293
- mcpConfigPath,
294
- serverScriptPath
295
- });
288
+ logger.info(
289
+ hadStaleEntry ? "Removed the stale codevibe-antigravity MCP server entry from mcp_config.json (the daemon is launched by the wrapper only)" : "Wrote the mcp_config.json courtesy object",
290
+ { mcpConfigPath, removedStaleEntry: hadStaleEntry }
291
+ );
296
292
  }
297
293
  function sha256Hex(s) {
298
294
  return crypto.createHash("sha256").update(s, "utf8").digest("hex");
@@ -425,11 +421,6 @@ async function rmRecursive(p) {
425
421
  } catch {
426
422
  }
427
423
  }
428
- function arraysEqual(a, b) {
429
- if (a.length !== b.length) return false;
430
- for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
431
- return true;
432
- }
433
424
  function defaultLogger() {
434
425
  return {
435
426
  info: () => {
package/dist/server.js CHANGED
@@ -34,10 +34,12 @@ __export(server_exports, {
34
34
  SessionNotFoundError: () => SessionNotFoundError,
35
35
  TERMINAL_SHUTDOWN_SIGNALS: () => TERMINAL_SHUTDOWN_SIGNALS,
36
36
  __testing: () => __testing,
37
+ classifyEntrypoint: () => classifyEntrypoint,
37
38
  classifyTmuxHasSessionError: () => classifyTmuxHasSessionError,
38
39
  generateLaunchSessionId: () => generateLaunchSessionId,
39
40
  getActiveConversationFromCliLog: () => getActiveConversationFromCliLog,
40
- parseMaybeJson: () => parseMaybeJson
41
+ parseMaybeJson: () => parseMaybeJson,
42
+ runForeignSpawnIdle: () => runForeignSpawnIdle
41
43
  });
42
44
  module.exports = __toCommonJS(server_exports);
43
45
  var crypto3 = __toESM(require("crypto"));
@@ -5350,8 +5352,51 @@ function getActiveConversationFromCliLog(cliLogPath) {
5350
5352
  return null;
5351
5353
  }
5352
5354
  }
5355
+ function classifyEntrypoint(args) {
5356
+ return args.runtimeDir ? "wrapper" : "foreign";
5357
+ }
5358
+ function runForeignSpawnIdle(io) {
5359
+ let done = false;
5360
+ const finish = () => {
5361
+ if (done) return;
5362
+ done = true;
5363
+ io.exit(0);
5364
+ };
5365
+ io.writeStderr(
5366
+ "codevibe-antigravity-plugin: started without --runtime-dir, so not by the CodeVibe companion launcher (an agy mcp_config.json entry from an earlier release?). Refusing to start the daemon: no session, no backend calls. Run `codevibe --agent agy` once to remove the stale entry. Idling until the spawner closes stdin.\n"
5367
+ );
5368
+ void io.beacon().catch(() => void 0);
5369
+ io.onSignal(finish);
5370
+ io.stdin.on("end", finish);
5371
+ io.stdin.on("close", finish);
5372
+ io.stdin.on("error", finish);
5373
+ io.stdin.resume();
5374
+ return () => {
5375
+ done = true;
5376
+ };
5377
+ }
5353
5378
  async function main() {
5354
5379
  const args = parseArgs(process.argv.slice(2));
5380
+ if (classifyEntrypoint(args) === "foreign") {
5381
+ runForeignSpawnIdle({
5382
+ stdin: process.stdin,
5383
+ writeStderr: (line) => {
5384
+ process.stderr.write(line);
5385
+ },
5386
+ onSignal: (cb) => {
5387
+ process.once("SIGINT", cb);
5388
+ process.once("SIGTERM", cb);
5389
+ },
5390
+ exit: (code) => process.exit(code),
5391
+ beacon: () => fireDaemonBeacon("daemon_init_step", {
5392
+ step: "foreign_spawn",
5393
+ outcome: "refused",
5394
+ has_wrapper_pid: process.env.CODEVIBE_AGY_WRAPPER_PID ? "yes" : "no",
5395
+ has_tmux_target: process.env.CODEVIBE_AGY_TMUX_TARGET ? "yes" : "no"
5396
+ })
5397
+ });
5398
+ return;
5399
+ }
5355
5400
  const bearerToken = process.env.CODEVIBE_AGY_MCP_TOKEN || args.token;
5356
5401
  if (!bearerToken) {
5357
5402
  console.error("codevibe-antigravity-plugin: bearer token required via $CODEVIBE_AGY_MCP_TOKEN or --token");
@@ -5493,8 +5538,10 @@ var __testing = {
5493
5538
  SessionNotFoundError,
5494
5539
  TERMINAL_SHUTDOWN_SIGNALS,
5495
5540
  __testing,
5541
+ classifyEntrypoint,
5496
5542
  classifyTmuxHasSessionError,
5497
5543
  generateLaunchSessionId,
5498
5544
  getActiveConversationFromCliLog,
5499
- parseMaybeJson
5545
+ parseMaybeJson,
5546
+ runForeignSpawnIdle
5500
5547
  });
@@ -6,8 +6,10 @@
6
6
  # Architecture (DESIGN.md §3 + §5.2):
7
7
  #
8
8
  # 1. ensureInstalled (idempotent): writes plugin manifest to
9
- # ~/.gemini/antigravity-cli/plugins/codevibe-antigravity/ + wires
10
- # ~/.gemini/config/mcp_config.json's mcpServers entry.
9
+ # ~/.gemini/antigravity-cli/plugins/codevibe-antigravity/ and REMOVES any
10
+ # stale codevibe-antigravity entry from ~/.gemini/config/mcp_config.json
11
+ # (P42, 2026-09-09: a registered entry made agy spawn a second, argument-
12
+ # less daemon every few minutes; the daemon is launched by this launcher only).
11
13
  #
12
14
  # 2. Generate ephemeral port + bearer token; create runtime dir at
13
15
  # ~/.gemini/antigravity-cli/plugins/codevibe-antigravity/runtime/$$
@@ -279,9 +281,10 @@ fi
279
281
 
280
282
  # ─── Atomic install of plugin footprint (DESIGN.md §5.1) ───────────────
281
283
  # Runs ensureInstalled which writes antigravity-plugin.json to
282
- # ~/.gemini/antigravity-cli/plugins/codevibe-antigravity/ + adds the
283
- # mcpServers entry to ~/.gemini/config/mcp_config.json. Idempotent —
284
- # subsequent runs are no-ops if the manifest hash matches.
284
+ # ~/.gemini/antigravity-cli/plugins/codevibe-antigravity/ and removes any
285
+ # stale codevibe-antigravity entry from ~/.gemini/config/mcp_config.json
286
+ # (P42). Idempotent — subsequent runs are no-ops once the manifest hash
287
+ # matches and no stale entry remains.
285
288
  log "Running ensureInstalled"
286
289
  if ! node "$PLUGIN_DIR/dist/installer-cli.js"; then
287
290
  echo "Error: codevibe-antigravity installer failed."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantiya/codevibe-antigravity-plugin",
3
- "version": "2.0.25",
3
+ "version": "2.0.26",
4
4
  "description": "Control Antigravity CLI from your iPhone and Android — real-time sync, approve file edits, send prompts by voice. Part of CodeVibe.",
5
5
  "main": "dist/server.js",
6
6
  "codevibe": {
@@ -18,8 +18,6 @@
18
18
  "build": "rm -rf dist && npm run typecheck && esbuild src/server.ts src/installer-cli.ts --bundle --platform=node --target=node18 --packages=external --outdir=dist",
19
19
  "prepack": "npm run build",
20
20
  "postpack": "echo '[codevibe-antigravity-plugin] postpack: noop'",
21
- "dev": "node dist/server.js",
22
- "start": "node dist/server.js",
23
21
  "test": "jest --config jest.config.js --forceExit"
24
22
  },
25
23
  "keywords": [