@quantiya/codevibe-antigravity-plugin 2.0.99 → 2.0.100

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 +117 -30
  2. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -4475,7 +4475,7 @@ function buildQuestionSubmitMap(options) {
4475
4475
  }
4476
4476
  return map;
4477
4477
  }
4478
- function isWriteInInputActive(paneText, expectedHeader, expectedStep) {
4478
+ function isWriteInInputActive(paneText, expectedHeader, expectedStep, expectedBodyText) {
4479
4479
  if (!paneText) return false;
4480
4480
  if (detectActivePromptKind(paneText) !== "question") return false;
4481
4481
  const stripped = paneText.replace(ANSI_ESCAPE_REGEX2, "");
@@ -4493,10 +4493,27 @@ function isWriteInInputActive(paneText, expectedHeader, expectedStep) {
4493
4493
  const extracted = extractQuestionHeader(rawHeader);
4494
4494
  if (!extracted) return false;
4495
4495
  if (expectedHeader) {
4496
+ const normHeaderBlock = (s) => s.replace(/^[❯>\s*•-]+\s*/, "").replace(/\s+/g, " ").trim().toLowerCase();
4496
4497
  const expectedExtracted = extractQuestionHeader(expectedHeader);
4497
- const expectedBody = (expectedExtracted ? expectedExtracted.body : expectedHeader).replace(/^[❯>\s*•-]+\s*/, "").trim().toLowerCase();
4498
- const paneBody = extracted.body.replace(/^[❯>\s*•-]+\s*/, "").trim().toLowerCase();
4499
- if (paneBody !== expectedBody) {
4498
+ const baseExpectedHeader = expectedExtracted ? expectedExtracted.body : expectedHeader;
4499
+ const expectedBodyLines = expectedBodyText ? expectedBodyText.split("\n").map((l) => l.trim()).filter(Boolean) : [];
4500
+ const allowedExpectedBodies = /* @__PURE__ */ new Set();
4501
+ for (let j = 0; j <= expectedBodyLines.length; j++) {
4502
+ allowedExpectedBodies.add(
4503
+ normHeaderBlock([baseExpectedHeader, ...expectedBodyLines.slice(0, j)].join(" "))
4504
+ );
4505
+ }
4506
+ const contLines = [];
4507
+ for (let j = headerIdx + 1; j < lines.length; j++) {
4508
+ const t = lines[j].trim();
4509
+ if (!t) continue;
4510
+ if (/^Your answer:\s*/i.test(t) || t.startsWith("(Enter to submit") || t.includes("Navigate") && t.includes("Select") || t.startsWith("\u2191/\u2193") || /^[❯>\s]*\d+\.\s+/.test(lines[j]) || /^[─━═-]{4,}$/.test(t)) {
4511
+ break;
4512
+ }
4513
+ contLines.push(t);
4514
+ }
4515
+ const paneBody = normHeaderBlock([extracted.body, ...contLines].join(" "));
4516
+ if (!allowedExpectedBodies.has(paneBody)) {
4500
4517
  return false;
4501
4518
  }
4502
4519
  }
@@ -5059,6 +5076,33 @@ function questionsMatch(hookText, paneText) {
5059
5076
  if (!h || !p) return false;
5060
5077
  return h === p;
5061
5078
  }
5079
+ function matchPaneQuestionLines(hookText, candidate) {
5080
+ const firstHeaderText = (candidate.headerText || candidate.fullHeaderLine || "").trim();
5081
+ const firstFullHeaderLine = (candidate.fullHeaderLine || candidate.headerText || "").trim();
5082
+ if (!hookText || !firstHeaderText) return null;
5083
+ const rawBodyLines = candidate.body ? candidate.body.split("\n") : [];
5084
+ for (let k = 0; k <= rawBodyLines.length; k++) {
5085
+ if (k > 0 && !rawBodyLines[k - 1].trim()) {
5086
+ break;
5087
+ }
5088
+ const consumedLines = rawBodyLines.slice(0, k).map((l) => l.trim()).filter(Boolean);
5089
+ const stitchedHeaderText = [firstHeaderText, ...consumedLines].join(" ").replace(/\s+/g, " ").trim();
5090
+ const stitchedFullHeaderLine = [firstFullHeaderLine, ...consumedLines].join(" ").replace(/\s+/g, " ").trim();
5091
+ if (questionsMatch(hookText, stitchedHeaderText) || questionsMatch(hookText, stitchedFullHeaderLine)) {
5092
+ const remainingBody = rawBodyLines.slice(k).join("\n").trim();
5093
+ return {
5094
+ matched: true,
5095
+ stitchedHeaderText,
5096
+ stitchedFullHeaderLine,
5097
+ remainingBody
5098
+ };
5099
+ }
5100
+ }
5101
+ return null;
5102
+ }
5103
+ function paneCandidateMatchesQuestion(hookText, candidate) {
5104
+ return matchPaneQuestionLines(hookText, candidate) !== null;
5105
+ }
5062
5106
  function correlatesQuestionInvocation(call, delivery) {
5063
5107
  if (!call) return false;
5064
5108
  const tn = (delivery.toolName ?? "").toLowerCase().trim();
@@ -5232,10 +5276,11 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
5232
5276
  const scoreStep = (stepIdx) => {
5233
5277
  if (stepIdx < 0 || stepIdx >= sqs.length) return { score: -1, textMatch: false };
5234
5278
  const targetQ = sqs[stepIdx];
5235
- const textMatch = questionsMatch(targetQ.question, paneQuestionText);
5279
+ const textMatch = paneCandidateMatchesQuestion(targetQ.question, candidate);
5236
5280
  if (!textMatch) return { score: -1, textMatch: false };
5237
- const qOptTexts = (targetQ.options || []).map((o) => (typeof o === "string" ? o : o.text).toLowerCase().trim());
5238
- const cOptTexts = (candidate.options || []).map((o) => (typeof o === "string" ? o : o.text).toLowerCase().trim());
5281
+ const normOpt = (s) => s.normalize("NFKC").toLowerCase().replace(/\s+/g, " ").trim();
5282
+ const qOptTexts = (targetQ.options || []).map((o) => normOpt(typeof o === "string" ? o : o.text));
5283
+ const cOptTexts = (candidate.options || []).map((o) => normOpt(typeof o === "string" ? o : o.text));
5239
5284
  if (cOptTexts.length > 0 && qOptTexts.length > 0) {
5240
5285
  const hasTrailingWriteIn = cOptTexts.length === qOptTexts.length + 1 && /^\s*write-?in\b/i.test(cOptTexts[cOptTexts.length - 1]);
5241
5286
  const cEffectiveTexts = hasTrailingWriteIn ? cOptTexts.slice(0, -1) : cOptTexts;
@@ -5279,6 +5324,7 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
5279
5324
  return null;
5280
5325
  }
5281
5326
  const targetStepQ = sqs[bestStepIdx];
5327
+ const stepLineMatch = matchPaneQuestionLines(targetStepQ?.question, candidate);
5282
5328
  if (targetStepQ?.isMultiSelect) {
5283
5329
  candidate.submitMap = {};
5284
5330
  }
@@ -5298,9 +5344,9 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
5298
5344
  }
5299
5345
  existing.paneSemanticKey = candidateSemanticKey;
5300
5346
  existing.paneHash = candidate.paneHash;
5301
- if (candidate.headerText) existing.matchedPaneHeader = candidate.headerText;
5302
- if (candidate.fullHeaderLine) existing.paneDisplayHeader = candidate.fullHeaderLine;
5303
- if (candidate.body) existing.body = candidate.body;
5347
+ existing.matchedPaneHeader = stepLineMatch?.stitchedHeaderText ?? candidate.headerText ?? existing.matchedPaneHeader;
5348
+ existing.paneDisplayHeader = stepLineMatch?.stitchedFullHeaderLine ?? candidate.fullHeaderLine ?? existing.paneDisplayHeader;
5349
+ existing.body = stepLineMatch ? stepLineMatch.remainingBody || void 0 : candidate.body;
5304
5350
  if (candidate.paneHash) this.paneOnlyEmittedHashes.add(candidate.paneHash);
5305
5351
  logger.info("Upgraded hook-driven question prompt to actionable from pane observer", {
5306
5352
  promptId: existing.promptId,
@@ -5329,7 +5375,16 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
5329
5375
  if (!existing.paneSemanticKey) {
5330
5376
  existing.paneSemanticKey = candidateSemanticKey;
5331
5377
  }
5332
- if (candidate.body) existing.body = candidate.body;
5378
+ if (stepLineMatch) {
5379
+ existing.matchedPaneHeader = stepLineMatch.stitchedHeaderText;
5380
+ existing.paneDisplayHeader = stepLineMatch.stitchedFullHeaderLine;
5381
+ existing.body = stepLineMatch.remainingBody || void 0;
5382
+ if (existing.pendingCall && targetStepQ?.question) {
5383
+ existing.pendingCall.questionText = stepLineMatch.stitchedHeaderText;
5384
+ }
5385
+ } else if (candidate.body) {
5386
+ existing.body = candidate.body;
5387
+ }
5333
5388
  if (candidate.paneHash) this.paneOnlyEmittedHashes.add(candidate.paneHash);
5334
5389
  return null;
5335
5390
  }
@@ -5352,7 +5407,8 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
5352
5407
  for (const p of [...this.pendingPrompts.values()]) {
5353
5408
  if (p.conversationId === conversationId && p.kind === "question") {
5354
5409
  const hookQuestionText = p.pendingCall?.questionText || p.matchedPaneHeader || p.paneDisplayHeader;
5355
- const isSameQuestion = questionsMatch(hookQuestionText, paneQuestionText);
5410
+ const lineMatch = matchPaneQuestionLines(hookQuestionText, candidate);
5411
+ const isSameQuestion = lineMatch !== null;
5356
5412
  if (isSameQuestion) {
5357
5413
  const targetQ = p.pendingCall?.structuredQuestions?.[p.wizardStepIndex ?? 0];
5358
5414
  const isAuthoritativeMultiSelect = Boolean(targetQ?.isMultiSelect);
@@ -5368,9 +5424,9 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
5368
5424
  }
5369
5425
  p.paneSemanticKey = candidateSemanticKey;
5370
5426
  p.paneHash = candidate.paneHash;
5371
- if (candidate.headerText) p.matchedPaneHeader = candidate.headerText;
5372
- if (candidate.fullHeaderLine) p.paneDisplayHeader = candidate.fullHeaderLine;
5373
- if (candidate.body) p.body = candidate.body;
5427
+ p.matchedPaneHeader = lineMatch?.stitchedHeaderText ?? candidate.headerText ?? p.matchedPaneHeader;
5428
+ p.paneDisplayHeader = lineMatch?.stitchedFullHeaderLine ?? candidate.fullHeaderLine ?? p.paneDisplayHeader;
5429
+ p.body = lineMatch ? lineMatch.remainingBody || void 0 : candidate.body;
5374
5430
  if (candidate.paneHash) this.paneOnlyEmittedHashes.add(candidate.paneHash);
5375
5431
  logger.info("Upgraded hook-driven question prompt to actionable from pane observer", {
5376
5432
  promptId: p.promptId,
@@ -5382,6 +5438,14 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
5382
5438
  if (!p.paneSemanticKey) {
5383
5439
  p.paneSemanticKey = candidateSemanticKey;
5384
5440
  }
5441
+ if (lineMatch) {
5442
+ p.matchedPaneHeader = lineMatch.stitchedHeaderText;
5443
+ p.paneDisplayHeader = lineMatch.stitchedFullHeaderLine;
5444
+ p.body = lineMatch.remainingBody || void 0;
5445
+ if (p.pendingCall) {
5446
+ p.pendingCall.questionText = lineMatch.stitchedHeaderText;
5447
+ }
5448
+ }
5385
5449
  if (candidate.paneHash) this.paneOnlyEmittedHashes.add(candidate.paneHash);
5386
5450
  return null;
5387
5451
  }
@@ -5400,6 +5464,8 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
5400
5464
  } else if (matchingCall) {
5401
5465
  matchingCall.matchedPromptId = promptId;
5402
5466
  }
5467
+ const matchedStepQuestionText = candidate.kind === "question" ? liveWizard?.call.structuredQuestions?.[liveWizard.activeStepIndex ?? 0]?.question ?? matchingCall?.structuredQuestions?.find((sq) => paneCandidateMatchesQuestion(sq.question, candidate))?.question ?? matchingCall?.questionText : void 0;
5468
+ const synthLineMatch = candidate.kind === "question" && matchedStepQuestionText ? matchPaneQuestionLines(matchedStepQuestionText, candidate) : null;
5403
5469
  const syntheticCall = {
5404
5470
  conversationId,
5405
5471
  intentStepIndex: matchingCall?.intentStepIndex ?? -1,
@@ -5413,7 +5479,7 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
5413
5479
  command: matchingCall?.command ?? candidate.command,
5414
5480
  filePath: matchingCall?.filePath ?? candidate.filePath,
5415
5481
  rawToolName: matchingCall?.rawToolName ?? (candidate.kind === "question" ? "ask_question" : candidate.command ?? candidate.filePath ?? "unknown"),
5416
- questionText: candidate.kind === "question" && candidate.headerText ? candidate.headerText : matchingCall?.questionText,
5482
+ questionText: synthLineMatch?.stitchedHeaderText ?? (candidate.kind === "question" && candidate.headerText ? candidate.headerText : matchingCall?.questionText),
5417
5483
  // NOTE (Codex R3-F1 / Claude R3-F2): Pane options are authoritative ground truth
5418
5484
  // that directly correspond to desktop keystrokes (submitMap). Transcript options
5419
5485
  // serve solely as fallback when pane options cannot be parsed.
@@ -5431,9 +5497,9 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
5431
5497
  conversationId,
5432
5498
  pendingCall: syntheticCall,
5433
5499
  submitMap: candidate.submitMap,
5434
- matchedPaneHeader: candidate.headerText,
5435
- paneDisplayHeader: candidate.fullHeaderLine,
5436
- body: candidate.body,
5500
+ matchedPaneHeader: synthLineMatch?.stitchedHeaderText ?? candidate.headerText,
5501
+ paneDisplayHeader: synthLineMatch?.stitchedFullHeaderLine ?? candidate.fullHeaderLine,
5502
+ body: synthLineMatch ? synthLineMatch.remainingBody || void 0 : candidate.body,
5437
5503
  paneOptions: candidate.options,
5438
5504
  paneSemanticKey: buildApprovalSemanticKey(candidate),
5439
5505
  kind: candidate.kind,
@@ -5471,8 +5537,19 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
5471
5537
  if (p.conversationId === call.conversationId && p.kind === "question") {
5472
5538
  const existingText = p.pendingCall?.questionText || p.matchedPaneHeader || p.paneDisplayHeader;
5473
5539
  const isSameInvocation = !p.pendingCall || p.pendingCall.intentStepIndex === -1 || call.intentStepIndex === -1 || p.pendingCall.intentStepIndex === call.intentStepIndex;
5474
- if (isSameInvocation && questionsMatch(existingText, questionText)) {
5540
+ const hookLineMatch = matchPaneQuestionLines(questionText, {
5541
+ headerText: p.matchedPaneHeader,
5542
+ fullHeaderLine: p.paneDisplayHeader,
5543
+ body: p.body
5544
+ });
5545
+ if (isSameInvocation && (questionsMatch(existingText, questionText) || hookLineMatch !== null)) {
5546
+ if (hookLineMatch) {
5547
+ p.matchedPaneHeader = hookLineMatch.stitchedHeaderText;
5548
+ p.paneDisplayHeader = hookLineMatch.stitchedFullHeaderLine;
5549
+ p.body = hookLineMatch.remainingBody || void 0;
5550
+ }
5475
5551
  if (p.pendingCall) {
5552
+ p.pendingCall.questionText = hookLineMatch?.stitchedHeaderText ?? call.questionText ?? p.pendingCall.questionText;
5476
5553
  p.pendingCall.structuredQuestions = call.structuredQuestions;
5477
5554
  if (call.intentStepIndex !== -1) {
5478
5555
  p.pendingCall.intentStepIndex = call.intentStepIndex;
@@ -5485,10 +5562,20 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
5485
5562
  const isHookMultiSelect = Boolean(
5486
5563
  call.isMultiSelect || call.structuredQuestions && call.structuredQuestions[0]?.isMultiSelect
5487
5564
  );
5488
- if (isHookMultiSelect && Object.keys(p.submitMap || {}).length > 0) {
5565
+ const normOpt = (s) => s.normalize("NFKC").toLowerCase().replace(/\s+/g, " ").trim();
5566
+ const hookRawOpts = call.structuredQuestions?.[0]?.options ?? call.questionOptions ?? [];
5567
+ const qOptTexts = hookRawOpts.map((o) => normOpt(typeof o === "string" ? o : o.text));
5568
+ const cOptTexts = (p.paneOptions || []).map((o) => normOpt(typeof o === "string" ? o : o.text));
5569
+ const hasTrailingWriteIn = cOptTexts.length === qOptTexts.length + 1 && /^\s*write-?in\b/i.test(cOptTexts[cOptTexts.length - 1]);
5570
+ const cEffectiveTexts = hasTrailingWriteIn ? cOptTexts.slice(0, -1) : cOptTexts;
5571
+ const hasStructuredQuestion = Boolean(call.structuredQuestions && call.structuredQuestions.length > 0);
5572
+ const hasHookOptionMismatch = cOptTexts.length > 0 && (hasStructuredQuestion && qOptTexts.length === 0 || qOptTexts.length > 0 && (cEffectiveTexts.length !== qOptTexts.length || qOptTexts.some((q) => !cEffectiveTexts.includes(q)) || cEffectiveTexts.some((c) => !qOptTexts.includes(c))));
5573
+ if ((isHookMultiSelect || hasHookOptionMismatch) && Object.keys(p.submitMap || {}).length > 0) {
5489
5574
  p.submitMap = {};
5490
- logger.info("emitHookQuestionPrompt: revoked actionable submitMap because incoming hook is multi-select", {
5491
- promptId: p.promptId
5575
+ logger.info("emitHookQuestionPrompt: revoked actionable submitMap due to multi-select or option mismatch", {
5576
+ promptId: p.promptId,
5577
+ isHookMultiSelect,
5578
+ hasHookOptionMismatch
5492
5579
  });
5493
5580
  this.emit("prompt-upgraded", p);
5494
5581
  }
@@ -5892,7 +5979,7 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
5892
5979
  if (candidate.kind === "question") {
5893
5980
  const questionCalls = Array.from(this.pendingCalls.values()).filter((c) => c.conversationId === conversationId).filter((c) => c.resultStepIndex === void 0).filter((c) => {
5894
5981
  if (c.structuredQuestions && candidate.headerText) {
5895
- const matchesStructured = c.structuredQuestions.some((sq) => questionsMatch(sq.question, candidate.headerText));
5982
+ const matchesStructured = c.structuredQuestions.some((sq) => paneCandidateMatchesQuestion(sq.question, candidate));
5896
5983
  if (matchesStructured) return true;
5897
5984
  }
5898
5985
  const associatedPromptId = c.matchedPromptId || c.promptId;
@@ -5904,9 +5991,9 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
5904
5991
  if (questionCalls.length > 0 && candidate.headerText) {
5905
5992
  const textMatch = questionCalls.find((c) => {
5906
5993
  if (!c.questionText) return false;
5907
- if (questionsMatch(c.questionText, candidate.headerText)) return true;
5994
+ if (paneCandidateMatchesQuestion(c.questionText, candidate)) return true;
5908
5995
  if (c.structuredQuestions) {
5909
- return c.structuredQuestions.some((sq) => questionsMatch(sq.question, candidate.headerText));
5996
+ return c.structuredQuestions.some((sq) => paneCandidateMatchesQuestion(sq.question, candidate));
5910
5997
  }
5911
5998
  return false;
5912
5999
  });
@@ -6139,7 +6226,7 @@ var PromptResponder = class {
6139
6226
  deliveryPhase
6140
6227
  };
6141
6228
  }
6142
- if (isQuestionPrompt && isWriteInInputActive(snapshot, state.matchedPaneHeader, expectedStep)) {
6229
+ if (isQuestionPrompt && (isWriteInInputActive(snapshot, state.matchedPaneHeader, expectedStep, state.body) || isWriteInInputActive(snapshot, void 0, expectedStep))) {
6143
6230
  return {
6144
6231
  ok: false,
6145
6232
  reason: "write-in-already-active",
@@ -6199,7 +6286,7 @@ var PromptResponder = class {
6199
6286
  await delay(100);
6200
6287
  try {
6201
6288
  const liveSnap = await this.paneObserver.captureSnapshot();
6202
- if (liveSnap && isWriteInInputActive(liveSnap, state.matchedPaneHeader, expectedStep)) {
6289
+ if (liveSnap && isWriteInInputActive(liveSnap, state.matchedPaneHeader, expectedStep, state.body)) {
6203
6290
  writeInReady = true;
6204
6291
  break;
6205
6292
  }
@@ -6237,7 +6324,7 @@ var PromptResponder = class {
6237
6324
  if (this.paneObserver) {
6238
6325
  try {
6239
6326
  const postHookSnap = await this.paneObserver.captureSnapshot();
6240
- if (!postHookSnap || !isWriteInInputActive(postHookSnap, state.matchedPaneHeader, expectedStep)) {
6327
+ if (!postHookSnap || !isWriteInInputActive(postHookSnap, state.matchedPaneHeader, expectedStep, state.body)) {
6241
6328
  return {
6242
6329
  ok: false,
6243
6330
  reason: "prompt-probe-failed",
@@ -6266,7 +6353,7 @@ var PromptResponder = class {
6266
6353
  if (this.paneObserver) {
6267
6354
  try {
6268
6355
  const preEnterSnap = await this.paneObserver.captureSnapshot();
6269
- if (!preEnterSnap || !isWriteInInputActive(preEnterSnap, state.matchedPaneHeader, expectedStep)) {
6356
+ if (!preEnterSnap || !isWriteInInputActive(preEnterSnap, state.matchedPaneHeader, expectedStep, state.body)) {
6270
6357
  return {
6271
6358
  ok: false,
6272
6359
  reason: "prompt-probe-failed",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantiya/codevibe-antigravity-plugin",
3
- "version": "2.0.99",
3
+ "version": "2.0.100",
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": {