@quantiya/codevibe-antigravity-plugin 2.0.15 → 2.0.16
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 +159 -68
- package/package.json +2 -2
package/dist/server.js
CHANGED
|
@@ -2092,7 +2092,7 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
|
|
|
2092
2092
|
*/
|
|
2093
2093
|
onPanePromptCleared() {
|
|
2094
2094
|
if (this.pendingPrompts.size === 0 && this.paneOnlyEmittedHashes.size === 0) {
|
|
2095
|
-
return;
|
|
2095
|
+
return [];
|
|
2096
2096
|
}
|
|
2097
2097
|
const now = Date.now();
|
|
2098
2098
|
const cleared = [];
|
|
@@ -2107,6 +2107,7 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
|
|
|
2107
2107
|
count: cleared.length
|
|
2108
2108
|
});
|
|
2109
2109
|
}
|
|
2110
|
+
return cleared;
|
|
2110
2111
|
}
|
|
2111
2112
|
resolvePrompt(promptId) {
|
|
2112
2113
|
const state = this.pendingPrompts.get(promptId);
|
|
@@ -2439,6 +2440,37 @@ var PromptResponder = class {
|
|
|
2439
2440
|
};
|
|
2440
2441
|
}
|
|
2441
2442
|
}
|
|
2443
|
+
/** Dismiss whichever approval UI is active without binding to a stale promptId. */
|
|
2444
|
+
async dismissActivePrompt() {
|
|
2445
|
+
const target = this.resolveTmuxTarget();
|
|
2446
|
+
if (!target) return { ok: false, reason: "no-tmux-target" };
|
|
2447
|
+
if (!this.paneObserver) {
|
|
2448
|
+
return { ok: false, reason: "prompt-probe-failed" };
|
|
2449
|
+
}
|
|
2450
|
+
try {
|
|
2451
|
+
const before = await this.paneObserver.probeApprovalUIActive();
|
|
2452
|
+
if (!before.probeSucceeded) return { ok: false, reason: "prompt-probe-failed" };
|
|
2453
|
+
if (!before.active) return { ok: true };
|
|
2454
|
+
await this.sendKey(target, "Escape");
|
|
2455
|
+
await delay(250);
|
|
2456
|
+
const after = await this.paneObserver.probeApprovalUIActive();
|
|
2457
|
+
if (!after.probeSucceeded) return { ok: false, reason: "prompt-probe-failed" };
|
|
2458
|
+
if (after.active) {
|
|
2459
|
+
return {
|
|
2460
|
+
ok: false,
|
|
2461
|
+
reason: "prompt-superseded",
|
|
2462
|
+
details: "approval chooser remained active after Escape"
|
|
2463
|
+
};
|
|
2464
|
+
}
|
|
2465
|
+
return { ok: true };
|
|
2466
|
+
} catch (error) {
|
|
2467
|
+
return {
|
|
2468
|
+
ok: false,
|
|
2469
|
+
reason: "tmux-failed",
|
|
2470
|
+
details: error instanceof Error ? error.message : String(error)
|
|
2471
|
+
};
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2442
2474
|
// ─── Tmux primitives ────────────────────────────────────────────────────
|
|
2443
2475
|
async typeLiteral(target, text) {
|
|
2444
2476
|
const escaped = text.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\$/g, "\\$").replace(/`/g, "\\`");
|
|
@@ -3051,6 +3083,8 @@ var McpServer = class _McpServer {
|
|
|
3051
3083
|
// refreshE1Ttls finds it → refreshes → returns no aged-out → never re-fires,
|
|
3052
3084
|
// leaving the backend row permanently pointed at the RETIRED session = under-nag.
|
|
3053
3085
|
this.e1NeedsReRaise = /* @__PURE__ */ new Set();
|
|
3086
|
+
this.e1NeedsResolution = /* @__PURE__ */ new Set();
|
|
3087
|
+
this.e1ResolutionInFlight = /* @__PURE__ */ new Map();
|
|
3054
3088
|
// E1 (§6a-4 / F2) — bounded exponential backoff for the BLOCK-UNTIL-ACK
|
|
3055
3089
|
// retirement re-raise (the pre-heartbeat drain in createLaunchSession, which must
|
|
3056
3090
|
// ACK before the replacement's heartbeat advertises liveness — design line 93).
|
|
@@ -3263,7 +3297,15 @@ var McpServer = class _McpServer {
|
|
|
3263
3297
|
});
|
|
3264
3298
|
});
|
|
3265
3299
|
this.addListener(this.paneObserver, "prompt-cleared", () => {
|
|
3266
|
-
this.approvalDetector.onPanePromptCleared();
|
|
3300
|
+
const promptIds = this.approvalDetector.onPanePromptCleared();
|
|
3301
|
+
const session = this.session;
|
|
3302
|
+
if (!session) return;
|
|
3303
|
+
void this.resolveAcceptedInputPrompts(session, promptIds).catch((error) => {
|
|
3304
|
+
logger.warn("Failed to resolve backend prompts after pane cleared", {
|
|
3305
|
+
sessionId: session.sessionId,
|
|
3306
|
+
error: String(error)
|
|
3307
|
+
});
|
|
3308
|
+
});
|
|
3267
3309
|
});
|
|
3268
3310
|
await this.paneObserver.start(this.tmuxTarget);
|
|
3269
3311
|
} else {
|
|
@@ -3681,6 +3723,8 @@ var McpServer = class _McpServer {
|
|
|
3681
3723
|
}
|
|
3682
3724
|
}
|
|
3683
3725
|
if (input.type === import_codevibe_core4.EventType.USER_PROMPT && input.source === import_codevibe_core4.EventSource.DESKTOP) {
|
|
3726
|
+
const clearedPromptIds = this.approvalDetector.onPanePromptCleared();
|
|
3727
|
+
await this.resolveAcceptedInputPrompts(session, clearedPromptIds);
|
|
3684
3728
|
const dedupe = this.mobileDeduper.consumeIfDuplicate(session.sessionId, input.content);
|
|
3685
3729
|
if (dedupe) {
|
|
3686
3730
|
logger.info("Suppressed desktop echo of mobile prompt", {
|
|
@@ -3776,6 +3820,69 @@ var McpServer = class _McpServer {
|
|
|
3776
3820
|
entry.contentPlain = contentPlain;
|
|
3777
3821
|
entry.metadataPlain = metadataPlain;
|
|
3778
3822
|
}
|
|
3823
|
+
async resolveE1PluginApproval(sessionId, promptId) {
|
|
3824
|
+
const inFlight = this.e1ResolutionInFlight.get(promptId);
|
|
3825
|
+
if (inFlight) return inFlight;
|
|
3826
|
+
const entry = this.e1PromptRaises.get(promptId);
|
|
3827
|
+
if (!entry) {
|
|
3828
|
+
this.e1NeedsResolution.delete(promptId);
|
|
3829
|
+
return true;
|
|
3830
|
+
}
|
|
3831
|
+
this.e1NeedsResolution.add(promptId);
|
|
3832
|
+
const operation = (async () => {
|
|
3833
|
+
try {
|
|
3834
|
+
const ack = await this.appSyncClient.createEvent({
|
|
3835
|
+
sessionId: entry.record.sessionId || sessionId,
|
|
3836
|
+
type: import_codevibe_core4.EventType.NOTIFICATION,
|
|
3837
|
+
source: import_codevibe_core4.EventSource.DESKTOP,
|
|
3838
|
+
resolvedPromptId: promptId,
|
|
3839
|
+
ownerToken: entry.record.ownerToken
|
|
3840
|
+
});
|
|
3841
|
+
if (ack?.promptId !== promptId) return false;
|
|
3842
|
+
this.e1NeedsResolution.delete(promptId);
|
|
3843
|
+
this.e1NeedsReRaise.delete(promptId);
|
|
3844
|
+
this.e1PromptRaises.delete(promptId);
|
|
3845
|
+
for (const [key, indexedPromptId] of this.e1ContentKeyIndex.entries()) {
|
|
3846
|
+
if (indexedPromptId === promptId) this.e1ContentKeyIndex.delete(key);
|
|
3847
|
+
}
|
|
3848
|
+
return true;
|
|
3849
|
+
} catch (error) {
|
|
3850
|
+
logger.warn("E1: plugin prompt resolution failed; retained for retry", {
|
|
3851
|
+
sessionId,
|
|
3852
|
+
promptId,
|
|
3853
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3854
|
+
});
|
|
3855
|
+
return false;
|
|
3856
|
+
}
|
|
3857
|
+
})();
|
|
3858
|
+
this.e1ResolutionInFlight.set(promptId, operation);
|
|
3859
|
+
try {
|
|
3860
|
+
return await operation;
|
|
3861
|
+
} finally {
|
|
3862
|
+
if (this.e1ResolutionInFlight.get(promptId) === operation) {
|
|
3863
|
+
this.e1ResolutionInFlight.delete(promptId);
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3866
|
+
}
|
|
3867
|
+
async resolveAcceptedInputPrompts(session, observedPromptIds = []) {
|
|
3868
|
+
const promptIds = new Set(observedPromptIds);
|
|
3869
|
+
for (const entry of this.e1PromptRaises.values()) {
|
|
3870
|
+
if (entry.record.sessionId === session.sessionId) promptIds.add(entry.record.promptId);
|
|
3871
|
+
}
|
|
3872
|
+
for (const promptId of promptIds) {
|
|
3873
|
+
await this.resolveE1PluginApproval(session.sessionId, promptId);
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3876
|
+
async drainPendingE1Resolutions() {
|
|
3877
|
+
for (const promptId of [...this.e1NeedsResolution]) {
|
|
3878
|
+
const entry = this.e1PromptRaises.get(promptId);
|
|
3879
|
+
if (!entry) {
|
|
3880
|
+
this.e1NeedsResolution.delete(promptId);
|
|
3881
|
+
continue;
|
|
3882
|
+
}
|
|
3883
|
+
await this.resolveE1PluginApproval(entry.record.sessionId, promptId);
|
|
3884
|
+
}
|
|
3885
|
+
}
|
|
3779
3886
|
/**
|
|
3780
3887
|
* E1 (§6a-2b) — emit the badge-only carrier for a SUPPRESSED prompt (options
|
|
3781
3888
|
* couldn't be parsed → mobile must NOT fabricate any). A NOTIFICATION carrying
|
|
@@ -4036,6 +4143,7 @@ var McpServer = class _McpServer {
|
|
|
4036
4143
|
* re-materialized via reRaiseOneE1 (return-driven re-raise).
|
|
4037
4144
|
*/
|
|
4038
4145
|
async refreshE1Ttls() {
|
|
4146
|
+
await this.drainPendingE1Resolutions();
|
|
4039
4147
|
await this.drainPendingE1ReRaises();
|
|
4040
4148
|
await this.drainE1RetirementPark();
|
|
4041
4149
|
const session = this.session;
|
|
@@ -4413,7 +4521,7 @@ var McpServer = class _McpServer {
|
|
|
4413
4521
|
setAtWallMs: Date.now()
|
|
4414
4522
|
});
|
|
4415
4523
|
}
|
|
4416
|
-
if (evt.type === import_codevibe_core4.EventType.USER_PROMPT) {
|
|
4524
|
+
if (evt.type === import_codevibe_core4.EventType.USER_PROMPT || evt.type === import_codevibe_core4.EventType.PROMPT_RESPONSE) {
|
|
4417
4525
|
let promptContent = evt.content;
|
|
4418
4526
|
const attachments = evt.attachments ?? [];
|
|
4419
4527
|
if (attachments.length > 0) {
|
|
@@ -4449,57 +4557,65 @@ var McpServer = class _McpServer {
|
|
|
4449
4557
|
const pendingPrompts = this.approvalDetector.getPendingPromptsForConversation(
|
|
4450
4558
|
session.conversationId
|
|
4451
4559
|
);
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4560
|
+
const rawMeta = parseMaybeJson(evt.metadata);
|
|
4561
|
+
const meta = rawMeta && typeof rawMeta === "object" ? rawMeta : {};
|
|
4562
|
+
const evtAny = evt;
|
|
4563
|
+
const responsePromptId = evt.type === import_codevibe_core4.EventType.PROMPT_RESPONSE ? typeof meta.promptId === "string" && meta.promptId || typeof meta.prompt_id === "string" && meta.prompt_id || typeof evtAny.promptId === "string" && evtAny.promptId || typeof evtAny.prompt_id === "string" && evtAny.prompt_id || null : null;
|
|
4564
|
+
const advertisedOptionNumbers = Array.from(new Set(
|
|
4565
|
+
pendingPrompts.flatMap((prompt) => Object.keys(prompt.submitMap))
|
|
4566
|
+
));
|
|
4567
|
+
const intent = (0, import_codevibe_core4.classifyMobilePromptInput)(
|
|
4568
|
+
promptContent,
|
|
4569
|
+
advertisedOptionNumbers
|
|
4570
|
+
);
|
|
4571
|
+
if (intent.kind === "option") {
|
|
4572
|
+
const activePrompt = evt.type === import_codevibe_core4.EventType.PROMPT_RESPONSE ? pendingPrompts.find((prompt) => prompt.promptId === responsePromptId) ?? null : pendingPrompts.length === 1 ? pendingPrompts[0] : null;
|
|
4573
|
+
if (!activePrompt || !Object.prototype.hasOwnProperty.call(activePrompt.submitMap, intent.option)) {
|
|
4574
|
+
logger.warn("Numeric input does not identify one current advertised prompt; dropping", {
|
|
4575
|
+
sessionId: session.sessionId,
|
|
4576
|
+
eventPromptId: responsePromptId,
|
|
4577
|
+
pendingPromptCount: pendingPrompts.length,
|
|
4578
|
+
option: intent.option
|
|
4579
|
+
});
|
|
4459
4580
|
return;
|
|
4460
4581
|
}
|
|
4461
|
-
if (
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4582
|
+
if (evt.type === import_codevibe_core4.EventType.PROMPT_RESPONSE) {
|
|
4583
|
+
if (responsePromptId !== activePrompt.promptId) {
|
|
4584
|
+
logger.warn("PROMPT_RESPONSE does not identify the current advertised prompt; dropping", {
|
|
4585
|
+
sessionId: session.sessionId,
|
|
4586
|
+
eventPromptId: responsePromptId,
|
|
4587
|
+
activePromptId: activePrompt.promptId
|
|
4588
|
+
});
|
|
4589
|
+
return;
|
|
4590
|
+
}
|
|
4591
|
+
}
|
|
4592
|
+
this.mobileDeduper.track(session.sessionId, intent.option);
|
|
4593
|
+
const optionResult = await this.promptResponder.sendApprovalReply(
|
|
4594
|
+
activePrompt.promptId,
|
|
4595
|
+
intent.option
|
|
4596
|
+
);
|
|
4597
|
+
if (!optionResult.ok) {
|
|
4598
|
+
this.mobileDeduper.forget(session.sessionId, intent.option);
|
|
4466
4599
|
return;
|
|
4467
4600
|
}
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
await this.emitPromptSafetyNotification(
|
|
4472
|
-
session,
|
|
4473
|
-
"Your message was not sent because more than one desktop approval is pending. Resolve the approvals and send it again."
|
|
4474
|
-
);
|
|
4601
|
+
await this.resolveE1PluginApproval(session.sessionId, activePrompt.promptId);
|
|
4602
|
+
if (!intent.followUpText) {
|
|
4603
|
+
await this.markExecuted(evt);
|
|
4475
4604
|
return;
|
|
4476
4605
|
}
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
);
|
|
4481
|
-
if (!
|
|
4606
|
+
promptContent = intent.followUpText;
|
|
4607
|
+
await new Promise((resolve3) => setTimeout(resolve3, 250));
|
|
4608
|
+
} else {
|
|
4609
|
+
const dismissal = await this.promptResponder.dismissActivePrompt();
|
|
4610
|
+
if (!dismissal.ok) {
|
|
4482
4611
|
await this.emitPromptSafetyNotification(
|
|
4483
4612
|
session,
|
|
4484
|
-
"Your message was not sent because the
|
|
4613
|
+
"Your message was not sent because the desktop approval could not be dismissed safely. Try again or resolve it on the desktop."
|
|
4485
4614
|
);
|
|
4486
4615
|
return;
|
|
4487
4616
|
}
|
|
4488
|
-
this.
|
|
4489
|
-
|
|
4490
|
-
activePrompt.promptId,
|
|
4491
|
-
rejectOption.number
|
|
4492
|
-
);
|
|
4493
|
-
if (!rejectResult.ok) {
|
|
4494
|
-
this.mobileDeduper.forget(session.sessionId, rejectOption.number);
|
|
4495
|
-
logger.warn("Reject-before-free-form failed", {
|
|
4496
|
-
promptId: activePrompt.promptId,
|
|
4497
|
-
reason: rejectResult.reason,
|
|
4498
|
-
details: rejectResult.details
|
|
4499
|
-
});
|
|
4500
|
-
return;
|
|
4501
|
-
}
|
|
4502
|
-
await new Promise((resolve3) => setTimeout(resolve3, 250));
|
|
4617
|
+
const clearedPromptIds = this.approvalDetector.onPanePromptCleared();
|
|
4618
|
+
await this.resolveAcceptedInputPrompts(session, clearedPromptIds);
|
|
4503
4619
|
}
|
|
4504
4620
|
this.mobileDeduper.track(session.sessionId, promptContent);
|
|
4505
4621
|
const result = await this.promptResponder.sendFreeFormWhenNoApprovalActive(promptContent);
|
|
@@ -4511,31 +4627,6 @@ var McpServer = class _McpServer {
|
|
|
4511
4627
|
await this.markExecuted(evt);
|
|
4512
4628
|
return;
|
|
4513
4629
|
}
|
|
4514
|
-
if (evt.type === import_codevibe_core4.EventType.PROMPT_RESPONSE) {
|
|
4515
|
-
const rawMeta = parseMaybeJson(evt.metadata);
|
|
4516
|
-
const meta = rawMeta && typeof rawMeta === "object" ? rawMeta : {};
|
|
4517
|
-
const evtAny = evt;
|
|
4518
|
-
const promptId = typeof meta.promptId === "string" && meta.promptId || typeof meta.prompt_id === "string" && meta.prompt_id || typeof evtAny.promptId === "string" && evtAny.promptId || typeof evtAny.prompt_id === "string" && evtAny.prompt_id || null;
|
|
4519
|
-
if (!promptId) {
|
|
4520
|
-
logger.warn("PROMPT_RESPONSE missing promptId metadata; dropping", {
|
|
4521
|
-
sessionId: session.sessionId
|
|
4522
|
-
});
|
|
4523
|
-
return;
|
|
4524
|
-
}
|
|
4525
|
-
const optionNumber = (evt.content || "").trim();
|
|
4526
|
-
this.mobileDeduper.track(session.sessionId, optionNumber);
|
|
4527
|
-
const result = await this.promptResponder.sendApprovalReply(promptId, optionNumber);
|
|
4528
|
-
if (!result.ok) {
|
|
4529
|
-
this.mobileDeduper.forget(session.sessionId, optionNumber);
|
|
4530
|
-
logger.warn("sendApprovalReply failed", {
|
|
4531
|
-
promptId,
|
|
4532
|
-
reason: result.reason,
|
|
4533
|
-
details: result.details
|
|
4534
|
-
});
|
|
4535
|
-
return;
|
|
4536
|
-
}
|
|
4537
|
-
await this.markExecuted(evt);
|
|
4538
|
-
}
|
|
4539
4630
|
}
|
|
4540
4631
|
async emitPromptSafetyNotification(session, content) {
|
|
4541
4632
|
const input = {
|
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.16",
|
|
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.15",
|
|
52
52
|
"chokidar": "^5.0.0",
|
|
53
53
|
"dotenv": "^16.6.1",
|
|
54
54
|
"express": "^5.1.0",
|