@quantiya/codevibe-antigravity-plugin 2.0.101 → 2.0.102
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 +145 -8
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -2448,6 +2448,10 @@ var TmuxPaneObserver = class _TmuxPaneObserver extends import_events2.EventEmitt
|
|
|
2448
2448
|
* start()/stop() lifecycle and doesn't distinguish debounce
|
|
2449
2449
|
* cycles. (Stage 2 iter-4 R1 HIGH finding 2026-05-21.) */
|
|
2450
2450
|
this.debounceCycleId = 0;
|
|
2451
|
+
this.clearDebounceTimer = null;
|
|
2452
|
+
this.clearDebounceWindowOpenedAt = null;
|
|
2453
|
+
this.clearDebounceCycleId = 0;
|
|
2454
|
+
this.clearFlushInFlight = false;
|
|
2451
2455
|
this.pipeFilePath = options.pipeFilePath ?? path6.join(os5.tmpdir(), `codevibe-agy-pane-${process.pid}.log`);
|
|
2452
2456
|
this.skipTmuxCommands = options.skipTmuxCommands ?? false;
|
|
2453
2457
|
this.captureFn = options.captureFn ?? null;
|
|
@@ -2482,6 +2486,9 @@ var TmuxPaneObserver = class _TmuxPaneObserver extends import_events2.EventEmitt
|
|
|
2482
2486
|
* and they paint atomically. */
|
|
2483
2487
|
this.QUESTION_SETTLE_DELAY_MS = 100;
|
|
2484
2488
|
}
|
|
2489
|
+
static {
|
|
2490
|
+
this.CLEAR_CONFIRM_DELAY_MS = 50;
|
|
2491
|
+
}
|
|
2485
2492
|
/**
|
|
2486
2493
|
* Start observing the tmux pane targeted by `sessionName`. If sessionName
|
|
2487
2494
|
* is omitted, read $CODEVIBE_AGY_TMUX_TARGET from env (set by the
|
|
@@ -2707,6 +2714,7 @@ var TmuxPaneObserver = class _TmuxPaneObserver extends import_events2.EventEmitt
|
|
|
2707
2714
|
* goes idle (e.g. an animated spinner inside the approval UI).
|
|
2708
2715
|
*/
|
|
2709
2716
|
scheduleDebouncedEmit(candidate) {
|
|
2717
|
+
this.cancelClearDebounce();
|
|
2710
2718
|
const now = Date.now();
|
|
2711
2719
|
this.pendingEmitCandidate = candidate;
|
|
2712
2720
|
if (this.debounceWindowOpenedAt === null) {
|
|
@@ -2754,7 +2762,10 @@ var TmuxPaneObserver = class _TmuxPaneObserver extends import_events2.EventEmitt
|
|
|
2754
2762
|
let candidate = staged;
|
|
2755
2763
|
try {
|
|
2756
2764
|
const freshSnapshot = await this.captureSnapshot();
|
|
2765
|
+
if (this.generation !== scheduledGeneration || !this.started) return;
|
|
2766
|
+
if (this.debounceCycleId !== scheduledCycleId) return;
|
|
2757
2767
|
if (freshSnapshot && looksLikePromptSnapshot(freshSnapshot)) {
|
|
2768
|
+
this.cancelClearDebounce();
|
|
2758
2769
|
candidate = {
|
|
2759
2770
|
rawDelta: staged.rawDelta,
|
|
2760
2771
|
snapshot: freshSnapshot,
|
|
@@ -2774,6 +2785,7 @@ var TmuxPaneObserver = class _TmuxPaneObserver extends import_events2.EventEmitt
|
|
|
2774
2785
|
const settledHeader = settled ? extractQuestionHeader(settled) : null;
|
|
2775
2786
|
const sameBody = !!firstHeader && !!settledHeader && firstHeader.body === settledHeader.body;
|
|
2776
2787
|
if (settled && looksLikePromptSnapshot(settled) && detectActivePromptKind(settled) === "question" && sameBody) {
|
|
2788
|
+
this.cancelClearDebounce();
|
|
2777
2789
|
candidate = {
|
|
2778
2790
|
rawDelta: staged.rawDelta,
|
|
2779
2791
|
snapshot: settled,
|
|
@@ -2825,6 +2837,64 @@ var TmuxPaneObserver = class _TmuxPaneObserver extends import_events2.EventEmitt
|
|
|
2825
2837
|
this.pendingEmitCandidate = null;
|
|
2826
2838
|
this.debounceWindowOpenedAt = null;
|
|
2827
2839
|
this.debounceCycleId += 1;
|
|
2840
|
+
this.cancelClearDebounce();
|
|
2841
|
+
}
|
|
2842
|
+
cancelClearDebounce() {
|
|
2843
|
+
if (this.clearDebounceTimer) {
|
|
2844
|
+
clearTimeout(this.clearDebounceTimer);
|
|
2845
|
+
this.clearDebounceTimer = null;
|
|
2846
|
+
}
|
|
2847
|
+
this.clearDebounceWindowOpenedAt = null;
|
|
2848
|
+
this.clearFlushInFlight = false;
|
|
2849
|
+
this.clearDebounceCycleId += 1;
|
|
2850
|
+
}
|
|
2851
|
+
/**
|
|
2852
|
+
* Schedule a debounced check for whether the active prompt UI has genuinely
|
|
2853
|
+
* cleared from the pane. Ink full-viewport redraws write upper scrollback
|
|
2854
|
+
* chunks (`!looksLikePromptDelta(chunk)`) before repainting the bottom
|
|
2855
|
+
* chooser; capturing immediately mid-redraw would false-fire
|
|
2856
|
+
* `prompt-cleared` and cause the repainted chooser to re-emit a duplicate card.
|
|
2857
|
+
* Bounded by DEBOUNCE_MAX_WAIT_MS (600ms) so continuous post-approval output
|
|
2858
|
+
* never starves `prompt-cleared` (Claude R1-F2, Codex R1-F3).
|
|
2859
|
+
*/
|
|
2860
|
+
scheduleDebouncedClear() {
|
|
2861
|
+
if (!this.approvalUIActive || this.clearFlushInFlight) return;
|
|
2862
|
+
const now = Date.now();
|
|
2863
|
+
if (this.clearDebounceWindowOpenedAt === null) {
|
|
2864
|
+
this.clearDebounceWindowOpenedAt = now;
|
|
2865
|
+
}
|
|
2866
|
+
const elapsed = now - this.clearDebounceWindowOpenedAt;
|
|
2867
|
+
const cappedRemaining = Math.max(0, _TmuxPaneObserver.DEBOUNCE_MAX_WAIT_MS - elapsed);
|
|
2868
|
+
if (cappedRemaining === 0 && this.clearDebounceTimer !== null) {
|
|
2869
|
+
return;
|
|
2870
|
+
}
|
|
2871
|
+
const waitMs = Math.min(_TmuxPaneObserver.DEBOUNCE_QUIET_MS, cappedRemaining);
|
|
2872
|
+
if (this.clearDebounceTimer) clearTimeout(this.clearDebounceTimer);
|
|
2873
|
+
this.clearDebounceCycleId += 1;
|
|
2874
|
+
const myGeneration = this.generation;
|
|
2875
|
+
const myCycleId = this.clearDebounceCycleId;
|
|
2876
|
+
this.clearDebounceTimer = setTimeout(() => {
|
|
2877
|
+
void this.flushDebouncedClear(myGeneration, myCycleId);
|
|
2878
|
+
}, waitMs);
|
|
2879
|
+
if (this.clearDebounceTimer.unref) this.clearDebounceTimer.unref();
|
|
2880
|
+
}
|
|
2881
|
+
async flushDebouncedClear(scheduledGeneration, scheduledCycleId) {
|
|
2882
|
+
if (this.generation !== scheduledGeneration || !this.started) return;
|
|
2883
|
+
if (this.clearDebounceCycleId !== scheduledCycleId) return;
|
|
2884
|
+
this.clearDebounceTimer = null;
|
|
2885
|
+
if (!this.approvalUIActive) {
|
|
2886
|
+
this.clearDebounceWindowOpenedAt = null;
|
|
2887
|
+
return;
|
|
2888
|
+
}
|
|
2889
|
+
this.clearFlushInFlight = true;
|
|
2890
|
+
try {
|
|
2891
|
+
await this.checkPromptCleared(scheduledGeneration, scheduledCycleId);
|
|
2892
|
+
} finally {
|
|
2893
|
+
if (this.clearDebounceCycleId === scheduledCycleId) {
|
|
2894
|
+
this.clearFlushInFlight = false;
|
|
2895
|
+
this.clearDebounceWindowOpenedAt = null;
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2828
2898
|
}
|
|
2829
2899
|
/**
|
|
2830
2900
|
* Public hook for tests / drivers: trigger a read+process cycle as if
|
|
@@ -2846,6 +2916,11 @@ var TmuxPaneObserver = class _TmuxPaneObserver extends import_events2.EventEmitt
|
|
|
2846
2916
|
this.debounceTimer = null;
|
|
2847
2917
|
await this.flushDebouncedEmit(this.generation, this.debounceCycleId);
|
|
2848
2918
|
}
|
|
2919
|
+
if (this.clearDebounceTimer) {
|
|
2920
|
+
clearTimeout(this.clearDebounceTimer);
|
|
2921
|
+
this.clearDebounceTimer = null;
|
|
2922
|
+
await this.flushDebouncedClear(this.generation, this.clearDebounceCycleId);
|
|
2923
|
+
}
|
|
2849
2924
|
}
|
|
2850
2925
|
async processFileChanges() {
|
|
2851
2926
|
if (this.processing) {
|
|
@@ -2863,7 +2938,7 @@ var TmuxPaneObserver = class _TmuxPaneObserver extends import_events2.EventEmitt
|
|
|
2863
2938
|
if (!chunk) continue;
|
|
2864
2939
|
if (!looksLikePromptDelta(chunk)) {
|
|
2865
2940
|
if (this.approvalUIActive) {
|
|
2866
|
-
|
|
2941
|
+
this.scheduleDebouncedClear();
|
|
2867
2942
|
}
|
|
2868
2943
|
continue;
|
|
2869
2944
|
}
|
|
@@ -2871,10 +2946,11 @@ var TmuxPaneObserver = class _TmuxPaneObserver extends import_events2.EventEmitt
|
|
|
2871
2946
|
if (this.generation !== myGeneration || !this.started) return;
|
|
2872
2947
|
if (!snapshot || !looksLikePromptSnapshot(snapshot)) {
|
|
2873
2948
|
if (this.approvalUIActive) {
|
|
2874
|
-
|
|
2949
|
+
this.scheduleDebouncedClear();
|
|
2875
2950
|
}
|
|
2876
2951
|
continue;
|
|
2877
2952
|
}
|
|
2953
|
+
this.cancelClearDebounce();
|
|
2878
2954
|
const snapshotHash = hashPromptSnapshot(snapshot);
|
|
2879
2955
|
if (snapshotHash === this.lastPromptHash) {
|
|
2880
2956
|
continue;
|
|
@@ -2898,13 +2974,31 @@ var TmuxPaneObserver = class _TmuxPaneObserver extends import_events2.EventEmitt
|
|
|
2898
2974
|
}
|
|
2899
2975
|
}
|
|
2900
2976
|
/** Called when we suspect the approval UI may have vanished. Does a
|
|
2901
|
-
* fresh capture; if the pane no longer shows approval UI
|
|
2902
|
-
* 'prompt-cleared'. Generation-checked
|
|
2903
|
-
|
|
2977
|
+
* fresh capture; if the pane no longer shows approval UI across two
|
|
2978
|
+
* consecutive captures, fires 'prompt-cleared'. Generation-checked
|
|
2979
|
+
* and cycle-checked to suppress emit after stop() or concurrent prompt
|
|
2980
|
+
* re-assertion (Claude R1-F3, Codex R1-F4). */
|
|
2981
|
+
async checkPromptCleared(myGeneration, expectedClearCycleId) {
|
|
2904
2982
|
try {
|
|
2905
2983
|
const snapshot = await this.captureSnapshot();
|
|
2906
2984
|
if (this.generation !== myGeneration || !this.started) return;
|
|
2907
|
-
if (
|
|
2985
|
+
if (expectedClearCycleId !== void 0 && this.clearDebounceCycleId !== expectedClearCycleId) {
|
|
2986
|
+
return;
|
|
2987
|
+
}
|
|
2988
|
+
if (looksLikePromptSnapshot(snapshot)) {
|
|
2989
|
+
return;
|
|
2990
|
+
}
|
|
2991
|
+
await sleep(_TmuxPaneObserver.CLEAR_CONFIRM_DELAY_MS);
|
|
2992
|
+
if (this.generation !== myGeneration || !this.started) return;
|
|
2993
|
+
if (expectedClearCycleId !== void 0 && this.clearDebounceCycleId !== expectedClearCycleId) {
|
|
2994
|
+
return;
|
|
2995
|
+
}
|
|
2996
|
+
const confirmSnapshot = await this.captureSnapshot();
|
|
2997
|
+
if (this.generation !== myGeneration || !this.started) return;
|
|
2998
|
+
if (expectedClearCycleId !== void 0 && this.clearDebounceCycleId !== expectedClearCycleId) {
|
|
2999
|
+
return;
|
|
3000
|
+
}
|
|
3001
|
+
if (!looksLikePromptSnapshot(confirmSnapshot)) {
|
|
2908
3002
|
this.approvalUIActive = false;
|
|
2909
3003
|
this.lastPromptHash = null;
|
|
2910
3004
|
this.currentApprovalHeader = null;
|
|
@@ -4701,8 +4795,16 @@ function extractQuestionHeader(snapshot) {
|
|
|
4701
4795
|
}
|
|
4702
4796
|
function hashPromptSnapshot(snapshot) {
|
|
4703
4797
|
let normalized = stripAnsiAndExpandRep(snapshot).replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/[ \t]+\n/g, "\n").trim();
|
|
4704
|
-
|
|
4798
|
+
const activeKind = detectActivePromptKind(normalized);
|
|
4799
|
+
if (activeKind === "system_error") {
|
|
4705
4800
|
normalized = normalized.replace(/\bResets\s+in\s+\d+[dhms](?:\s*\d+[dhms])*\.?/gi, "Resets in <duration>");
|
|
4801
|
+
} else if (activeKind === "approval") {
|
|
4802
|
+
const fileEdit = extractFileEditPrompt(snapshot);
|
|
4803
|
+
if (fileEdit?.body) {
|
|
4804
|
+
normalized = `${normalized}
|
|
4805
|
+
@@file-edit-body@@
|
|
4806
|
+
${fileEdit.body}`;
|
|
4807
|
+
}
|
|
4706
4808
|
}
|
|
4707
4809
|
return (0, import_crypto.createHash)("sha256").update(normalized).digest("hex");
|
|
4708
4810
|
}
|
|
@@ -4762,7 +4864,7 @@ function parseApprovalSnapshot(snapshot) {
|
|
|
4762
4864
|
}
|
|
4763
4865
|
}
|
|
4764
4866
|
if (lastFileEditIdx > lastCmdApprovalIdx) {
|
|
4765
|
-
const fileEdit = extractFileEditPrompt(
|
|
4867
|
+
const fileEdit = extractFileEditPrompt(snapshot);
|
|
4766
4868
|
if (fileEdit) {
|
|
4767
4869
|
return parseFileEditSnapshot(stripped, snapshot, fileEdit);
|
|
4768
4870
|
}
|
|
@@ -4788,6 +4890,13 @@ function buildApprovalSemanticKey(candidate) {
|
|
|
4788
4890
|
};
|
|
4789
4891
|
return (0, import_crypto2.createHash)("sha256").update(JSON.stringify(payload)).digest("hex");
|
|
4790
4892
|
}
|
|
4893
|
+
function buildApprovalRedrawSemanticKey(candidate) {
|
|
4894
|
+
const rightTrimmedBody = (candidate.body ?? "").replace(/\r\n/g, "\n").split("\n").map((line) => line.replace(/[ \t]+$/g, "")).join("\n");
|
|
4895
|
+
return buildApprovalSemanticKey({
|
|
4896
|
+
...candidate,
|
|
4897
|
+
body: rightTrimmedBody
|
|
4898
|
+
});
|
|
4899
|
+
}
|
|
4791
4900
|
function hasNegativeApprovalOption(options) {
|
|
4792
4901
|
return options.some((option) => /^(?:no|deny|reject|cancel)\b/i.test(option.text.trim()));
|
|
4793
4902
|
}
|
|
@@ -5763,6 +5872,32 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
|
|
|
5763
5872
|
});
|
|
5764
5873
|
return null;
|
|
5765
5874
|
}
|
|
5875
|
+
if (candidate.kind === "approval") {
|
|
5876
|
+
const candidateSemanticKey = buildApprovalSemanticKey(candidate);
|
|
5877
|
+
const candidateRedrawSemanticKey = buildApprovalRedrawSemanticKey(candidate);
|
|
5878
|
+
const existing = [...this.pendingPrompts.values()].find(
|
|
5879
|
+
(p) => p.conversationId === conversationId && p.kind === candidate.kind && (p.paneSemanticKey === candidateSemanticKey || p.paneRedrawSemanticKey !== void 0 && p.paneRedrawSemanticKey === candidateRedrawSemanticKey)
|
|
5880
|
+
);
|
|
5881
|
+
if (existing) {
|
|
5882
|
+
if (existing.paneHash && existing.paneHash !== candidate.paneHash) {
|
|
5883
|
+
this.paneOnlyEmittedHashes.delete(existing.paneHash);
|
|
5884
|
+
}
|
|
5885
|
+
existing.paneHash = candidate.paneHash;
|
|
5886
|
+
if (candidate.paneHash) {
|
|
5887
|
+
this.paneOnlyEmittedHashes.add(candidate.paneHash);
|
|
5888
|
+
}
|
|
5889
|
+
existing.paneSemanticKey = candidateSemanticKey;
|
|
5890
|
+
existing.paneRedrawSemanticKey = candidateRedrawSemanticKey;
|
|
5891
|
+
existing.body = candidate.body;
|
|
5892
|
+
logger.debug("Skipping pane-only re-emit for active approval with matching semantic key", {
|
|
5893
|
+
promptId: existing.promptId,
|
|
5894
|
+
paneSemanticKey: candidateSemanticKey.substring(0, 16),
|
|
5895
|
+
paneRedrawSemanticKey: candidateRedrawSemanticKey.substring(0, 16),
|
|
5896
|
+
paneHash: candidate.paneHash.substring(0, 16)
|
|
5897
|
+
});
|
|
5898
|
+
return null;
|
|
5899
|
+
}
|
|
5900
|
+
}
|
|
5766
5901
|
if (candidate.kind === "question") {
|
|
5767
5902
|
const paneQuestionText = candidate.headerText || candidate.fullHeaderLine;
|
|
5768
5903
|
const candidateSemanticKey = buildApprovalSemanticKey(candidate);
|
|
@@ -5999,6 +6134,7 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
|
|
|
5999
6134
|
body: synthLineMatch ? synthLineMatch.remainingBody || void 0 : candidate.body,
|
|
6000
6135
|
paneOptions: candidate.options,
|
|
6001
6136
|
paneSemanticKey: buildApprovalSemanticKey(candidate),
|
|
6137
|
+
paneRedrawSemanticKey: buildApprovalRedrawSemanticKey(candidate),
|
|
6002
6138
|
kind: candidate.kind,
|
|
6003
6139
|
isHookPrompt: isHookCall,
|
|
6004
6140
|
// Carry hook ownership forward so pane redraws do not drop it! (Codex S2-Codex-R6-F15)
|
|
@@ -6319,6 +6455,7 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
|
|
|
6319
6455
|
body: candidate.body,
|
|
6320
6456
|
paneOptions: candidate.options,
|
|
6321
6457
|
paneSemanticKey: buildApprovalSemanticKey(candidate),
|
|
6458
|
+
paneRedrawSemanticKey: buildApprovalRedrawSemanticKey(candidate),
|
|
6322
6459
|
kind: candidate.kind,
|
|
6323
6460
|
emittedAt: Date.now(),
|
|
6324
6461
|
ttlMs: this.promptTtlMs
|
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.102",
|
|
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": {
|