@quantiya/codevibe-antigravity-plugin 1.0.13 → 1.0.14
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/bin/codevibe-agy +13 -17
- package/dist/installer-cli.js +17 -26
- package/dist/server.js +49 -2
- package/package.json +1 -3
package/bin/codevibe-agy
CHANGED
|
@@ -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/
|
|
10
|
-
# ~/.gemini/config/mcp_config.json
|
|
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 wrapper only).
|
|
11
13
|
#
|
|
12
14
|
# 2. Generate ephemeral port + bearer token; create runtime dir at
|
|
13
15
|
# ~/.gemini/antigravity-cli/plugins/codevibe-antigravity/runtime/$$
|
|
@@ -240,9 +242,10 @@ fi
|
|
|
240
242
|
|
|
241
243
|
# ─── Atomic install of plugin footprint (DESIGN.md §5.1) ───────────────
|
|
242
244
|
# Runs ensureInstalled which writes antigravity-plugin.json to
|
|
243
|
-
# ~/.gemini/antigravity-cli/plugins/codevibe-antigravity/
|
|
244
|
-
#
|
|
245
|
-
# subsequent runs are no-ops
|
|
245
|
+
# ~/.gemini/antigravity-cli/plugins/codevibe-antigravity/ and removes any
|
|
246
|
+
# stale codevibe-antigravity entry from ~/.gemini/config/mcp_config.json
|
|
247
|
+
# (P42). Idempotent — subsequent runs are no-ops once the manifest hash
|
|
248
|
+
# matches and no stale entry remains.
|
|
246
249
|
log "Running ensureInstalled"
|
|
247
250
|
if ! node "$PLUGIN_DIR/dist/installer-cli.js"; then
|
|
248
251
|
echo "Error: codevibe-antigravity installer failed."
|
|
@@ -340,21 +343,14 @@ cleanup() {
|
|
|
340
343
|
cv_telem "wrapper_exited" "\"exit_code\":$wrapper_exit_code,\"lifetime_seconds\":$lifetime,\"agy_exit_code\":\"$agy_exit\",\"agy_lifetime_seconds\":$agy_lifetime,\"tmux_session_started\":$_CV_TMUX_STARTED,\"agent_invoked\":$_CV_AGENT_INVOKED,\"terminal_outcome\":\"$outcome\""
|
|
341
344
|
fi
|
|
342
345
|
|
|
343
|
-
# Stop the MCP server
|
|
344
|
-
#
|
|
346
|
+
# Stop the MCP server gracefully and wait for product-owned cleanup. Never
|
|
347
|
+
# force-kill here: stop() must finish its hosted INACTIVE obligation before
|
|
348
|
+
# the process exits, even when that takes longer than the old 3s bound.
|
|
349
|
+
# (P42 backport of the 2.0 launcher: a daemon force-terminated 3s after TERM,
|
|
350
|
+
# mid-write, left an ACTIVE row that only the next launch's 6-minute sweep reaped.)
|
|
345
351
|
if [ -n "$MCP_PID" ] && kill -0 "$MCP_PID" 2>/dev/null; then
|
|
346
352
|
log "Stopping MCP server (PID: $MCP_PID)"
|
|
347
353
|
kill -TERM "$MCP_PID" 2>/dev/null || true
|
|
348
|
-
# Wait up to 3s for graceful shutdown.
|
|
349
|
-
local i=0
|
|
350
|
-
while [ $i -lt 30 ] && kill -0 "$MCP_PID" 2>/dev/null; do
|
|
351
|
-
sleep 0.1
|
|
352
|
-
i=$((i + 1))
|
|
353
|
-
done
|
|
354
|
-
if kill -0 "$MCP_PID" 2>/dev/null; then
|
|
355
|
-
log "MCP server did not exit cleanly; sending SIGKILL"
|
|
356
|
-
kill -KILL "$MCP_PID" 2>/dev/null || true
|
|
357
|
-
fi
|
|
358
354
|
wait "$MCP_PID" 2>/dev/null || true
|
|
359
355
|
fi
|
|
360
356
|
|
package/dist/installer-cli.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
217
|
+
await doEnsureMcpConfigCourtesy(mcpConfigPath, logger);
|
|
219
218
|
} finally {
|
|
220
219
|
await releaseLock();
|
|
221
220
|
}
|
|
222
221
|
}
|
|
223
|
-
async function
|
|
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
|
|
254
|
-
|
|
255
|
-
|
|
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
|
|
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(
|
|
293
|
-
|
|
294
|
-
|
|
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
|
@@ -33,9 +33,11 @@ __export(server_exports, {
|
|
|
33
33
|
McpServer: () => McpServer,
|
|
34
34
|
SessionNotFoundError: () => SessionNotFoundError,
|
|
35
35
|
__testing: () => __testing,
|
|
36
|
+
classifyEntrypoint: () => classifyEntrypoint,
|
|
36
37
|
generateLaunchSessionId: () => generateLaunchSessionId,
|
|
37
38
|
getActiveConversationFromCliLog: () => getActiveConversationFromCliLog,
|
|
38
|
-
parseMaybeJson: () => parseMaybeJson
|
|
39
|
+
parseMaybeJson: () => parseMaybeJson,
|
|
40
|
+
runForeignSpawnIdle: () => runForeignSpawnIdle
|
|
39
41
|
});
|
|
40
42
|
module.exports = __toCommonJS(server_exports);
|
|
41
43
|
var crypto3 = __toESM(require("crypto"));
|
|
@@ -3828,8 +3830,51 @@ function getActiveConversationFromCliLog(cliLogPath) {
|
|
|
3828
3830
|
return null;
|
|
3829
3831
|
}
|
|
3830
3832
|
}
|
|
3833
|
+
function classifyEntrypoint(args) {
|
|
3834
|
+
return args.runtimeDir ? "wrapper" : "foreign";
|
|
3835
|
+
}
|
|
3836
|
+
function runForeignSpawnIdle(io) {
|
|
3837
|
+
let done = false;
|
|
3838
|
+
const finish = () => {
|
|
3839
|
+
if (done) return;
|
|
3840
|
+
done = true;
|
|
3841
|
+
io.exit(0);
|
|
3842
|
+
};
|
|
3843
|
+
io.writeStderr(
|
|
3844
|
+
"codevibe-antigravity-plugin: started without --runtime-dir, so not by the codevibe-agy wrapper (an agy mcp_config.json entry from an earlier release?). Refusing to start the daemon: no session, no backend calls. Run `codevibe-agy` once to remove the stale entry. Idling until the spawner closes stdin.\n"
|
|
3845
|
+
);
|
|
3846
|
+
void io.beacon().catch(() => void 0);
|
|
3847
|
+
io.onSignal(finish);
|
|
3848
|
+
io.stdin.on("end", finish);
|
|
3849
|
+
io.stdin.on("close", finish);
|
|
3850
|
+
io.stdin.on("error", finish);
|
|
3851
|
+
io.stdin.resume();
|
|
3852
|
+
return () => {
|
|
3853
|
+
done = true;
|
|
3854
|
+
};
|
|
3855
|
+
}
|
|
3831
3856
|
async function main() {
|
|
3832
3857
|
const args = parseArgs(process.argv.slice(2));
|
|
3858
|
+
if (classifyEntrypoint(args) === "foreign") {
|
|
3859
|
+
runForeignSpawnIdle({
|
|
3860
|
+
stdin: process.stdin,
|
|
3861
|
+
writeStderr: (line) => {
|
|
3862
|
+
process.stderr.write(line);
|
|
3863
|
+
},
|
|
3864
|
+
onSignal: (cb) => {
|
|
3865
|
+
process.once("SIGINT", cb);
|
|
3866
|
+
process.once("SIGTERM", cb);
|
|
3867
|
+
},
|
|
3868
|
+
exit: (code) => process.exit(code),
|
|
3869
|
+
beacon: () => fireDaemonBeacon("daemon_init_step", {
|
|
3870
|
+
step: "foreign_spawn",
|
|
3871
|
+
outcome: "refused",
|
|
3872
|
+
has_wrapper_pid: process.env.CODEVIBE_AGY_WRAPPER_PID ? "yes" : "no",
|
|
3873
|
+
has_tmux_target: process.env.CODEVIBE_AGY_TMUX_TARGET ? "yes" : "no"
|
|
3874
|
+
})
|
|
3875
|
+
});
|
|
3876
|
+
return;
|
|
3877
|
+
}
|
|
3833
3878
|
const bearerToken = process.env.CODEVIBE_AGY_MCP_TOKEN || args.token;
|
|
3834
3879
|
if (!bearerToken) {
|
|
3835
3880
|
console.error("codevibe-antigravity-plugin: bearer token required via $CODEVIBE_AGY_MCP_TOKEN or --token");
|
|
@@ -3965,7 +4010,9 @@ var __testing = {
|
|
|
3965
4010
|
McpServer,
|
|
3966
4011
|
SessionNotFoundError,
|
|
3967
4012
|
__testing,
|
|
4013
|
+
classifyEntrypoint,
|
|
3968
4014
|
generateLaunchSessionId,
|
|
3969
4015
|
getActiveConversationFromCliLog,
|
|
3970
|
-
parseMaybeJson
|
|
4016
|
+
parseMaybeJson,
|
|
4017
|
+
runForeignSpawnIdle
|
|
3971
4018
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quantiya/codevibe-antigravity-plugin",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.14",
|
|
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
|
"bin": {
|
|
@@ -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": [
|