@quantiya/codevibe-antigravity-plugin 2.0.15 → 2.0.17

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.
Files changed (2) hide show
  1. package/dist/server.js +159 -68
  2. 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
- if (pendingPrompts.length === 0) {
4453
- const probe = await this.paneObserver.probeApprovalUIActive();
4454
- if (!probe.probeSucceeded) {
4455
- await this.emitPromptSafetyNotification(
4456
- session,
4457
- "Your message was not sent because the desktop approval state could not be verified. Try again or resolve the prompt on the desktop."
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 (probe.active) {
4462
- await this.emitPromptSafetyNotification(
4463
- session,
4464
- "Your message was not sent because a desktop approval is active but its options are not yet available on mobile. Resolve it on the desktop and send the message again."
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
- if (pendingPrompts.length > 0) {
4470
- if (pendingPrompts.length !== 1) {
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
- const activePrompt = pendingPrompts[0];
4478
- const rejectOption = activePrompt.paneOptions?.find(
4479
- (option) => /^(?:no|deny|reject|cancel)\b/i.test(option.text.trim()) && (activePrompt.submitMap[option.number]?.length ?? 0) > 0
4480
- );
4481
- if (!rejectOption) {
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 active desktop approval has no verified reject option. Resolve it on the desktop and send the message again."
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.mobileDeduper.track(session.sessionId, rejectOption.number);
4489
- const rejectResult = await this.promptResponder.sendApprovalReply(
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.15",
3
+ "version": "2.0.17",
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.14",
51
+ "@quantiya/codevibe-core": "2.0.16",
52
52
  "chokidar": "^5.0.0",
53
53
  "dotenv": "^16.6.1",
54
54
  "express": "^5.1.0",