@quantiya/codevibe-antigravity-plugin 2.0.86 → 2.0.87
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/README.md +2 -2
- package/antigravity-plugin.json +1 -1
- package/dist/server.js +87 -10
- package/libexec/companion-launcher +48 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ install the unified `codevibe --agent` interface yet.
|
|
|
45
45
|
- **macOS, Linux, or WSL Ubuntu** — Windows without WSL is not supported
|
|
46
46
|
- **Node.js** 22.0.0+
|
|
47
47
|
- **tmux** — `brew install tmux` on macOS, `apt install tmux` on Linux/WSL
|
|
48
|
-
- **Antigravity CLI (`agy`)
|
|
48
|
+
- **Antigravity CLI (`agy`) 1.4.3+** with `--log-file` advertised by `agy --help` — install from [Google's documentation](https://antigravity.google/docs). Companion checks this before creating a session and tells unsupported clients to update; direct `agy` is unchanged.
|
|
49
49
|
|
|
50
50
|
> Antigravity CLI is distributed by Google as a binary; it is not on npm. After installing `agy`, run `codevibe --agent agy` and the plugin handles the rest.
|
|
51
51
|
|
|
@@ -66,7 +66,7 @@ All agy sessions on a computer write to the same folders. CodeVibe tells them ap
|
|
|
66
66
|
|
|
67
67
|
On each launch, `codevibe --agent agy` registers one named entry, `codevibe-antigravity`, in `~/.gemini/config/hooks.json`, and keeps the hook scripts in `~/.codevibe/codevibe-agy/hooks/`:
|
|
68
68
|
- Other entries in that file are left as they are.
|
|
69
|
-
- A file that is not valid JSON, or is a symlink, is never changed. CodeVibe
|
|
69
|
+
- A file that is not valid JSON, or is a symlink, is never changed. Without hooks, CodeVibe uses only this launch's pinned native log and a `USER_INPUT`-first main transcript; unknown ownership waits, and `SYSTEM_MESSAGE`-first subagents are excluded.
|
|
70
70
|
- For other agy sessions on the machine, `PreToolUse` immediately permits execution with `{"decision":"allow"}`, and other hook events are skipped.
|
|
71
71
|
- To switch it off, add `"enabled": false` to the `codevibe-antigravity` entry. CodeVibe leaves a switched-off entry alone. Deleting the entry only lasts until the next `codevibe --agent agy` launch, which writes it again. After uninstalling CodeVibe you can delete the entry; see INSTALLATION.md.
|
|
72
72
|
|
package/antigravity-plugin.json
CHANGED
|
@@ -6,6 +6,6 @@
|
|
|
6
6
|
"implementation": {
|
|
7
7
|
"architecture": "transcript+tmux+approval-heuristic",
|
|
8
8
|
"schema_version": "v1.0",
|
|
9
|
-
"note": "Monitors ~/.gemini/antigravity-cli/brain/<UUID>/.system_generated/logs/transcript.jsonl and the tmux pane.
|
|
9
|
+
"note": "Requires native agy 1.4.3+ advertising --log-file; checked before Companion session creation. Monitors ~/.gemini/antigravity-cli/brain/<UUID>/.system_generated/logs/transcript.jsonl and the tmux pane. Own agy JSON hooks identify main conversations. Without hooks, ownership requires this launch's pinned native log and a USER_INPUT-first main transcript; SYSTEM_MESSAGE-first subagents and shared/newest logs are excluded. Unknown ownership defers sync."
|
|
10
10
|
}
|
|
11
11
|
}
|
package/dist/server.js
CHANGED
|
@@ -790,6 +790,20 @@ var TranscriptTailer = class extends import_events.EventEmitter {
|
|
|
790
790
|
getActiveConversations() {
|
|
791
791
|
return Array.from(this.activeConversations);
|
|
792
792
|
}
|
|
793
|
+
/** Retry a deferred native read after THIS launcher's hook proves ownership.
|
|
794
|
+
* No offset reset or legacy fallback. Join an in-flight read first so a late
|
|
795
|
+
* hook cannot lose its wakeup in that read's rejection/finally cleanup. */
|
|
796
|
+
retryOwnedConversation(conversationId) {
|
|
797
|
+
if (!this.admission || !this.isWatching || !CONVERSATION_DIR_REGEX.test(conversationId)) return;
|
|
798
|
+
const generation = this.readGeneration, filePath = this.transcriptPathFor(conversationId);
|
|
799
|
+
const pending = this.activeReads.get(filePath) ?? Promise.resolve();
|
|
800
|
+
void pending.then(() => {
|
|
801
|
+
if (!this.isWatching || generation !== this.readGeneration) return;
|
|
802
|
+
if (!fs2.existsSync(filePath)) return;
|
|
803
|
+
this.ensureOffset(filePath);
|
|
804
|
+
void this.scheduleReadNewLines(filePath, conversationId);
|
|
805
|
+
}).catch((error) => logger.warn("Owned transcript wakeup failed", { conversationId, error: String(error) }));
|
|
806
|
+
}
|
|
793
807
|
// ─── follower event handlers ─────────────────────────────────────────────
|
|
794
808
|
onFileAdded(filePath) {
|
|
795
809
|
if (this.isShuttingDown) return;
|
|
@@ -1234,6 +1248,10 @@ var TranscriptTailer = class extends import_events.EventEmitter {
|
|
|
1234
1248
|
}
|
|
1235
1249
|
const generation = this.readGeneration;
|
|
1236
1250
|
const read = this.drainReadQueue(filePath, conversationId, generation).catch((error) => {
|
|
1251
|
+
if (error?.code === "SYNC_SOURCE_OWNERSHIP_PENDING") {
|
|
1252
|
+
logger.debug("Transcript waiting for its own hook or process-bound log", { conversationId });
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1237
1255
|
logger.error("Transcript read chain failed (non-fatal) \u2014 next change event retries", {
|
|
1238
1256
|
filePath,
|
|
1239
1257
|
conversationId,
|
|
@@ -7018,6 +7036,7 @@ ${commandErrorText(error, "stdout")}`;
|
|
|
7018
7036
|
}
|
|
7019
7037
|
var McpServer = class _McpServer {
|
|
7020
7038
|
constructor(options) {
|
|
7039
|
+
this.lastNativeLogConversation = null;
|
|
7021
7040
|
/** Per-session causal floor for transcript-event timestamps emitted right
|
|
7022
7041
|
* after a MOBILE prompt. The mobile USER_PROMPT is created by the iOS app
|
|
7023
7042
|
* (real ms clock) and never flows through prepareEventTimestamp, while the
|
|
@@ -7210,6 +7229,15 @@ var McpServer = class _McpServer {
|
|
|
7210
7229
|
this.tmuxTarget = options.tmuxTarget ?? process.env.CODEVIBE_AGY_TMUX_TARGET ?? null;
|
|
7211
7230
|
this.wrapperPid = options.wrapperPid ?? null;
|
|
7212
7231
|
this.cliLogPath = options.cliLogPath ?? null;
|
|
7232
|
+
this.nativeCliLogPath = options.nativeCliLogPath ?? null;
|
|
7233
|
+
this.nativeCliLogIdentity = null;
|
|
7234
|
+
if (this.nativeCliLogPath) {
|
|
7235
|
+
const st = fs7.lstatSync(this.nativeCliLogPath);
|
|
7236
|
+
if (!st.isFile() || st.isSymbolicLink() || st.nlink !== 1 || st.uid !== process.getuid?.()) {
|
|
7237
|
+
throw Error("AGY_NATIVE_LOG_NOT_OWNED");
|
|
7238
|
+
}
|
|
7239
|
+
this.nativeCliLogIdentity = { dev: st.dev, ino: st.ino };
|
|
7240
|
+
}
|
|
7213
7241
|
this.launchSettleTimeoutMs = options.launchSettleTimeoutMs ?? LAUNCH_SETTLE_TIMEOUT_MS;
|
|
7214
7242
|
this.appSyncClient = options.appSyncClient ?? new import_codevibe_core7.AppSyncClient();
|
|
7215
7243
|
this.toolActivity = options.toolActivityIntegration === void 0 ? new AgyToolActivityIntegration({
|
|
@@ -7837,13 +7865,14 @@ var McpServer = class _McpServer {
|
|
|
7837
7865
|
}
|
|
7838
7866
|
}
|
|
7839
7867
|
/**
|
|
7840
|
-
*
|
|
7841
|
-
*
|
|
7842
|
-
*
|
|
7843
|
-
* another live CodeVibe daemon has claimed is never "the active one" here,
|
|
7844
|
-
* so another session's busy log cannot make us drop our own turn.
|
|
7868
|
+
* Production launchers supply a pinned native log. Hookless routing admits
|
|
7869
|
+
* only its positively identified main transcript. The unbound pre-F1 rule
|
|
7870
|
+
* below remains for legacy/test callers, not durable source allocation.
|
|
7845
7871
|
*/
|
|
7846
7872
|
legacyAcceptsConversation(conversationId) {
|
|
7873
|
+
if (this.nativeCliLogPath) {
|
|
7874
|
+
return !this.claims.isClaimedElsewhere(conversationId) && this.boundNativeLogConversation() === conversationId;
|
|
7875
|
+
}
|
|
7847
7876
|
if (process.env.JEST_WORKER_ID && !this.cliLogPath) {
|
|
7848
7877
|
return true;
|
|
7849
7878
|
}
|
|
@@ -7857,6 +7886,38 @@ var McpServer = class _McpServer {
|
|
|
7857
7886
|
}
|
|
7858
7887
|
return activeId === conversationId;
|
|
7859
7888
|
}
|
|
7889
|
+
/** Never resolve the shared cli.log symlink or scan for another process's log. */
|
|
7890
|
+
boundNativeLogConversation() {
|
|
7891
|
+
if (!this.nativeCliLogPath || !this.nativeCliLogIdentity) return null;
|
|
7892
|
+
let fd;
|
|
7893
|
+
try {
|
|
7894
|
+
fd = fs7.openSync(this.nativeCliLogPath, fs7.constants.O_RDONLY | fs7.constants.O_NOFOLLOW);
|
|
7895
|
+
const st = fs7.fstatSync(fd), expected = this.nativeCliLogIdentity;
|
|
7896
|
+
if (!st.isFile() || st.nlink !== 1 || st.dev !== expected.dev || st.ino !== expected.ino) return null;
|
|
7897
|
+
const matches = [...fs7.readFileSync(fd, "utf8").matchAll(/(?:Created conversation|Streaming conversation|Starting conversation update stream for)\s+([0-9a-fA-F-]{36})/g)];
|
|
7898
|
+
const checked = /* @__PURE__ */ new Set();
|
|
7899
|
+
for (let i = matches.length - 1; i >= 0; i--) {
|
|
7900
|
+
const conversationId = matches[i][1];
|
|
7901
|
+
if (checked.has(conversationId)) continue;
|
|
7902
|
+
checked.add(conversationId);
|
|
7903
|
+
if (this.router.roleOf(conversationId) !== "subagent" && !this.claims.isClaimedElsewhere(conversationId) && this.readConversationFirstType(conversationId) === "USER_INPUT") return conversationId;
|
|
7904
|
+
}
|
|
7905
|
+
return null;
|
|
7906
|
+
} catch {
|
|
7907
|
+
return null;
|
|
7908
|
+
} finally {
|
|
7909
|
+
if (fd !== void 0) fs7.closeSync(fd);
|
|
7910
|
+
}
|
|
7911
|
+
}
|
|
7912
|
+
refreshNativeLogOwnership() {
|
|
7913
|
+
const conversationId = this.boundNativeLogConversation();
|
|
7914
|
+
if (!conversationId || conversationId === this.lastNativeLogConversation) return;
|
|
7915
|
+
this.lastNativeLogConversation = conversationId;
|
|
7916
|
+
if (this.router.roleOf(conversationId) !== "subagent" && !this.claims.isClaimedElsewhere(conversationId)) {
|
|
7917
|
+
logger.info("Agy launch log identified its native conversation", { conversationId, ownership: "process-bound-log" });
|
|
7918
|
+
this.transcriptTailer.retryOwnedConversation(conversationId);
|
|
7919
|
+
}
|
|
7920
|
+
}
|
|
7860
7921
|
/**
|
|
7861
7922
|
* F1 moved the runtime dir under ~/.codevibe, so it outlives reboots. The
|
|
7862
7923
|
* wrapper's EXIT trap removes it, except when the outer terminal closed while
|
|
@@ -7867,6 +7928,12 @@ var McpServer = class _McpServer {
|
|
|
7867
7928
|
removeOrphanedRuntimeDir() {
|
|
7868
7929
|
if (!this.runtimeDir || this.wrapperPid === null || isPidAlive(this.wrapperPid)) return;
|
|
7869
7930
|
try {
|
|
7931
|
+
if (this.nativeCliLogPath && this.nativeCliLogIdentity) {
|
|
7932
|
+
const st = fs7.lstatSync(this.nativeCliLogPath);
|
|
7933
|
+
if (st.isFile() && !st.isSymbolicLink() && st.nlink === 1 && st.dev === this.nativeCliLogIdentity.dev && st.ino === this.nativeCliLogIdentity.ino) {
|
|
7934
|
+
fs7.unlinkSync(this.nativeCliLogPath);
|
|
7935
|
+
}
|
|
7936
|
+
}
|
|
7870
7937
|
fs7.rmdirSync(this.runtimeDir);
|
|
7871
7938
|
logger.info("Removed runtime dir left by a closed terminal", { runtimeDir: this.runtimeDir });
|
|
7872
7939
|
} catch (error) {
|
|
@@ -7938,6 +8005,7 @@ var McpServer = class _McpServer {
|
|
|
7938
8005
|
});
|
|
7939
8006
|
if (role === "main") {
|
|
7940
8007
|
this.observedMainConversationIds.add(conversationId);
|
|
8008
|
+
this.transcriptTailer.retryOwnedConversation(conversationId);
|
|
7941
8009
|
if (!wasAlreadyAdmitted) {
|
|
7942
8010
|
await this.drainHeldHookDeliveries(conversationId, "process", delivery.initialNumSteps);
|
|
7943
8011
|
}
|
|
@@ -7986,6 +8054,7 @@ var McpServer = class _McpServer {
|
|
|
7986
8054
|
logger.warn("PostToolUse prompt resolution failed", { error: String(error) });
|
|
7987
8055
|
});
|
|
7988
8056
|
this.router.onPostToolUse(conversationId);
|
|
8057
|
+
if (this.router.roleOf(conversationId) === "main") this.transcriptTailer.retryOwnedConversation(conversationId);
|
|
7989
8058
|
}
|
|
7990
8059
|
resolveLingeringQuestionPrompts(conversationId) {
|
|
7991
8060
|
const lingeringPrompts = this.approvalDetector.getPendingPromptsForConversation(conversationId).filter((p) => p.kind === "question");
|
|
@@ -8411,8 +8480,9 @@ var McpServer = class _McpServer {
|
|
|
8411
8480
|
const selection = await this.selectNativeSource(session);
|
|
8412
8481
|
if (!this.started || this.session !== session) throw Error("SYNC_SOURCE_OWNER_CHANGED");
|
|
8413
8482
|
if (selection.protocol === "LEGACY") return null;
|
|
8414
|
-
|
|
8415
|
-
|
|
8483
|
+
const ownsConversation = () => !this.claims.isClaimedElsewhere(source.conversationId) && (this.router.roleOf(source.conversationId) === "main" || this.router.roleOf(source.conversationId) === void 0 && this.boundNativeLogConversation() === source.conversationId);
|
|
8484
|
+
if (!ownsConversation()) {
|
|
8485
|
+
throw Object.assign(Error("SYNC_SOURCE_OWNERSHIP_UNAVAILABLE"), { code: "SYNC_SOURCE_OWNERSHIP_PENDING" });
|
|
8416
8486
|
}
|
|
8417
8487
|
if (!this.toolActivity?.isActive(session.sessionId)) {
|
|
8418
8488
|
if (this.nativeToolStart && this.nativeToolStart.session !== session) throw Error("SYNC_SOURCE_OWNER_CHANGED");
|
|
@@ -8426,7 +8496,12 @@ var McpServer = class _McpServer {
|
|
|
8426
8496
|
}
|
|
8427
8497
|
await this.nativeToolStart.pending;
|
|
8428
8498
|
}
|
|
8429
|
-
if (!
|
|
8499
|
+
if (!ownsConversation()) {
|
|
8500
|
+
throw Object.assign(Error("SYNC_SOURCE_OWNERSHIP_UNAVAILABLE"), { code: "SYNC_SOURCE_OWNERSHIP_PENDING" });
|
|
8501
|
+
}
|
|
8502
|
+
if (!this.toolActivity?.isActive(session.sessionId) || this.session !== session || !this.started) {
|
|
8503
|
+
throw Error("SYNC_SOURCE_OWNERSHIP_UNAVAILABLE");
|
|
8504
|
+
}
|
|
8430
8505
|
const streamSha256 = crypto3.createHash("sha256").update(JSON.stringify([source.filePath, source.conversationId])).digest("hex");
|
|
8431
8506
|
const key = `${selection.sessionScope.sessionId}:${selection.sessionScope.sessionGenerationId}:${streamSha256}`;
|
|
8432
8507
|
const existing = this.nativeSources.get(key);
|
|
@@ -8470,7 +8545,7 @@ var McpServer = class _McpServer {
|
|
|
8470
8545
|
this.assertNativeOwner(owner);
|
|
8471
8546
|
if ((await owner.cursor.open(0)).byteOffset >= source.endOffset) return;
|
|
8472
8547
|
const admission = { outputs: [null, null] };
|
|
8473
|
-
await this.handleTranscriptEmit(emit, "
|
|
8548
|
+
await this.handleTranscriptEmit(emit, "process", admission);
|
|
8474
8549
|
this.assertNativeOwner(owner);
|
|
8475
8550
|
const echoFrame = {
|
|
8476
8551
|
streamSha256: owner.streamSha256,
|
|
@@ -9283,7 +9358,7 @@ var McpServer = class _McpServer {
|
|
|
9283
9358
|
if (bound && this.observedMainConversationIds.has(bound)) return bound;
|
|
9284
9359
|
if (this.observedMainConversationIds.size === 0) return null;
|
|
9285
9360
|
const cliLogPath = this.cliLogPath ?? path8.join(os7.homedir(), ".gemini", "antigravity-cli", "cli.log");
|
|
9286
|
-
const cliActive = getActiveConversationFromCliLog(cliLogPath);
|
|
9361
|
+
const cliActive = this.nativeCliLogPath ? this.boundNativeLogConversation() : getActiveConversationFromCliLog(cliLogPath);
|
|
9287
9362
|
if (cliActive && this.observedMainConversationIds.has(cliActive)) {
|
|
9288
9363
|
return cliActive;
|
|
9289
9364
|
}
|
|
@@ -9914,6 +9989,7 @@ var McpServer = class _McpServer {
|
|
|
9914
9989
|
const state = await this.tmuxSessionState(tmuxTarget);
|
|
9915
9990
|
if (state === "alive") {
|
|
9916
9991
|
this.tmuxLifecycleObservedAlive = true;
|
|
9992
|
+
this.refreshNativeLogOwnership();
|
|
9917
9993
|
return;
|
|
9918
9994
|
}
|
|
9919
9995
|
const ownershipEstablished = this.tmuxLifecycleObservedAlive || Date.now() >= this.tmuxLifecycleInitDeadlineMs;
|
|
@@ -10241,6 +10317,7 @@ async function main() {
|
|
|
10241
10317
|
bearerToken,
|
|
10242
10318
|
preferredPort: args.port,
|
|
10243
10319
|
runtimeDir: args.runtimeDir,
|
|
10320
|
+
nativeCliLogPath: args.runtimeDir ? path8.join(args.runtimeDir, "native-cli.log") : void 0,
|
|
10244
10321
|
tmuxTarget: process.env.CODEVIBE_AGY_TMUX_TARGET ?? void 0,
|
|
10245
10322
|
wrapperPid: process.env.CODEVIBE_AGY_WRAPPER_PID ? parseInt(process.env.CODEVIBE_AGY_WRAPPER_PID, 10) : void 0
|
|
10246
10323
|
});
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
# Companion limitations:
|
|
51
51
|
# - Requires tmux. Print mode (`--print` / `-p`) is NOT supported.
|
|
52
52
|
# - Requires Node.js 22 or newer for the MCP server.
|
|
53
|
+
# - Requires agy 1.4.3+ advertising --log-file for launch ownership.
|
|
53
54
|
#
|
|
54
55
|
|
|
55
56
|
set -e
|
|
@@ -182,6 +183,37 @@ case "${1:-}" in
|
|
|
182
183
|
;;
|
|
183
184
|
esac
|
|
184
185
|
|
|
186
|
+
# Companion owns the log used as native transcript ownership proof. Do not
|
|
187
|
+
# silently override a user's log or let a second flag redirect that proof.
|
|
188
|
+
for _CV_ARG in "$@"; do
|
|
189
|
+
case "$_CV_ARG" in
|
|
190
|
+
--log-file|--log-file=*|-log-file|-log-file=*)
|
|
191
|
+
echo 'Error: CodeVibe Companion owns --log-file for session isolation. Run agy directly to use a custom log.' >&2
|
|
192
|
+
exit 2
|
|
193
|
+
;;
|
|
194
|
+
esac
|
|
195
|
+
done
|
|
196
|
+
unset _CV_ARG
|
|
197
|
+
|
|
198
|
+
# Require the tested native contract before installer, runtime, or session work.
|
|
199
|
+
# Metadata passthrough above and direct `agy` use remain unchanged.
|
|
200
|
+
require_native_sync_support() {
|
|
201
|
+
local version="$1" binary="$2" help major minor patch
|
|
202
|
+
if [[ "$version" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
|
|
203
|
+
major=$((10#${BASH_REMATCH[1]}))
|
|
204
|
+
minor=$((10#${BASH_REMATCH[2]}))
|
|
205
|
+
patch=$((10#${BASH_REMATCH[3]}))
|
|
206
|
+
if (( major > 1 || (major == 1 && (minor > 4 || (minor == 4 && patch >= 3))) )); then
|
|
207
|
+
if help="$("$binary" --help 2>&1)" &&
|
|
208
|
+
[[ "$help" =~ (^|[[:space:]])--log-file([=[:space:]]|$) ]]; then
|
|
209
|
+
return 0
|
|
210
|
+
fi
|
|
211
|
+
fi
|
|
212
|
+
fi
|
|
213
|
+
echo 'Error: CodeVibe Companion requires Antigravity CLI 1.4.3+ with --log-file support. Update agy, then retry. Direct agy use is unchanged.' >&2
|
|
214
|
+
return 1
|
|
215
|
+
}
|
|
216
|
+
|
|
185
217
|
# ─── Environment probes ───────────────────────────────────────────────
|
|
186
218
|
_CV_NODE_VER="missing"
|
|
187
219
|
command -v node >/dev/null 2>&1 && _CV_NODE_VER="$(node -v 2>/dev/null | cv_sanitize)"
|
|
@@ -191,7 +223,7 @@ command -v tmux >/dev/null 2>&1 && _CV_TMUX_VER="$(tmux -V 2>/dev/null | cv_sani
|
|
|
191
223
|
[ -z "$_CV_TMUX_VER" ] && _CV_TMUX_VER="unknown"
|
|
192
224
|
_CV_AGY_BIN="$(resolve_agy_bin)"
|
|
193
225
|
_CV_AGY_VER="missing"
|
|
194
|
-
[ -n "$_CV_AGY_BIN" ] && _CV_AGY_VER="$("$_CV_AGY_BIN" --version 2>/dev/null
|
|
226
|
+
[ -n "$_CV_AGY_BIN" ] && _CV_AGY_VER="$(cv_sanitize "$("$_CV_AGY_BIN" --version 2>/dev/null)")"
|
|
195
227
|
[ -z "$_CV_AGY_VER" ] && _CV_AGY_VER="unknown"
|
|
196
228
|
_CV_OS_VER="$(uname -s | cv_sanitize)"
|
|
197
229
|
[ -z "$_CV_OS_VER" ] && _CV_OS_VER="unknown"
|
|
@@ -262,6 +294,11 @@ if [ -z "$_CV_AGY_BIN" ] || ! [ -f "$_CV_AGY_BIN" ] || ! [ -x "$_CV_AGY_BIN" ];
|
|
|
262
294
|
exit 1
|
|
263
295
|
fi
|
|
264
296
|
|
|
297
|
+
if ! require_native_sync_support "$_CV_AGY_VER" "$_CV_AGY_BIN"; then
|
|
298
|
+
cv_failed "agy_sync_contract_unsupported"
|
|
299
|
+
exit 2
|
|
300
|
+
fi
|
|
301
|
+
|
|
265
302
|
if ! command -v node &> /dev/null; then
|
|
266
303
|
echo "Error: Node.js is required but not installed."
|
|
267
304
|
echo "Install Node.js 22+ from: https://nodejs.org"
|
|
@@ -348,6 +385,14 @@ rm -f "$RUNTIME_DIR/conn.json" "$RUNTIME_DIR/claims.json"
|
|
|
348
385
|
# Also tighten the runtime base — best-effort, no fatal on failure.
|
|
349
386
|
chmod 0700 "$RUNTIME_BASE" 2>/dev/null || true
|
|
350
387
|
|
|
388
|
+
# The native process and daemon share only this launch's fresh regular log.
|
|
389
|
+
# Refuse a stale PID-reuse file rather than trusting its conversation identity.
|
|
390
|
+
NATIVE_CLI_LOG="$RUNTIME_DIR/native-cli.log"
|
|
391
|
+
if ! (umask 077; set -o noclobber; : > "$NATIVE_CLI_LOG"); then
|
|
392
|
+
echo 'Error: this Companion runtime already contains a native log; refusing ambiguous ownership.' >&2
|
|
393
|
+
exit 1
|
|
394
|
+
fi
|
|
395
|
+
|
|
351
396
|
log "Wrapper PID=$WRAPPER_PID; token=$(printf '%s' "$BEARER_TOKEN" | cut -c1-8)...; port=$MCP_PORT; runtime=$RUNTIME_DIR"
|
|
352
397
|
|
|
353
398
|
# Export env vars consumed by the MCP server + any in-tree tooling.
|
|
@@ -481,7 +526,8 @@ escaped_exit_file="$(escape_sq "$_CV_AGY_EXIT_FILE")"
|
|
|
481
526
|
escaped_conn_file="$(escape_sq "$RUNTIME_DIR/conn.json")"
|
|
482
527
|
escaped_mcp_log="$(escape_sq "$MCP_LOG_FILE")"
|
|
483
528
|
escaped_node_bin="$(escape_sq "$(command -v node)")"
|
|
484
|
-
|
|
529
|
+
escaped_native_log="$(escape_sq "$NATIVE_CLI_LOG")"
|
|
530
|
+
AGY_CMD="'$escaped_bin' --log-file '$escaped_native_log'"
|
|
485
531
|
for arg in "$@"; do
|
|
486
532
|
escaped_arg="$(escape_sq "$arg")"
|
|
487
533
|
AGY_CMD="$AGY_CMD '$escaped_arg'"
|
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.87",
|
|
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": {
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"node": ">=22.0.0"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@quantiya/codevibe-core": "^2.0.
|
|
49
|
+
"@quantiya/codevibe-core": "^2.0.76",
|
|
50
50
|
"chokidar": "^5.0.0",
|
|
51
51
|
"dotenv": "^16.6.1",
|
|
52
52
|
"express": "^5.1.0",
|