@quantiya/codevibe-antigravity-plugin 2.0.102 → 2.0.103

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/agy-hook.js CHANGED
@@ -100,8 +100,32 @@ function decodeBoolean(val) {
100
100
  if (typeof val === "number") return val !== 0;
101
101
  return false;
102
102
  }
103
+ function sliceToSerializedBudget(rawText, buildCandidate, maxChars) {
104
+ if (maxChars <= 0) return null;
105
+ const emptyCandidate = buildCandidate("");
106
+ if (emptyCandidate.length > maxChars) {
107
+ return "[]".length <= maxChars ? "[]" : null;
108
+ }
109
+ const fullCandidate = buildCandidate(rawText);
110
+ if (fullCandidate.length <= maxChars) return fullCandidate;
111
+ let low = 0;
112
+ let high = rawText.length;
113
+ let best = emptyCandidate;
114
+ while (low <= high) {
115
+ const mid = low + high >>> 1;
116
+ const candidate = buildCandidate(rawText.slice(0, mid));
117
+ if (candidate.length <= maxChars) {
118
+ best = candidate;
119
+ low = mid + 1;
120
+ } else {
121
+ high = mid - 1;
122
+ }
123
+ }
124
+ return best;
125
+ }
103
126
  function serializeStructuredArg(value, maxChars = MAX_STRUCTURED_ARG_CHARS, keyHint) {
104
127
  try {
128
+ if (maxChars <= 0) return null;
105
129
  let parsed = value;
106
130
  if (typeof value === "string") {
107
131
  const trimmed = value.trim();
@@ -121,15 +145,15 @@ function serializeStructuredArg(value, maxChars = MAX_STRUCTURED_ARG_CHARS, keyH
121
145
  );
122
146
  if (isOptionArray) {
123
147
  const boundedOptions = parsed.slice(0, 30).map((opt, idx) => {
124
- if (typeof opt === "string") return { number: String(idx + 1), text: opt.slice(0, 256) };
148
+ if (typeof opt === "string") return { number: String(idx + 1), text: opt };
125
149
  if (typeof opt === "number") return { number: String(idx + 1), text: String(opt) };
126
150
  if (opt && typeof opt === "object") {
127
151
  const optObj = opt;
128
152
  const num = typeof optObj.number !== "undefined" ? String(optObj.number).slice(0, 16) : String(idx + 1);
129
- const txt = typeof optObj.text === "string" ? optObj.text.slice(0, 256) : String(optObj.text ?? optObj.label ?? optObj.value ?? "").slice(0, 256);
153
+ const txt = typeof optObj.text === "string" ? optObj.text : String(optObj.text ?? optObj.label ?? optObj.value ?? "");
130
154
  return { number: num, text: txt };
131
155
  }
132
- return { number: String(idx + 1), text: String(opt ?? "").slice(0, 256) };
156
+ return { number: String(idx + 1), text: String(opt ?? "") };
133
157
  });
134
158
  const str2 = JSON.stringify(boundedOptions);
135
159
  if (str2.length <= maxChars) return str2;
@@ -139,38 +163,41 @@ function serializeStructuredArg(value, maxChars = MAX_STRUCTURED_ARG_CHARS, keyH
139
163
  }
140
164
  const firstOpt = boundedOptions[0];
141
165
  if (firstOpt) {
142
- const safeText = firstOpt.text.slice(0, Math.max(0, maxChars - 30));
143
- return JSON.stringify([{ number: firstOpt.number, text: safeText }]);
166
+ return sliceToSerializedBudget(
167
+ firstOpt.text,
168
+ (safeText) => JSON.stringify([{ number: firstOpt.number, text: safeText }]),
169
+ maxChars
170
+ );
144
171
  }
145
- return JSON.stringify(boundedOptions.slice(0, 1));
172
+ return "[]".length <= maxChars ? "[]" : null;
146
173
  }
147
174
  const bounded = parsed.slice(0, 20).map((item) => {
148
- if (typeof item === "string") return { question: item.slice(0, 1024), options: [] };
175
+ if (typeof item === "string") return { question: item, options: [] };
149
176
  if (item && typeof item === "object") {
150
177
  const res = {};
151
178
  const obj = item;
152
- if (typeof obj.question === "string") res.question = obj.question.slice(0, 1024);
179
+ if (typeof obj.question === "string") res.question = obj.question;
153
180
  if (typeof obj.is_multi_select !== "undefined") res.is_multi_select = decodeBoolean(obj.is_multi_select);
154
181
  if (typeof obj.isMultiSelect !== "undefined") res.isMultiSelect = decodeBoolean(obj.isMultiSelect);
155
182
  if (Array.isArray(obj.options)) {
156
- res.options = obj.options.slice(0, 25).map((opt, idx) => {
157
- if (typeof opt === "string") return opt.slice(0, 256);
183
+ res.options = obj.options.slice(0, 25).map((opt) => {
184
+ if (typeof opt === "string") return opt;
158
185
  if (typeof opt === "number") return String(opt);
159
186
  if (opt && typeof opt === "object") {
160
187
  const optObj = opt;
161
188
  return {
162
189
  ...typeof optObj.number !== "undefined" ? { number: String(optObj.number).slice(0, 16) } : {},
163
- text: typeof optObj.text === "string" ? optObj.text.slice(0, 256) : String(optObj.text ?? optObj.label ?? optObj.value ?? "").slice(0, 256)
190
+ text: typeof optObj.text === "string" ? optObj.text : String(optObj.text ?? optObj.label ?? optObj.value ?? "")
164
191
  };
165
192
  }
166
- return String(opt ?? "").slice(0, 256);
193
+ return String(opt ?? "");
167
194
  });
168
195
  }
169
196
  return res;
170
197
  }
171
- return { question: String(item).slice(0, 1024), options: [] };
198
+ return { question: String(item), options: [] };
172
199
  });
173
- let str = JSON.stringify(bounded);
200
+ const str = JSON.stringify(bounded);
174
201
  if (str.length <= maxChars) return str;
175
202
  for (let i = bounded.length - 1; i >= 1; i--) {
176
203
  const sub = JSON.stringify(bounded.slice(0, i));
@@ -178,11 +205,20 @@ function serializeStructuredArg(value, maxChars = MAX_STRUCTURED_ARG_CHARS, keyH
178
205
  }
179
206
  const firstQ = bounded[0];
180
207
  if (firstQ && typeof firstQ === "object") {
181
- const qText = typeof firstQ.question === "string" ? firstQ.question : "";
182
- const safeQText = qText.slice(0, Math.max(0, maxChars - 30));
183
- return JSON.stringify([{ question: safeQText, options: [] }]);
208
+ const qObj = firstQ;
209
+ const qText = typeof qObj.question === "string" ? qObj.question : "";
210
+ const opts = Array.isArray(qObj.options) ? qObj.options : [];
211
+ for (let k = opts.length - 1; k >= 1; k--) {
212
+ const subQ = JSON.stringify([{ ...qObj, options: opts.slice(0, k) }]);
213
+ if (subQ.length <= maxChars) return subQ;
214
+ }
215
+ return sliceToSerializedBudget(
216
+ qText,
217
+ (safeQText) => JSON.stringify([{ question: safeQText, options: [] }]),
218
+ maxChars
219
+ );
184
220
  }
185
- return JSON.stringify(bounded.slice(0, 1));
221
+ return "[]".length <= maxChars ? "[]" : null;
186
222
  }
187
223
  if (parsed && typeof parsed === "object") {
188
224
  const raw = JSON.stringify(parsed);
@@ -216,6 +252,8 @@ function buildBody(raw) {
216
252
  if (key === "questions" || key === "options") {
217
253
  const serialized = serializeStructuredArg(value, MAX_STRUCTURED_ARG_CHARS, key);
218
254
  if (serialized !== null) kept[key] = serialized;
255
+ } else if (key === "question" || key === "prompt") {
256
+ kept[key] = value.slice(0, MAX_STRUCTURED_ARG_CHARS);
219
257
  } else {
220
258
  kept[key] = value.slice(0, MAX_TOOL_ARG_CHARS);
221
259
  }
package/dist/server.js CHANGED
@@ -3556,8 +3556,9 @@ function looksLikePromptSnapshot(snapshot) {
3556
3556
  const allLines = stripped.trimEnd().split("\n");
3557
3557
  const fenceMask = computeFenceMask(allLines);
3558
3558
  const windowStart = Math.max(0, allLines.length - 30);
3559
+ const questionWindowStart = Math.max(0, allLines.length - 80);
3559
3560
  let headerIdx = -1;
3560
- for (let i = allLines.length - 1; i >= windowStart; i--) {
3561
+ for (let i = allLines.length - 1; i >= questionWindowStart; i--) {
3561
3562
  if (!fenceMask[i] && isQuestionHeaderLine(allLines[i])) {
3562
3563
  headerIdx = i;
3563
3564
  break;
@@ -3565,9 +3566,16 @@ function looksLikePromptSnapshot(snapshot) {
3565
3566
  }
3566
3567
  if (headerIdx < 0) return false;
3567
3568
  const headerLine = allLines[headerIdx].trim();
3569
+ const isTallHeader = headerIdx < windowStart;
3568
3570
  const optionIndices = [];
3569
3571
  for (let i = headerIdx + 1; i < allLines.length; i++) {
3570
- if (!fenceMask[i] && /^[>\s❯]*[1-9][.)]\s+\S/.test(allLines[i].trim())) {
3572
+ if (fenceMask[i]) continue;
3573
+ const trimmed = allLines[i].trim();
3574
+ if (!trimmed) continue;
3575
+ if ((isTallHeader || optionIndices.length > 0) && isResumedTurnSignal(trimmed) || isComposerBoxAt(allLines, i) || /^\s*(?:[❯>]\s*)?\?\s+for\s+(?:shortcuts|help|commands|options|keybindings|menu)\b/i.test(trimmed)) {
3576
+ return false;
3577
+ }
3578
+ if (/^(?:[>\s❯]{0,4})[1-9][.)]\s+\S/.test(allLines[i])) {
3571
3579
  optionIndices.push(i);
3572
3580
  }
3573
3581
  }
@@ -3591,7 +3599,7 @@ function looksLikePromptSnapshot(snapshot) {
3591
3599
  }
3592
3600
  }
3593
3601
  }
3594
- if (!hasQuestionFooter) return false;
3602
+ if (!hasQuestionFooter || footerIdx < windowStart) return false;
3595
3603
  if (hasTrailingAgyComposer(allLines, footerIdx, fenceMask)) {
3596
3604
  return false;
3597
3605
  }
@@ -4310,6 +4318,38 @@ function detectActivePromptKind(snapshot) {
4310
4318
  }
4311
4319
  if (lastApprovalLineIdx >= 0 && lastQuestionLineIdx >= 0 && lastFileAccessLineIdx >= 0 && lastSystemErrorLineIdx >= 0) break;
4312
4320
  }
4321
+ if (lastQuestionLineIdx < 0 && lastApprovalLineIdx < 0 && lastFileAccessLineIdx < 0) {
4322
+ const tallQuestionStart = Math.max(0, lines.length - 80);
4323
+ for (let i = windowStart - 1; i >= tallQuestionStart; i--) {
4324
+ if (lineInFence[i]) continue;
4325
+ if (isQuestionHeaderLine(lines[i])) {
4326
+ let optCount = 0;
4327
+ let footerInBottomWindow = false;
4328
+ let footerIdx = i;
4329
+ let interrupted = false;
4330
+ for (let j = i + 1; j < lines.length; j++) {
4331
+ if (lineInFence[j]) continue;
4332
+ const trimmed = lines[j].trim();
4333
+ if (!trimmed) continue;
4334
+ if (approvalRe.test(trimmed) || fileEditQuestionRe.test(trimmed) || fileAccessRe.test(trimmed) || fileAccessQuestionRe.test(trimmed) || /Do you want to proceed\?/i.test(trimmed) || isResumedTurnSignal(trimmed) || isComposerBoxAt(lines, j) || /^\s*(?:[❯>]\s*)?\?\s+for\s+(?:shortcuts|help|commands|options|keybindings|menu)\b/i.test(trimmed)) {
4335
+ interrupted = true;
4336
+ break;
4337
+ }
4338
+ if (/^(?:[>\s❯]{0,4})[1-9][.)]\s+\S/.test(lines[j])) {
4339
+ optCount += 1;
4340
+ } else if (isQuestionFooterLine(trimmed) && j >= windowStart) {
4341
+ footerInBottomWindow = true;
4342
+ footerIdx = j;
4343
+ break;
4344
+ }
4345
+ }
4346
+ if (!interrupted && optCount >= 2 && footerInBottomWindow && !hasTrailingAgyComposer(lines, footerIdx, lineInFence)) {
4347
+ lastQuestionLineIdx = i;
4348
+ }
4349
+ break;
4350
+ }
4351
+ }
4352
+ }
4313
4353
  if (lastApprovalLineIdx < 0 && lastQuestionLineIdx < 0) {
4314
4354
  for (let i = lines.length - 1; i >= windowStart; i--) {
4315
4355
  if (/Do you want to proceed\?/i.test(lines[i])) {
@@ -4335,7 +4375,7 @@ function detectActivePromptKind(snapshot) {
4335
4375
  for (let j = lastSystemErrorLineIdx + 1; j < lines.length; j++) {
4336
4376
  if (!lineInFence[j]) {
4337
4377
  const trimmed = lines[j].trim();
4338
- if (hasQuestionOptions && isResumedTurnSignal(trimmed)) {
4378
+ if ((hasQuestionOptions || lastQuestionLineIdx < windowStart) && isResumedTurnSignal(trimmed)) {
4339
4379
  break;
4340
4380
  }
4341
4381
  if (/^[>\s❯]*[1-9][.)]\s+\S/.test(trimmed)) {
@@ -4389,7 +4429,7 @@ function detectActivePromptKind(snapshot) {
4389
4429
  if (isAgyStatusFooterLine(trimmed) || /^\s*(?:[❯>]\s*)?\?\s+for\s+(?:shortcuts|help)\b/i.test(trimmed) || isComposerBoxAt(lines, j)) {
4390
4430
  break;
4391
4431
  }
4392
- if (hasFoundOptions && isResumedTurnSignal(trimmed)) {
4432
+ if ((hasFoundOptions || lastQuestionLineIdx < windowStart) && isResumedTurnSignal(trimmed)) {
4393
4433
  break;
4394
4434
  }
4395
4435
  if (/^[>\s❯]*[1-9][.)]\s+\S/.test(trimmed)) {
@@ -4882,7 +4922,8 @@ function buildApprovalSemanticKey(candidate) {
4882
4922
  body: isDiffBody ? rawBody.trim() : normalize(rawBody),
4883
4923
  options: (candidate.options ?? []).map((option) => ({
4884
4924
  number: option.number,
4885
- text: normalize(option.text)
4925
+ text: normalize(option.text),
4926
+ ...option.rawLines && option.rawLines.length > 1 ? { rawLines: option.rawLines.map((line) => normalize(line)) } : {}
4886
4927
  })),
4887
4928
  submitMap: Object.fromEntries(
4888
4929
  Object.entries(candidate.submitMap ?? {}).sort(([left], [right]) => Number(left) - Number(right)).map(([number, keys]) => [number, [...keys]])
@@ -5003,7 +5044,7 @@ function parseQuestionSnapshot(stripped, originalSnapshot, header) {
5003
5044
  }
5004
5045
  }
5005
5046
  const belowHeader = headerIdx >= 0 ? lines.slice(headerIdx + 1).join("\n") : stripped;
5006
- const options = parseOptions(belowHeader);
5047
+ const options = parseOptions(belowHeader, 80);
5007
5048
  if (options.length < 2) return null;
5008
5049
  const normalizedHeader = header.fullLine.replace(/^[❯>\s*•-]+\s*/, "").trim();
5009
5050
  const isMultiSelect = /\bselect\s+(?:all|multiple)\b/i.test(normalizedHeader) || /\bselect\s+(?:all|multiple)\b/i.test(belowHeader) || /(?:<space>|\bspace)\s+(?:to\s+)?(?:select|toggle)\b/i.test(belowHeader) || /\bctrl\+a\s+select\s+all\b/i.test(belowHeader);
@@ -5024,7 +5065,7 @@ function parseQuestionSnapshot(stripped, originalSnapshot, header) {
5024
5065
  }
5025
5066
  function extractBodyBetweenHeaderAndFirstOption(belowHeader) {
5026
5067
  const lines = belowHeader.split("\n");
5027
- const optionRegex = /^[>\s❯]*([1-9])[.)]\s+(.+?)\s*$/;
5068
+ const optionRegex = /^(?:[>\s❯]{0,4})([1-9])[.)]\s+(.+?)\s*$/;
5028
5069
  const lineInFence = computeFenceMask(lines);
5029
5070
  let firstOptionIdx = -1;
5030
5071
  for (let i = 0; i < lines.length; i++) {
@@ -5037,7 +5078,34 @@ function extractBodyBetweenHeaderAndFirstOption(belowHeader) {
5037
5078
  if (firstOptionIdx <= 0) return "";
5038
5079
  return lines.slice(0, firstOptionIdx).join("\n").trim();
5039
5080
  }
5040
- function parseOptions(snapshot) {
5081
+ function isOptionContinuationBoundary(trimmed, rawLine, nextTrimmedLine) {
5082
+ if (!trimmed) return true;
5083
+ if (/^[•‣◦▪▫]\s/.test(trimmed)) return true;
5084
+ if (/^\s{2,4}[-*]\s/.test(rawLine)) return true;
5085
+ if (isQuestionFooterLine(trimmed)) return true;
5086
+ if (isQuestionHeaderLine(trimmed)) return true;
5087
+ if (isResumedTurnSignal(trimmed)) return true;
5088
+ if (/^[─━═-]{4,}$/.test(trimmed)) return true;
5089
+ if (/^\s*esc to cancel\b/i.test(trimmed)) return true;
5090
+ if (/^(?:↑\/↓|↑|↓|\^|v)\s*(?:\/|\s+)?Navigate\b/i.test(trimmed)) return true;
5091
+ if (/^ctrl\+[a-z0-9]+\s+[a-zA-Z]+$/i.test(trimmed)) return true;
5092
+ if (/^\(?use\s+(?:arrow|\S+)\s+keys[^)]*\)?$/i.test(trimmed)) return true;
5093
+ if (!/^ {5,}/.test(rawLine) && /^Your answer:/i.test(trimmed) && (/^Your answer:\s*$/i.test(trimmed) || Boolean(
5094
+ nextTrimmedLine && (isQuestionFooterLine(nextTrimmedLine) || /^\(?Enter to submit(?:\s*,\s*esc to cancel)?\)?\s*$/i.test(nextTrimmedLine) || /^\s*esc to cancel\b/i.test(nextTrimmedLine))
5095
+ ))) {
5096
+ return true;
5097
+ }
5098
+ if (!/^ {5,}/.test(rawLine) && /^\(?Enter to submit(?:\s*,\s*esc to cancel)?\)?\s*$/i.test(trimmed)) {
5099
+ return true;
5100
+ }
5101
+ if (/^\s*(?:[❯>]\s*)?\?\s+for\s+(?:shortcuts|help|commands|options|keybindings|menu)\b/i.test(trimmed)) return true;
5102
+ if (/Requesting permission for:/i.test(trimmed)) return true;
5103
+ if (/Do you want to proceed\?/i.test(trimmed)) return true;
5104
+ if (/^\s*Allow access to this file\?\s*$/i.test(trimmed)) return true;
5105
+ if (/^\s*(?:Accept this file edit\?|Allow creation of this file\?)\s*$/i.test(trimmed)) return true;
5106
+ return false;
5107
+ }
5108
+ function parseOptions(snapshot, maxRecentLines = 30) {
5041
5109
  const lines = snapshot.split("\n");
5042
5110
  const lineInFence = computeFenceMask(lines);
5043
5111
  const filteredLines = [];
@@ -5049,16 +5117,72 @@ function parseOptions(snapshot) {
5049
5117
  while (filteredLines.length > 0 && filteredLines[filteredLines.length - 1].trim() === "") {
5050
5118
  filteredLines.pop();
5051
5119
  }
5052
- const recent = filteredLines.slice(-30);
5053
- const optionRegex = /^[>\s❯]*([1-9])[.)]\s+(.+?)\s*$/;
5120
+ const recent = filteredLines.slice(-Math.max(30, maxRecentLines));
5121
+ const optionRegex = /^(?:[>\s❯]{0,4})([1-9])[.)]\s+(.+?)\s*$/;
5054
5122
  const found = /* @__PURE__ */ new Map();
5055
- for (const line of recent) {
5123
+ let currentOptionNum = null;
5124
+ let currentOptionTextCol = 5;
5125
+ let prevOptionRawLen = 0;
5126
+ for (let idx = 0; idx < recent.length; idx++) {
5127
+ const line = recent[idx];
5056
5128
  const m = line.match(optionRegex);
5057
- if (!m) continue;
5058
- const num = m[1];
5059
- found.set(num, m[2].trim());
5129
+ if (m) {
5130
+ const num = m[1];
5131
+ const text = m[2].trim();
5132
+ const prefixMatch = line.match(/^(?:[>\s❯]{0,4})[1-9][.)]\s+/);
5133
+ currentOptionTextCol = prefixMatch ? prefixMatch[0].length : 5;
5134
+ prevOptionRawLen = line.trimEnd().length;
5135
+ found.set(num, {
5136
+ text,
5137
+ rawLines: [text],
5138
+ rawLineLengths: [prevOptionRawLen]
5139
+ });
5140
+ currentOptionNum = /^\s*write-?in(?:\s*(?:\.{3}|…))?\s*$/i.test(text) ? null : num;
5141
+ continue;
5142
+ }
5143
+ const trimmed = line.trim();
5144
+ if (currentOptionNum !== null) {
5145
+ let nextTrimmedLine;
5146
+ for (let k = idx + 1; k < recent.length; k++) {
5147
+ const nt = recent[k].trim();
5148
+ if (nt) {
5149
+ nextTrimmedLine = nt;
5150
+ break;
5151
+ }
5152
+ }
5153
+ const leadingSpaces = (line.match(/^\s*/)?.[0] ?? "").length;
5154
+ const isIndentedContinuation = leadingSpaces >= Math.max(4, currentOptionTextCol);
5155
+ const isColumnZeroWrapContinuation = leadingSpaces === 0;
5156
+ if (isOptionContinuationBoundary(trimmed, line, nextTrimmedLine) || !isIndentedContinuation && !isColumnZeroWrapContinuation) {
5157
+ currentOptionNum = null;
5158
+ } else {
5159
+ const entry = found.get(currentOptionNum);
5160
+ if (entry) {
5161
+ const existing = entry.text;
5162
+ entry.text = existing ? `${existing} ${trimmed}` : trimmed;
5163
+ entry.rawLines.push(trimmed);
5164
+ prevOptionRawLen = line.trimEnd().length;
5165
+ entry.rawLineLengths.push(prevOptionRawLen);
5166
+ }
5167
+ }
5168
+ }
5060
5169
  }
5061
- return Array.from(found.entries()).sort(([a], [b]) => Number(a) - Number(b)).map(([number, text]) => ({ number, text }));
5170
+ return Array.from(found.entries()).sort(([a], [b]) => Number(a) - Number(b)).map(([number, entry]) => {
5171
+ const opt = { number, text: entry.text };
5172
+ if (entry.rawLines.length > 1) {
5173
+ Object.defineProperty(opt, "rawLines", {
5174
+ value: entry.rawLines,
5175
+ enumerable: false,
5176
+ configurable: true
5177
+ });
5178
+ Object.defineProperty(opt, "rawLineLengths", {
5179
+ value: entry.rawLineLengths,
5180
+ enumerable: false,
5181
+ configurable: true
5182
+ });
5183
+ }
5184
+ return opt;
5185
+ });
5062
5186
  }
5063
5187
  function buildApprovalSubmitMap(options) {
5064
5188
  const map = {};
@@ -5079,10 +5203,11 @@ function isWriteInInputActive(paneText, expectedHeader, expectedStep, expectedBo
5079
5203
  if (detectActivePromptKind(paneText) !== "question") return false;
5080
5204
  const stripped = stripAnsiAndExpandRep(paneText);
5081
5205
  const lines = stripped.trimEnd().split("\n");
5082
- const windowStart = Math.max(0, lines.length - 30);
5206
+ const fenceMask = computeFenceMask(lines);
5207
+ const windowStart = Math.max(0, lines.length - 80);
5083
5208
  let headerIdx = -1;
5084
5209
  for (let i = lines.length - 1; i >= windowStart; i--) {
5085
- if (isQuestionHeaderLine(lines[i])) {
5210
+ if (!fenceMask[i] && isQuestionHeaderLine(lines[i])) {
5086
5211
  headerIdx = i;
5087
5212
  break;
5088
5213
  }
@@ -5987,10 +6112,13 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
5987
6112
  this.emit("prompt-upgraded", existing);
5988
6113
  return existing;
5989
6114
  }
6115
+ const hasPriorPaneObservation = Boolean(existing.paneSemanticKey);
5990
6116
  const areControlsMutated = (existingOpts, candidateOpts) => {
5991
6117
  if (!existingOpts || !candidateOpts) return false;
5992
6118
  if (existingOpts.length !== candidateOpts.length) return true;
5993
- return !existingOpts.every((o, idx) => o.number === candidateOpts[idx].number && (o.text || "").trim() === (candidateOpts[idx].text || "").trim());
6119
+ return !existingOpts.every(
6120
+ (o, idx) => o.number === candidateOpts[idx].number && (o.text || "").trim() === (candidateOpts[idx].text || "").trim() && (!hasPriorPaneObservation || JSON.stringify(o.rawLines ?? []) === JSON.stringify(candidateOpts[idx].rawLines ?? []))
6121
+ );
5994
6122
  };
5995
6123
  const isControlDelta = Boolean(
5996
6124
  existing.paneOptions && existing.paneOptions.length > 0 && candidate.options && candidate.options.length > 0 && areControlsMutated(existing.paneOptions, candidate.options)
@@ -6006,6 +6134,9 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
6006
6134
  } else {
6007
6135
  if (!existing.paneSemanticKey) {
6008
6136
  existing.paneSemanticKey = candidateSemanticKey;
6137
+ if (candidate.options && candidate.options.length > 0) {
6138
+ existing.paneOptions = candidate.options;
6139
+ }
6009
6140
  }
6010
6141
  if (stepLineMatch) {
6011
6142
  existing.matchedPaneHeader = stepLineMatch.stitchedHeaderText;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantiya/codevibe-antigravity-plugin",
3
- "version": "2.0.102",
3
+ "version": "2.0.103",
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": {