@quantiya/codevibe-antigravity-plugin 1.0.8 → 1.0.10
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 +90 -17
- package/package.json +2 -2
package/dist/server.js
CHANGED
|
@@ -2789,6 +2789,7 @@ function truncate(s, maxBytes) {
|
|
|
2789
2789
|
|
|
2790
2790
|
// src/server.ts
|
|
2791
2791
|
var MOBILE_PROMPT_FLOOR_RECENCY_MS = 12e4;
|
|
2792
|
+
var LAUNCH_SETTLE_TIMEOUT_MS = 3e3;
|
|
2792
2793
|
var McpServer = class {
|
|
2793
2794
|
constructor(options) {
|
|
2794
2795
|
/** Per-session causal floor for transcript-event timestamps emitted right
|
|
@@ -2836,6 +2837,19 @@ var McpServer = class {
|
|
|
2836
2837
|
* process.exit before the original stop's cleanup completes.
|
|
2837
2838
|
* (Stage 1 MED finding 2026-05-20.) */
|
|
2838
2839
|
this.stopPromise = null;
|
|
2840
|
+
/** In-flight createLaunchSession() promise. doStop() awaits it so a
|
|
2841
|
+
* SIGTERM arriving DURING startup (while resumeOrCreateSession is still
|
|
2842
|
+
* creating the ACTIVE row) doesn't let the signal handler's
|
|
2843
|
+
* process.exit() fire before the just-created row is marked INACTIVE.
|
|
2844
|
+
* Resolved (instant) for the normal shutdown path. (#142 ii) */
|
|
2845
|
+
this.launchSessionPromise = null;
|
|
2846
|
+
/** The launch sessionId whose ACTIVE row has been (or is being) created by
|
|
2847
|
+
* an in-flight createLaunchSession(), until it is either wired into
|
|
2848
|
+
* this.session (happy path) or marked INACTIVE. doStop() reads it to
|
|
2849
|
+
* guarantee the INACTIVE write lands even if the Phase-0 create-wait timed
|
|
2850
|
+
* out mid-cleanup — separating the bounded "wait for create" from the
|
|
2851
|
+
* always-awaited "mark INACTIVE". (#142 Stage 2 R1 iter-2 HIGH.) */
|
|
2852
|
+
this.pendingLaunchSessionId = null;
|
|
2839
2853
|
/** Listener references kept so doStop() can detach them. Without this,
|
|
2840
2854
|
* a server restart (test mode or future hot-reload) accumulates
|
|
2841
2855
|
* listeners on the shared module instances. (Stage 2 HIGH finding
|
|
@@ -2853,6 +2867,7 @@ var McpServer = class {
|
|
|
2853
2867
|
this.tmuxTarget = options.tmuxTarget ?? process.env.CODEVIBE_AGY_TMUX_TARGET ?? null;
|
|
2854
2868
|
this.wrapperPid = options.wrapperPid ?? null;
|
|
2855
2869
|
this.cliLogPath = options.cliLogPath ?? null;
|
|
2870
|
+
this.launchSettleTimeoutMs = options.launchSettleTimeoutMs ?? LAUNCH_SETTLE_TIMEOUT_MS;
|
|
2856
2871
|
this.appSyncClient = options.appSyncClient ?? new import_codevibe_core4.AppSyncClient();
|
|
2857
2872
|
this.workingDirectory = process.cwd();
|
|
2858
2873
|
this.approvalDetector = options.approvalDetector ?? new ApprovalDetector(options.detectorOptions);
|
|
@@ -2897,11 +2912,19 @@ var McpServer = class {
|
|
|
2897
2912
|
}
|
|
2898
2913
|
this.started = true;
|
|
2899
2914
|
this.lifecycleGen++;
|
|
2915
|
+
this.registerSignalHandlers();
|
|
2900
2916
|
await (0, import_codevibe_core4.registerDeviceEncryptionKey)(this.appSyncClient, logger);
|
|
2917
|
+
if (!this.started) return { httpPort: 0 };
|
|
2901
2918
|
(0, import_codevibe_core4.startDeviceKeyWatcher)(this.appSyncClient, logger);
|
|
2902
2919
|
try {
|
|
2903
2920
|
const swept = await this.appSyncClient.sweepOrphanSessions({
|
|
2904
|
-
agentType: "ANTIGRAVITY"
|
|
2921
|
+
agentType: "ANTIGRAVITY",
|
|
2922
|
+
// 6 min (core default is 15 min). A live daemon heartbeats every
|
|
2923
|
+
// 2 min, so a session stale >6 min has missed 3 beats and is
|
|
2924
|
+
// provably dead — safe to reap without false-positiving a
|
|
2925
|
+
// concurrently-running agy wrapper, while catching orphans on the
|
|
2926
|
+
// next start sooner. (agy orphaned-session fix 2026-05-31.)
|
|
2927
|
+
staleThresholdMs: 6 * 60 * 1e3
|
|
2905
2928
|
});
|
|
2906
2929
|
if (swept > 0) {
|
|
2907
2930
|
logger.info("Orphan sweep: marked stale Antigravity sessions INACTIVE", { swept });
|
|
@@ -2911,13 +2934,18 @@ var McpServer = class {
|
|
|
2911
2934
|
error: error instanceof Error ? error.message : String(error)
|
|
2912
2935
|
});
|
|
2913
2936
|
}
|
|
2937
|
+
if (!this.started) return { httpPort: 0 };
|
|
2914
2938
|
this.approvalDetector.start();
|
|
2915
2939
|
this.addListener(this.approvalDetector, "pending-prompt", (state) => {
|
|
2916
2940
|
void this.handlePendingPrompt(state).catch((err) => {
|
|
2917
2941
|
logger.error("handlePendingPrompt failed", { error: String(err) });
|
|
2918
2942
|
});
|
|
2919
2943
|
});
|
|
2920
|
-
|
|
2944
|
+
this.launchSessionPromise = this.createLaunchSession();
|
|
2945
|
+
await this.launchSessionPromise;
|
|
2946
|
+
if (!this.started) {
|
|
2947
|
+
return { httpPort: 0 };
|
|
2948
|
+
}
|
|
2921
2949
|
this.addListener(this.transcriptTailer, "event", (emit) => {
|
|
2922
2950
|
void this.handleTranscriptEmit(emit).catch((err) => {
|
|
2923
2951
|
logger.error("handleTranscriptEmit failed", { error: String(err) });
|
|
@@ -2943,7 +2971,6 @@ var McpServer = class {
|
|
|
2943
2971
|
logger.warn("No tmux target \u2014 pane observer disabled (mobile-only mode)");
|
|
2944
2972
|
}
|
|
2945
2973
|
const httpPort = await this.httpApi.start();
|
|
2946
|
-
this.registerSignalHandlers();
|
|
2947
2974
|
await fireDaemonBeacon("daemon_init_step", {
|
|
2948
2975
|
step: "ready",
|
|
2949
2976
|
outcome: "ok",
|
|
@@ -2966,15 +2993,23 @@ var McpServer = class {
|
|
|
2966
2993
|
}
|
|
2967
2994
|
async doStop() {
|
|
2968
2995
|
this.started = false;
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2996
|
+
if (this.launchSessionPromise) {
|
|
2997
|
+
const settled = this.launchSessionPromise.catch(() => void 0);
|
|
2998
|
+
let timer;
|
|
2999
|
+
const timeout = new Promise((resolve3) => {
|
|
3000
|
+
timer = setTimeout(resolve3, this.launchSettleTimeoutMs);
|
|
3001
|
+
timer.unref?.();
|
|
3002
|
+
});
|
|
3003
|
+
try {
|
|
3004
|
+
await Promise.race([settled, timeout]);
|
|
3005
|
+
} finally {
|
|
3006
|
+
if (timer) clearTimeout(timer);
|
|
3007
|
+
}
|
|
2973
3008
|
}
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
3009
|
+
if (this.pendingLaunchSessionId) {
|
|
3010
|
+
const pendingId = this.pendingLaunchSessionId;
|
|
3011
|
+
this.pendingLaunchSessionId = null;
|
|
3012
|
+
await this.deactivateLaunchRow(pendingId);
|
|
2978
3013
|
}
|
|
2979
3014
|
if (this.subscription) {
|
|
2980
3015
|
try {
|
|
@@ -2990,8 +3025,11 @@ var McpServer = class {
|
|
|
2990
3025
|
}
|
|
2991
3026
|
}
|
|
2992
3027
|
this.listenerHandles = [];
|
|
2993
|
-
this.unregisterSignalHandlers();
|
|
2994
3028
|
if (this.session) {
|
|
3029
|
+
try {
|
|
3030
|
+
this.appSyncClient.stopHeartbeat(this.session.sessionId);
|
|
3031
|
+
} catch {
|
|
3032
|
+
}
|
|
2995
3033
|
try {
|
|
2996
3034
|
await this.appSyncClient.updateSession({
|
|
2997
3035
|
sessionId: this.session.sessionId,
|
|
@@ -3003,10 +3041,17 @@ var McpServer = class {
|
|
|
3003
3041
|
error: String(err)
|
|
3004
3042
|
});
|
|
3005
3043
|
}
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3044
|
+
}
|
|
3045
|
+
this.unregisterSignalHandlers();
|
|
3046
|
+
try {
|
|
3047
|
+
await this.transcriptTailer.stop();
|
|
3048
|
+
} catch (err) {
|
|
3049
|
+
logger.warn("transcriptTailer.stop failed", { error: String(err) });
|
|
3050
|
+
}
|
|
3051
|
+
try {
|
|
3052
|
+
await this.paneObserver.stop();
|
|
3053
|
+
} catch (err) {
|
|
3054
|
+
logger.warn("paneObserver.stop failed", { error: String(err) });
|
|
3010
3055
|
}
|
|
3011
3056
|
try {
|
|
3012
3057
|
await this.approvalDetector.stop();
|
|
@@ -3045,6 +3090,27 @@ var McpServer = class {
|
|
|
3045
3090
|
launchSessionId: this.session.sessionId
|
|
3046
3091
|
});
|
|
3047
3092
|
}
|
|
3093
|
+
/**
|
|
3094
|
+
* Stop the heartbeat and mark a launch row INACTIVE. Best-effort + idempotent:
|
|
3095
|
+
* createLaunchSession's bail and doStop's pending-id guarantee may both call
|
|
3096
|
+
* this for the same id, but both write the SAME terminal status (INACTIVE) on
|
|
3097
|
+
* an unconditional last-writer-wins resolver, so a double/reordered write
|
|
3098
|
+
* can't clobber or resurrect ACTIVE. (Separate from the ACTIVE-vs-INACTIVE
|
|
3099
|
+
* per-session ordering that core 1.0.29's status-write chain handles.)
|
|
3100
|
+
* (#142 Stage 2 R1 iter-2.)
|
|
3101
|
+
*/
|
|
3102
|
+
async deactivateLaunchRow(sessionId) {
|
|
3103
|
+
try {
|
|
3104
|
+
this.appSyncClient.stopHeartbeat(sessionId);
|
|
3105
|
+
await this.appSyncClient.updateSession({ sessionId, status: "INACTIVE" });
|
|
3106
|
+
logger.info("Marked superseded/interrupted launch session INACTIVE", { sessionId });
|
|
3107
|
+
} catch (err) {
|
|
3108
|
+
logger.warn("Failed to mark superseded/interrupted launch session INACTIVE", {
|
|
3109
|
+
sessionId,
|
|
3110
|
+
error: String(err)
|
|
3111
|
+
});
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3048
3114
|
/**
|
|
3049
3115
|
* v9 launch-session: create the wrapper-lifetime backend session at
|
|
3050
3116
|
* start(). All agy conv UUIDs observed during this lifetime emit
|
|
@@ -3057,6 +3123,7 @@ var McpServer = class {
|
|
|
3057
3123
|
const sessionId = generateLaunchSessionId(this.wrapperPid ?? process.pid);
|
|
3058
3124
|
const userId = this.appSyncClient.getCurrentUserId();
|
|
3059
3125
|
const projectPath = process.cwd();
|
|
3126
|
+
this.pendingLaunchSessionId = sessionId;
|
|
3060
3127
|
let sessionKey = null;
|
|
3061
3128
|
try {
|
|
3062
3129
|
const result = await (0, import_codevibe_core4.resumeOrCreateSession)(
|
|
@@ -3072,6 +3139,7 @@ var McpServer = class {
|
|
|
3072
3139
|
);
|
|
3073
3140
|
sessionKey = result.sessionKey ?? null;
|
|
3074
3141
|
} catch (err) {
|
|
3142
|
+
this.pendingLaunchSessionId = null;
|
|
3075
3143
|
const msg = String(err);
|
|
3076
3144
|
if (msg.includes("session-limit-exceeded")) {
|
|
3077
3145
|
logger.warn("Free-tier session limit reached \u2014 mobile sync disabled for this wrapper", { sessionId });
|
|
@@ -3082,7 +3150,11 @@ var McpServer = class {
|
|
|
3082
3150
|
logger.error("createLaunchSession failed (non-fatal)", { sessionId, error: msg });
|
|
3083
3151
|
return;
|
|
3084
3152
|
}
|
|
3085
|
-
if (gen !== this.lifecycleGen || !this.started)
|
|
3153
|
+
if (gen !== this.lifecycleGen || !this.started) {
|
|
3154
|
+
await this.deactivateLaunchRow(sessionId);
|
|
3155
|
+
if (this.pendingLaunchSessionId === sessionId) this.pendingLaunchSessionId = null;
|
|
3156
|
+
return;
|
|
3157
|
+
}
|
|
3086
3158
|
this.session = {
|
|
3087
3159
|
sessionId,
|
|
3088
3160
|
conversationId: "",
|
|
@@ -3095,6 +3167,7 @@ var McpServer = class {
|
|
|
3095
3167
|
metadata: { wrapperPid: this.wrapperPid ?? void 0, launch: true },
|
|
3096
3168
|
sessionKey
|
|
3097
3169
|
};
|
|
3170
|
+
this.pendingLaunchSessionId = null;
|
|
3098
3171
|
try {
|
|
3099
3172
|
const stop = this.appSyncClient.subscribeToEvents(
|
|
3100
3173
|
sessionId,
|
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.10",
|
|
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": {
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"node": ">=18.0.0"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@quantiya/codevibe-core": "^1.0.
|
|
51
|
+
"@quantiya/codevibe-core": "^1.0.29",
|
|
52
52
|
"chokidar": "^5.0.0",
|
|
53
53
|
"dotenv": "^16.6.1",
|
|
54
54
|
"express": "^5.1.0",
|