@quantiya/codevibe-antigravity-plugin 2.0.11 → 2.0.13
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/dist/server.js +179 -26
- package/libexec/companion-launcher +40 -12
- package/package.json +2 -2
package/dist/server.js
CHANGED
|
@@ -32,7 +32,9 @@ var server_exports = {};
|
|
|
32
32
|
__export(server_exports, {
|
|
33
33
|
McpServer: () => McpServer,
|
|
34
34
|
SessionNotFoundError: () => SessionNotFoundError,
|
|
35
|
+
TERMINAL_SHUTDOWN_SIGNALS: () => TERMINAL_SHUTDOWN_SIGNALS,
|
|
35
36
|
__testing: () => __testing,
|
|
37
|
+
classifyTmuxHasSessionError: () => classifyTmuxHasSessionError,
|
|
36
38
|
generateLaunchSessionId: () => generateLaunchSessionId,
|
|
37
39
|
getActiveConversationFromCliLog: () => getActiveConversationFromCliLog,
|
|
38
40
|
parseMaybeJson: () => parseMaybeJson
|
|
@@ -42,6 +44,8 @@ var crypto3 = __toESM(require("crypto"));
|
|
|
42
44
|
var path5 = __toESM(require("path"));
|
|
43
45
|
var fs5 = __toESM(require("fs"));
|
|
44
46
|
var os5 = __toESM(require("os"));
|
|
47
|
+
var import_child_process3 = require("child_process");
|
|
48
|
+
var import_util3 = require("util");
|
|
45
49
|
var import_codevibe_core4 = require("@quantiya/codevibe-core");
|
|
46
50
|
|
|
47
51
|
// src/logger.ts
|
|
@@ -2977,6 +2981,27 @@ function truncate(s, maxBytes) {
|
|
|
2977
2981
|
// src/server.ts
|
|
2978
2982
|
var MOBILE_PROMPT_FLOOR_RECENCY_MS = 12e4;
|
|
2979
2983
|
var LAUNCH_SETTLE_TIMEOUT_MS = 3e3;
|
|
2984
|
+
var TMUX_LIFECYCLE_POLL_MS = 1e3;
|
|
2985
|
+
var TMUX_LIFECYCLE_INIT_GRACE_MS = 1e4;
|
|
2986
|
+
var SESSION_RETIRE_MAX_ATTEMPTS = 3;
|
|
2987
|
+
var SESSION_RETIRE_RETRY_MS = 100;
|
|
2988
|
+
var execFileAsync = (0, import_util3.promisify)(import_child_process3.execFile);
|
|
2989
|
+
var TERMINAL_SHUTDOWN_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
2990
|
+
function commandErrorText(error, field) {
|
|
2991
|
+
const value = error?.[field];
|
|
2992
|
+
if (typeof value === "string") return value;
|
|
2993
|
+
if (Buffer.isBuffer(value)) return value.toString("utf8");
|
|
2994
|
+
return "";
|
|
2995
|
+
}
|
|
2996
|
+
function classifyTmuxHasSessionError(error) {
|
|
2997
|
+
if (typeof error?.code !== "number") return "unknown";
|
|
2998
|
+
const output = `${commandErrorText(error, "stderr")}
|
|
2999
|
+
${commandErrorText(error, "stdout")}`;
|
|
3000
|
+
if (/^can't find session:/m.test(output) || /^no server running on /m.test(output) || /^error connecting to .* \((?:No such file or directory|Connection refused)\)$/m.test(output)) {
|
|
3001
|
+
return "absent";
|
|
3002
|
+
}
|
|
3003
|
+
return "unknown";
|
|
3004
|
+
}
|
|
2980
3005
|
var McpServer = class _McpServer {
|
|
2981
3006
|
constructor(options) {
|
|
2982
3007
|
/** Per-session causal floor for transcript-event timestamps emitted right
|
|
@@ -3117,6 +3142,11 @@ var McpServer = class _McpServer {
|
|
|
3117
3142
|
this.signalsRegistered = false;
|
|
3118
3143
|
this.boundSigintHandler = null;
|
|
3119
3144
|
this.boundSigtermHandler = null;
|
|
3145
|
+
this.boundSighupHandler = null;
|
|
3146
|
+
this.tmuxLifecycleTimer = null;
|
|
3147
|
+
this.tmuxLifecycleCheckInFlight = false;
|
|
3148
|
+
this.tmuxLifecycleObservedAlive = false;
|
|
3149
|
+
this.tmuxLifecycleInitDeadlineMs = Number.POSITIVE_INFINITY;
|
|
3120
3150
|
if (!options.bearerToken || options.bearerToken.length < 16) {
|
|
3121
3151
|
throw new Error("McpServer requires a non-trivial bearerToken");
|
|
3122
3152
|
}
|
|
@@ -3156,6 +3186,8 @@ var McpServer = class _McpServer {
|
|
|
3156
3186
|
async start() {
|
|
3157
3187
|
if (this.started) throw new Error("McpServer.start() called twice");
|
|
3158
3188
|
this.stopPromise = null;
|
|
3189
|
+
this.stopTmuxLifecycleMonitor();
|
|
3190
|
+
this.tmuxLifecycleObservedAlive = false;
|
|
3159
3191
|
await fireDaemonBeacon("daemon_init_start", {
|
|
3160
3192
|
step: "init",
|
|
3161
3193
|
outcome: "ok",
|
|
@@ -3176,6 +3208,7 @@ var McpServer = class _McpServer {
|
|
|
3176
3208
|
this.lifecycleGen++;
|
|
3177
3209
|
this.stopRequestedDuringCreate = false;
|
|
3178
3210
|
this.registerSignalHandlers();
|
|
3211
|
+
this.startTmuxLifecycleMonitor();
|
|
3179
3212
|
await (0, import_codevibe_core4.registerDeviceEncryptionKey)(this.appSyncClient, logger);
|
|
3180
3213
|
if (!this.started) return { httpPort: 0 };
|
|
3181
3214
|
(0, import_codevibe_core4.startDeviceKeyWatcher)(this.appSyncClient, logger);
|
|
@@ -3259,15 +3292,18 @@ var McpServer = class _McpServer {
|
|
|
3259
3292
|
}
|
|
3260
3293
|
async doStop() {
|
|
3261
3294
|
this.started = false;
|
|
3262
|
-
|
|
3263
|
-
|
|
3295
|
+
this.stopTmuxLifecycleMonitor();
|
|
3296
|
+
const launchSettlement = this.launchSessionPromise?.catch(() => void 0) ?? null;
|
|
3297
|
+
let mustAwaitLaunchSettlement = false;
|
|
3298
|
+
let hostedRetirementError = null;
|
|
3299
|
+
if (launchSettlement) {
|
|
3264
3300
|
let timer;
|
|
3265
3301
|
const timeout = new Promise((resolve3) => {
|
|
3266
3302
|
timer = setTimeout(resolve3, this.launchSettleTimeoutMs);
|
|
3267
3303
|
timer.unref?.();
|
|
3268
3304
|
});
|
|
3269
3305
|
try {
|
|
3270
|
-
await Promise.race([
|
|
3306
|
+
await Promise.race([launchSettlement, timeout]);
|
|
3271
3307
|
} finally {
|
|
3272
3308
|
if (timer) clearTimeout(timer);
|
|
3273
3309
|
}
|
|
@@ -3279,8 +3315,9 @@ var McpServer = class _McpServer {
|
|
|
3279
3315
|
if (deactivated) {
|
|
3280
3316
|
if (this.pendingLaunchSessionId === pendingId) this.pendingLaunchSessionId = null;
|
|
3281
3317
|
} else {
|
|
3318
|
+
mustAwaitLaunchSettlement = launchSettlement !== null;
|
|
3282
3319
|
logger.error(
|
|
3283
|
-
"doStop: INACTIVE write for the pending launch row FAILED (row may not exist yet \u2014 create still in flight). Keeping the settlement obligation: if the create resolves in-process its tail deactivates the row;
|
|
3320
|
+
"doStop: INACTIVE write for the pending launch row FAILED (row may not exist yet \u2014 create still in flight). Keeping the settlement obligation: if the create resolves in-process its tail deactivates the row; the process remains alive so the delayed create tail can settle it (#638 H4)",
|
|
3284
3321
|
{ sessionId: pendingId }
|
|
3285
3322
|
);
|
|
3286
3323
|
}
|
|
@@ -3304,16 +3341,11 @@ var McpServer = class _McpServer {
|
|
|
3304
3341
|
this.appSyncClient.stopHeartbeat(this.session.sessionId);
|
|
3305
3342
|
} catch {
|
|
3306
3343
|
}
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
} catch (err) {
|
|
3313
|
-
logger.warn("updateSession INACTIVE failed during shutdown", {
|
|
3314
|
-
sessionId: this.session.sessionId,
|
|
3315
|
-
error: String(err)
|
|
3316
|
-
});
|
|
3344
|
+
const retired = await this.deactivateLaunchRow(this.session.sessionId);
|
|
3345
|
+
if (!retired) {
|
|
3346
|
+
hostedRetirementError = new Error(
|
|
3347
|
+
`Failed to retire hosted Antigravity session ${this.session.sessionId}`
|
|
3348
|
+
);
|
|
3317
3349
|
}
|
|
3318
3350
|
}
|
|
3319
3351
|
this.unregisterSignalHandlers();
|
|
@@ -3349,10 +3381,25 @@ var McpServer = class _McpServer {
|
|
|
3349
3381
|
this.launchKeyUnavailable = false;
|
|
3350
3382
|
this.ensureLaunchInFlight = null;
|
|
3351
3383
|
this.observedMainConversationIds.clear();
|
|
3384
|
+
if (mustAwaitLaunchSettlement && launchSettlement) {
|
|
3385
|
+
await launchSettlement;
|
|
3386
|
+
if (this.pendingLaunchSessionId) {
|
|
3387
|
+
const pendingId = this.pendingLaunchSessionId;
|
|
3388
|
+
const retired = await this.deactivateLaunchRow(pendingId);
|
|
3389
|
+
if (retired) {
|
|
3390
|
+
if (this.pendingLaunchSessionId === pendingId) this.pendingLaunchSessionId = null;
|
|
3391
|
+
} else {
|
|
3392
|
+
hostedRetirementError = new Error(
|
|
3393
|
+
`Failed to settle delayed Antigravity launch session ${pendingId}`
|
|
3394
|
+
);
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3352
3398
|
await fireDaemonBeacon("daemon_init_step", {
|
|
3353
3399
|
step: "shutdown",
|
|
3354
|
-
outcome: "ok"
|
|
3400
|
+
outcome: hostedRetirementError ? "fail" : "ok"
|
|
3355
3401
|
});
|
|
3402
|
+
if (hostedRetirementError) throw hostedRetirementError;
|
|
3356
3403
|
}
|
|
3357
3404
|
async handleConversationDiscovered(conversationId) {
|
|
3358
3405
|
if (!this.started) return;
|
|
@@ -3387,16 +3434,30 @@ var McpServer = class _McpServer {
|
|
|
3387
3434
|
async deactivateLaunchRow(sessionId) {
|
|
3388
3435
|
try {
|
|
3389
3436
|
this.appSyncClient.stopHeartbeat(sessionId);
|
|
3390
|
-
|
|
3391
|
-
logger.
|
|
3392
|
-
return true;
|
|
3393
|
-
} catch (err) {
|
|
3394
|
-
logger.warn("Failed to mark superseded/interrupted launch session INACTIVE", {
|
|
3437
|
+
} catch (error) {
|
|
3438
|
+
logger.warn("Failed to stop heartbeat while retiring Antigravity session", {
|
|
3395
3439
|
sessionId,
|
|
3396
|
-
error: String(
|
|
3440
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3397
3441
|
});
|
|
3398
|
-
return false;
|
|
3399
3442
|
}
|
|
3443
|
+
for (let attempt = 1; attempt <= SESSION_RETIRE_MAX_ATTEMPTS; attempt += 1) {
|
|
3444
|
+
try {
|
|
3445
|
+
await this.appSyncClient.updateSession({ sessionId, status: "INACTIVE" });
|
|
3446
|
+
logger.info("Marked superseded/interrupted launch session INACTIVE", { sessionId });
|
|
3447
|
+
return true;
|
|
3448
|
+
} catch (err) {
|
|
3449
|
+
logger.warn("Failed to mark superseded/interrupted launch session INACTIVE", {
|
|
3450
|
+
sessionId,
|
|
3451
|
+
attempt,
|
|
3452
|
+
maxAttempts: SESSION_RETIRE_MAX_ATTEMPTS,
|
|
3453
|
+
error: String(err)
|
|
3454
|
+
});
|
|
3455
|
+
if (attempt < SESSION_RETIRE_MAX_ATTEMPTS) {
|
|
3456
|
+
await new Promise((resolve3) => setTimeout(resolve3, SESSION_RETIRE_RETRY_MS * attempt));
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
return false;
|
|
3400
3461
|
}
|
|
3401
3462
|
/**
|
|
3402
3463
|
* v9 launch-session: create the wrapper-lifetime backend session at
|
|
@@ -4575,27 +4636,117 @@ var McpServer = class _McpServer {
|
|
|
4575
4636
|
return this.session.sessionId === backendSessionId ? this.session : null;
|
|
4576
4637
|
}
|
|
4577
4638
|
// ─── Signal handling ────────────────────────────────────────────────────
|
|
4639
|
+
/** Preserve a supported detach, but retire the daemon when its observed tmux owner ends. */
|
|
4640
|
+
startTmuxLifecycleMonitor() {
|
|
4641
|
+
if (this.tmuxLifecycleTimer || !this.tmuxTarget) return;
|
|
4642
|
+
const tmuxTarget = this.tmuxTarget;
|
|
4643
|
+
this.tmuxLifecycleTimer = setInterval(() => {
|
|
4644
|
+
void this.checkTmuxLifecycle(tmuxTarget);
|
|
4645
|
+
}, TMUX_LIFECYCLE_POLL_MS);
|
|
4646
|
+
this.tmuxLifecycleInitDeadlineMs = Date.now() + TMUX_LIFECYCLE_INIT_GRACE_MS;
|
|
4647
|
+
this.tmuxLifecycleTimer.unref?.();
|
|
4648
|
+
void this.checkTmuxLifecycle(tmuxTarget);
|
|
4649
|
+
}
|
|
4650
|
+
async tmuxSessionState(tmuxTarget) {
|
|
4651
|
+
try {
|
|
4652
|
+
await execFileAsync("tmux", ["has-session", "-t", tmuxTarget]);
|
|
4653
|
+
return "alive";
|
|
4654
|
+
} catch (error) {
|
|
4655
|
+
const state = classifyTmuxHasSessionError(error);
|
|
4656
|
+
if (state === "absent") return state;
|
|
4657
|
+
logger.warn("Could not inspect native Antigravity tmux session; preserving daemon", {
|
|
4658
|
+
tmuxTarget,
|
|
4659
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4660
|
+
});
|
|
4661
|
+
return "unknown";
|
|
4662
|
+
}
|
|
4663
|
+
}
|
|
4664
|
+
async checkTmuxLifecycle(tmuxTarget) {
|
|
4665
|
+
if (!this.started || this.tmuxLifecycleCheckInFlight) return;
|
|
4666
|
+
this.tmuxLifecycleCheckInFlight = true;
|
|
4667
|
+
try {
|
|
4668
|
+
const state = await this.tmuxSessionState(tmuxTarget);
|
|
4669
|
+
if (state === "alive") {
|
|
4670
|
+
this.tmuxLifecycleObservedAlive = true;
|
|
4671
|
+
return;
|
|
4672
|
+
}
|
|
4673
|
+
const ownershipEstablished = this.tmuxLifecycleObservedAlive || Date.now() >= this.tmuxLifecycleInitDeadlineMs;
|
|
4674
|
+
if (state === "unknown" || !ownershipEstablished || !this.started) return;
|
|
4675
|
+
this.stopTmuxLifecycleMonitor();
|
|
4676
|
+
logger.info("Native Antigravity tmux session ended; stopping companion daemon", {
|
|
4677
|
+
tmuxTarget
|
|
4678
|
+
});
|
|
4679
|
+
void this.stop().then(
|
|
4680
|
+
() => process.exit(0),
|
|
4681
|
+
(error) => {
|
|
4682
|
+
logger.error("Failed to stop companion daemon after native session end", {
|
|
4683
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4684
|
+
});
|
|
4685
|
+
process.exit(1);
|
|
4686
|
+
}
|
|
4687
|
+
);
|
|
4688
|
+
} finally {
|
|
4689
|
+
this.tmuxLifecycleCheckInFlight = false;
|
|
4690
|
+
}
|
|
4691
|
+
}
|
|
4692
|
+
stopTmuxLifecycleMonitor() {
|
|
4693
|
+
if (this.tmuxLifecycleTimer) {
|
|
4694
|
+
clearInterval(this.tmuxLifecycleTimer);
|
|
4695
|
+
this.tmuxLifecycleTimer = null;
|
|
4696
|
+
}
|
|
4697
|
+
}
|
|
4578
4698
|
registerSignalHandlers() {
|
|
4579
4699
|
if (this.signalsRegistered) return;
|
|
4580
4700
|
this.signalsRegistered = true;
|
|
4581
4701
|
const onSignal = (sig) => {
|
|
4582
|
-
logger.info(`${sig} received \u2014
|
|
4583
|
-
void this.
|
|
4702
|
+
logger.info(`${sig} received \u2014 evaluating terminal shutdown`);
|
|
4703
|
+
void this.stopForTerminalSignal(sig).then((stopped) => {
|
|
4704
|
+
if (stopped) process.exit(0);
|
|
4705
|
+
}).catch((err) => {
|
|
4584
4706
|
logger.error("shutdown error", { error: String(err) });
|
|
4585
4707
|
process.exit(1);
|
|
4586
4708
|
});
|
|
4587
4709
|
};
|
|
4588
4710
|
this.boundSigintHandler = () => onSignal("SIGINT");
|
|
4589
4711
|
this.boundSigtermHandler = () => onSignal("SIGTERM");
|
|
4590
|
-
|
|
4591
|
-
|
|
4712
|
+
this.boundSighupHandler = () => onSignal("SIGHUP");
|
|
4713
|
+
const handlers = {
|
|
4714
|
+
SIGINT: this.boundSigintHandler,
|
|
4715
|
+
SIGTERM: this.boundSigtermHandler,
|
|
4716
|
+
SIGHUP: this.boundSighupHandler
|
|
4717
|
+
};
|
|
4718
|
+
for (const signal of TERMINAL_SHUTDOWN_SIGNALS) {
|
|
4719
|
+
process.on(signal, handlers[signal]);
|
|
4720
|
+
}
|
|
4721
|
+
}
|
|
4722
|
+
/**
|
|
4723
|
+
* A tmux client detach can hang up the wrapper PTY while Antigravity remains
|
|
4724
|
+
* alive in the bound tmux session. Only treat SIGHUP as terminal when tmux
|
|
4725
|
+
* authoritatively reports that target absent; an unknown result preserves the
|
|
4726
|
+
* daemon until the lifecycle monitor retries.
|
|
4727
|
+
*/
|
|
4728
|
+
async stopForTerminalSignal(signal) {
|
|
4729
|
+
if (signal === "SIGHUP" && this.tmuxTarget) {
|
|
4730
|
+
const state = await this.tmuxSessionState(this.tmuxTarget);
|
|
4731
|
+
if (state !== "absent") {
|
|
4732
|
+
logger.info("Ignoring SIGHUP while native Antigravity tmux session remains available", {
|
|
4733
|
+
tmuxTarget: this.tmuxTarget,
|
|
4734
|
+
state
|
|
4735
|
+
});
|
|
4736
|
+
return false;
|
|
4737
|
+
}
|
|
4738
|
+
}
|
|
4739
|
+
await this.stop();
|
|
4740
|
+
return true;
|
|
4592
4741
|
}
|
|
4593
4742
|
unregisterSignalHandlers() {
|
|
4594
4743
|
if (!this.signalsRegistered) return;
|
|
4595
4744
|
if (this.boundSigintHandler) process.off("SIGINT", this.boundSigintHandler);
|
|
4596
4745
|
if (this.boundSigtermHandler) process.off("SIGTERM", this.boundSigtermHandler);
|
|
4746
|
+
if (this.boundSighupHandler) process.off("SIGHUP", this.boundSighupHandler);
|
|
4597
4747
|
this.boundSigintHandler = null;
|
|
4598
4748
|
this.boundSigtermHandler = null;
|
|
4749
|
+
this.boundSighupHandler = null;
|
|
4599
4750
|
this.signalsRegistered = false;
|
|
4600
4751
|
}
|
|
4601
4752
|
// ─── Lookups (test surface) ────────────────────────────────────────────
|
|
@@ -4835,7 +4986,9 @@ var __testing = {
|
|
|
4835
4986
|
0 && (module.exports = {
|
|
4836
4987
|
McpServer,
|
|
4837
4988
|
SessionNotFoundError,
|
|
4989
|
+
TERMINAL_SHUTDOWN_SIGNALS,
|
|
4838
4990
|
__testing,
|
|
4991
|
+
classifyTmuxHasSessionError,
|
|
4839
4992
|
generateLaunchSessionId,
|
|
4840
4993
|
getActiveConversationFromCliLog,
|
|
4841
4994
|
parseMaybeJson
|
|
@@ -201,6 +201,27 @@ log() {
|
|
|
201
201
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
+
tmux_session_state() {
|
|
205
|
+
local target="$1"
|
|
206
|
+
local output
|
|
207
|
+
local rc
|
|
208
|
+
output="$(tmux has-session -t "$target" 2>&1)"
|
|
209
|
+
rc=$?
|
|
210
|
+
if [ "$rc" -eq 0 ]; then
|
|
211
|
+
printf '%s\n' "alive"
|
|
212
|
+
return
|
|
213
|
+
fi
|
|
214
|
+
case "$output" in
|
|
215
|
+
"can't find session:"*|"no server running on "*|"error connecting to "*" (No such file or directory)"|"error connecting to "*" (Connection refused)")
|
|
216
|
+
printf '%s\n' "absent"
|
|
217
|
+
;;
|
|
218
|
+
*)
|
|
219
|
+
log "WARN: tmux inspection failed for $target; preserving daemon: $output"
|
|
220
|
+
printf '%s\n' "unknown"
|
|
221
|
+
;;
|
|
222
|
+
esac
|
|
223
|
+
}
|
|
224
|
+
|
|
204
225
|
# ─── Reject unsupported invocations ───────────────────────────────────
|
|
205
226
|
# `--print` / `-p` mode bypasses the interactive TUI we depend on. Bail
|
|
206
227
|
# out with a clear message rather than silently producing no mobile sync.
|
|
@@ -326,6 +347,22 @@ cleanup() {
|
|
|
326
347
|
local wrapper_exit_code=$?
|
|
327
348
|
log "Cleanup triggered"
|
|
328
349
|
|
|
350
|
+
# Closing or detaching the outer terminal must not disconnect an agy
|
|
351
|
+
# process that is still alive inside tmux. The daemon monitors this exact
|
|
352
|
+
# tmux target and owns full cleanup after the native session disappears.
|
|
353
|
+
if [ "${_CV_TMUX_STARTED:-false}" = "true" ]; then
|
|
354
|
+
local tmux_state
|
|
355
|
+
tmux_state="$(tmux_session_state "$SESSION_NAME")"
|
|
356
|
+
if [ "$tmux_state" = "alive" ]; then
|
|
357
|
+
log "Tmux client detached while Antigravity remains active; leaving daemon running"
|
|
358
|
+
return
|
|
359
|
+
fi
|
|
360
|
+
if [ "$tmux_state" = "unknown" ]; then
|
|
361
|
+
log "Tmux state is ambiguous; preserving daemon and native session"
|
|
362
|
+
return
|
|
363
|
+
fi
|
|
364
|
+
fi
|
|
365
|
+
|
|
329
366
|
# Fire wrapper_exited telemetry BEFORE killing the server so MCP
|
|
330
367
|
# logs are intact. cv_failed sets _CV_EXITED on pre-flight failures,
|
|
331
368
|
# so this block won't double-fire.
|
|
@@ -358,21 +395,12 @@ cleanup() {
|
|
|
358
395
|
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\""
|
|
359
396
|
fi
|
|
360
397
|
|
|
361
|
-
# Stop the MCP server
|
|
362
|
-
#
|
|
398
|
+
# Stop the MCP server gracefully and wait for product-owned cleanup. Never
|
|
399
|
+
# force-kill here: stop() must finish its hosted INACTIVE obligation before
|
|
400
|
+
# the process exits, even when that takes longer than the old 3s bound.
|
|
363
401
|
if [ -n "$MCP_PID" ] && kill -0 "$MCP_PID" 2>/dev/null; then
|
|
364
402
|
log "Stopping MCP server (PID: $MCP_PID)"
|
|
365
403
|
kill -TERM "$MCP_PID" 2>/dev/null || true
|
|
366
|
-
# Wait up to 3s for graceful shutdown.
|
|
367
|
-
local i=0
|
|
368
|
-
while [ $i -lt 30 ] && kill -0 "$MCP_PID" 2>/dev/null; do
|
|
369
|
-
sleep 0.1
|
|
370
|
-
i=$((i + 1))
|
|
371
|
-
done
|
|
372
|
-
if kill -0 "$MCP_PID" 2>/dev/null; then
|
|
373
|
-
log "MCP server did not exit cleanly; sending SIGKILL"
|
|
374
|
-
kill -KILL "$MCP_PID" 2>/dev/null || true
|
|
375
|
-
fi
|
|
376
404
|
wait "$MCP_PID" 2>/dev/null || true
|
|
377
405
|
fi
|
|
378
406
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quantiya/codevibe-antigravity-plugin",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.13",
|
|
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": {
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"node": ">=22.0.0"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@quantiya/codevibe-core": "2.0.
|
|
51
|
+
"@quantiya/codevibe-core": "2.0.12",
|
|
52
52
|
"chokidar": "^5.0.0",
|
|
53
53
|
"dotenv": "^16.6.1",
|
|
54
54
|
"express": "^5.1.0",
|