@quantiya/codevibe-antigravity-plugin 2.0.85 → 2.0.86
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 +409 -90
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -32,6 +32,7 @@ var server_exports = {};
|
|
|
32
32
|
__export(server_exports, {
|
|
33
33
|
ApprovalDetector: () => ApprovalDetector,
|
|
34
34
|
McpServer: () => McpServer,
|
|
35
|
+
PromptResponder: () => PromptResponder,
|
|
35
36
|
SessionNotFoundError: () => SessionNotFoundError,
|
|
36
37
|
StoredAuthFailureError: () => StoredAuthFailureError,
|
|
37
38
|
TERMINAL_SHUTDOWN_SIGNALS: () => TERMINAL_SHUTDOWN_SIGNALS,
|
|
@@ -2924,6 +2925,7 @@ function isComposerBoxAt(lines, idx) {
|
|
|
2924
2925
|
}
|
|
2925
2926
|
return false;
|
|
2926
2927
|
}
|
|
2928
|
+
var ANSI_ESCAPE_REGEX = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
|
|
2927
2929
|
function isPaneInputSafetyBlocked(input) {
|
|
2928
2930
|
const rawText = Array.isArray(input) ? input.join("\n") : input;
|
|
2929
2931
|
if (!rawText) return { blocked: false, kind: null };
|
|
@@ -3093,9 +3095,10 @@ function isPaneInputSafetyBlocked(input) {
|
|
|
3093
3095
|
return { blocked: false, kind: null };
|
|
3094
3096
|
}
|
|
3095
3097
|
function looksLikePromptDelta(chunk) {
|
|
3098
|
+
const text = chunk.includes("\x1B") ? chunk.replace(ANSI_ESCAPE_REGEX, "") : chunk;
|
|
3096
3099
|
return (
|
|
3097
3100
|
// Approval UI signatures
|
|
3098
|
-
/Requesting permission for:/i.test(chunk) || /Do you want to proceed\?/i.test(chunk) || /\besc to cancel\b/i.test(
|
|
3101
|
+
/Requesting permission for:/i.test(chunk) || /Do you want to proceed\?/i.test(chunk) || /\besc to cancel\b/i.test(text) || /^\s*[1-9]\. (?:Yes|No)\b/im.test(text) || /\[(?:y\/n|Y\/n|y\/N)\]/.test(chunk) || /^\s*(?:[❯>]\s*)?\?\s+(?!for\s+(?:shortcuts|help|commands))\S/im.test(text) || /Question\b(?:\s+\d+(?:[\/:of\s]+\d+)?)?:/i.test(text) || /\benter\s+(?:to\s+)?(?:select|submit(?:\s+all)?|next)\b/i.test(text) || /\b(?:navigate|use\s+arrow\s+keys)\b/i.test(text) || /\b\d+[\/:of\s]+\d+:\s+\S/i.test(text) || /\bSubmit(?:\s+All)?\b/i.test(text) || /(?:^|[\s·|│])(?:←|<-)\s*Back\b/i.test(text) || /^File access\s*$/im.test(text) || /^\s*Allow access to this file\?\s*$/im.test(text) || /(?:Yes, allow access|No, deny access)/i.test(text) || /(?:↑\/↓\s+Navigate|ctrl\+r\s+Review)/i.test(text) || /Our servers are experiencing/i.test(text) || /Error ID:\s*[0-9a-fA-F-]*/i.test(text) || /(?:⚠️|⚠\uFE0F?)\s+.*(?:high[- ]traffic|try again in a minute|try again later|stream was interrupted|stream interrupted|overloaded|unexpected error|quota\b|upgrade your subscription|increase your limits|resets in\s+\d|credits balance is too low|out of credits|rate[- ]limit|capacity exhausted|exhausted your quota|internal error|currently unreachable|network issue connecting|authentication expired|authentication issue|terminated due to error|invalid output|invalid tool call)/i.test(text)
|
|
3099
3102
|
);
|
|
3100
3103
|
}
|
|
3101
3104
|
function isNonSystemErrorWarningLine(trimmed) {
|
|
@@ -4216,7 +4219,7 @@ function isQuestionFooterLine(line) {
|
|
|
4216
4219
|
}
|
|
4217
4220
|
if (/^[>\s❯]*[1-9][.)]\s+\S/.test(trimmed)) return false;
|
|
4218
4221
|
if (isQuestionHeaderLine(trimmed)) return false;
|
|
4219
|
-
return /^(?:esc|<esc>)\s+(?:to\s+)?(?:cancel|skip)(?:\s*[·│,]\s*(?:enter|<enter>)\s+(?:to\s+)?select)?(?:\s*[·│,]\s*ctrl\+[a-z0-9]+\s+[a-zA-Z]+)*$/i.test(trimmed) || /^(?:enter|<enter>)\s+(?:to\s+)?select(?:\s*[·│,]\s*(?:esc|<esc>)\s+(?:to\s+)?(?:cancel|skip))?(?:\s*[·│,]\s*ctrl\+[a-z0-9]+\s+[a-zA-Z]+)*$/i.test(trimmed) || /^(?:↑\/↓|↑|↓|\^|v)\s*(?:\/|\s+)?Navigate(?:\s*[·│,]\s*(?:enter|<enter>)?\s*(?:Select|to select))(?:\s*[·│,]\s*(?:esc|<esc>)?\s*(?:Skip|Cancel))?(?:\s*[·│,]\s*ctrl\+[a-z0-9]+\s+[a-zA-Z]+)*$/i.test(trimmed) || /^(?:↑\/↓|↑|↓|\^|v)\s*(?:\/|\s+)?Navigate(?:\s*[·│,]\s*(?:esc|<esc>)?\s*(?:Skip|Cancel))?$/i.test(trimmed) || /^press\s+<?(?:enter|return|space)>?\s+to\s+select$/i.test(trimmed) || /^space\s+to\s+select$/i.test(trimmed) || /^(?:\(?use\s+arrow\s+keys(?:\s+to\s+navigate)?\)?)$/i.test(trimmed) || /^(?:[\u2800-\u28FF]+(?:\s*waiting(?:\.\.\.)?)?|waiting(?:\.\.\.)?)$/i.test(trimmed);
|
|
4222
|
+
return /^(?:esc|<esc>)\s+(?:to\s+)?(?:cancel|skip)(?:\s*[·│,]\s*(?:enter|<enter>)\s+(?:to\s+)?(?:select|submit(?:\s+all)?))?(?:\s*[·│,]\s*ctrl\+[a-z0-9]+\s+[a-zA-Z]+)*$/i.test(trimmed) || /^(?:enter|<enter>)\s+(?:to\s+)?(?:select|submit(?:\s+all)?)(?:\s*[·│,]\s*(?:esc|<esc>)\s+(?:to\s+)?(?:cancel|skip|back))?(?:\s*[·│,]\s*ctrl\+[a-z0-9]+\s+[a-zA-Z]+)*$/i.test(trimmed) || /^(?:space|<space>)\s+(?:to\s+)?(?:select|toggle)(?:\s*[·│,]\s*(?:enter|<enter>)\s+(?:to\s+)?(?:select|submit(?:\s+all)?))?(?:\s*[·│,]\s*(?:esc|<esc>)\s+(?:to\s+)?(?:cancel|skip|back))?$/i.test(trimmed) || /^(?:↑\/↓|↑|↓|\^|v)\s*(?:\/|\s+)?Navigate(?:\s*[·│,]\s*(?:(?:→|->)\s*Next|(?:←|<-)\s*Back))*(?:\s*[·│,]\s*(?:enter|<enter>)?\s*(?:Select|Submit(?:\s+All)?|to select))(?:\s*[·│,]\s*(?:esc|<esc>)?\s*(?:Skip|Cancel|Back))?(?:\s*[·│,]\s*(?:(?:→|->)\s*Next|(?:←|<-)\s*Back))*(?:\s*[·│,]\s*ctrl\+[a-z0-9]+\s+[a-zA-Z]+)*$/i.test(trimmed) || /^(?:↑\/↓|↑|↓|\^|v)\s*(?:\/|\s+)?Navigate(?:\s*[·│,]\s*(?:esc|<esc>)?\s*(?:Skip|Cancel|Back))?$/i.test(trimmed) || /^(?:←|<-)\s*Back\s*[·│,]\s*(?:enter|<enter>)\s+(?:to\s+)?(?:select|submit(?:\s+all)?)(?:\s*[·│,]\s*(?:esc|<esc>)\s+(?:to\s+)?(?:cancel|skip))?$/i.test(trimmed) || /^press\s+<?(?:enter|return|space)>?\s+to\s+select$/i.test(trimmed) || /^space\s+to\s+select$/i.test(trimmed) || /^(?:\(?use\s+arrow\s+keys(?:\s+to\s+navigate)?\)?)$/i.test(trimmed) || /^(?:[\u2800-\u28FF]+(?:\s*waiting(?:\.\.\.)?)?|waiting(?:\.\.\.)?)$/i.test(trimmed);
|
|
4220
4223
|
}
|
|
4221
4224
|
function extractQuestionHeader(snapshot) {
|
|
4222
4225
|
const stripped = snapshot.replace(ANSI_ESCAPE_REGEX, "");
|
|
@@ -4242,7 +4245,6 @@ function hashPromptSnapshot(snapshot) {
|
|
|
4242
4245
|
}
|
|
4243
4246
|
return (0, import_crypto.createHash)("sha256").update(normalized).digest("hex");
|
|
4244
4247
|
}
|
|
4245
|
-
var ANSI_ESCAPE_REGEX = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
|
|
4246
4248
|
function escapeShellArg(value) {
|
|
4247
4249
|
return value.replace(/'/g, `'\\''`);
|
|
4248
4250
|
}
|
|
@@ -4387,33 +4389,8 @@ function parseQuestionSnapshot(stripped, originalSnapshot, header) {
|
|
|
4387
4389
|
const options = parseOptions(belowHeader);
|
|
4388
4390
|
if (options.length < 2) return null;
|
|
4389
4391
|
const normalizedHeader = header.fullLine.replace(/^[❯>\s*•-]+\s*/, "").trim();
|
|
4390
|
-
const
|
|
4391
|
-
|
|
4392
|
-
let footerHasCtrlRReview = false;
|
|
4393
|
-
const startLine = headerIdx >= 0 ? headerIdx + 1 : 0;
|
|
4394
|
-
let hasSeenOptions = false;
|
|
4395
|
-
for (let i = startLine; i < lines.length; i++) {
|
|
4396
|
-
if (lineInFence[i]) continue;
|
|
4397
|
-
const trimmed = lines[i].trim();
|
|
4398
|
-
if (!trimmed) continue;
|
|
4399
|
-
if (hasSeenOptions && isResumedTurnSignal(trimmed)) break;
|
|
4400
|
-
if (/^[>\s❯]*[1-9][.)]\s+\S/.test(trimmed)) {
|
|
4401
|
-
hasSeenOptions = true;
|
|
4402
|
-
}
|
|
4403
|
-
const isFooter = isQuestionFooterLine(trimmed) || /^(?:ctrl\+[a-z0-9]+\s+[a-zA-Z]+|\(?use\s+arrow\s+keys(?:\s+to\s+navigate)?\)?)$/i.test(trimmed);
|
|
4404
|
-
if (isFooter) {
|
|
4405
|
-
if (/(?:↑\/↓|↑|↓|\^|v)\s*(?:\/|\s+)?Navigate\b/i.test(trimmed) || /\(?use\s+arrow\s+keys(?:\s+to\s+navigate)?\)?/i.test(trimmed)) {
|
|
4406
|
-
footerHasArrowHint = true;
|
|
4407
|
-
}
|
|
4408
|
-
if (/ctrl\+r\s+Review\b/i.test(trimmed)) {
|
|
4409
|
-
footerHasCtrlRReview = true;
|
|
4410
|
-
}
|
|
4411
|
-
}
|
|
4412
|
-
}
|
|
4413
|
-
const hasArrowKeyHint = headerHasArrowHint || footerHasArrowHint;
|
|
4414
|
-
const hasWriteInOption = options.some((opt) => /^\s*write-?in\b/i.test(opt.text));
|
|
4415
|
-
const hasCtrlRReviewFooter = footerHasCtrlRReview;
|
|
4416
|
-
const isBaseNumberedWizard = /^Question\s+\d+\/\d+:/i.test(normalizedHeader) && !hasArrowKeyHint && !hasWriteInOption && !hasCtrlRReviewFooter;
|
|
4392
|
+
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);
|
|
4393
|
+
const isBaseNumberedWizard = /^Question\s+\d+\/\d+:/i.test(normalizedHeader) && !isMultiSelect;
|
|
4417
4394
|
const submitMap = isBaseNumberedWizard ? buildQuestionSubmitMap(options) : {};
|
|
4418
4395
|
const body = extractBodyBetweenHeaderAndFirstOption(belowHeader);
|
|
4419
4396
|
return {
|
|
@@ -4476,12 +4453,47 @@ function buildApprovalSubmitMap(options) {
|
|
|
4476
4453
|
function buildQuestionSubmitMap(options) {
|
|
4477
4454
|
const map = {};
|
|
4478
4455
|
for (const opt of options) {
|
|
4479
|
-
|
|
4480
|
-
map[opt.number] = [opt.number, "Enter"];
|
|
4481
|
-
}
|
|
4456
|
+
map[opt.number] = [opt.number];
|
|
4482
4457
|
}
|
|
4483
4458
|
return map;
|
|
4484
4459
|
}
|
|
4460
|
+
function isWriteInInputActive(paneText, expectedHeader, expectedStep) {
|
|
4461
|
+
if (!paneText) return false;
|
|
4462
|
+
if (detectActivePromptKind(paneText) !== "question") return false;
|
|
4463
|
+
const stripped = paneText.replace(ANSI_ESCAPE_REGEX2, "");
|
|
4464
|
+
const lines = stripped.trimEnd().split("\n");
|
|
4465
|
+
const windowStart = Math.max(0, lines.length - 30);
|
|
4466
|
+
let headerIdx = -1;
|
|
4467
|
+
for (let i = lines.length - 1; i >= windowStart; i--) {
|
|
4468
|
+
if (isQuestionHeaderLine(lines[i])) {
|
|
4469
|
+
headerIdx = i;
|
|
4470
|
+
break;
|
|
4471
|
+
}
|
|
4472
|
+
}
|
|
4473
|
+
if (headerIdx < 0) return false;
|
|
4474
|
+
const rawHeader = lines[headerIdx];
|
|
4475
|
+
const extracted = extractQuestionHeader(rawHeader);
|
|
4476
|
+
if (!extracted) return false;
|
|
4477
|
+
if (expectedHeader) {
|
|
4478
|
+
const expectedExtracted = extractQuestionHeader(expectedHeader);
|
|
4479
|
+
const expectedBody = (expectedExtracted ? expectedExtracted.body : expectedHeader).replace(/^[❯>\s*•-]+\s*/, "").trim().toLowerCase();
|
|
4480
|
+
const paneBody = extracted.body.replace(/^[❯>\s*•-]+\s*/, "").trim().toLowerCase();
|
|
4481
|
+
if (paneBody !== expectedBody) {
|
|
4482
|
+
return false;
|
|
4483
|
+
}
|
|
4484
|
+
}
|
|
4485
|
+
const expectedStepVal = expectedStep || (expectedHeader ? expectedHeader.match(/\bQuestion\s+(\d+(?:[\/:of\s]+\d+)?):/i)?.[1] : void 0);
|
|
4486
|
+
if (expectedStepVal) {
|
|
4487
|
+
const stepMatch = extracted.fullLine.match(/\bQuestion\s+(\d+(?:[\/:of\s]+\d+)?):/i);
|
|
4488
|
+
const paneStep = stepMatch ? stepMatch[1] : null;
|
|
4489
|
+
if (!paneStep || paneStep !== expectedStepVal) {
|
|
4490
|
+
return false;
|
|
4491
|
+
}
|
|
4492
|
+
}
|
|
4493
|
+
const searchLines = lines.slice(headerIdx);
|
|
4494
|
+
const text = searchLines.join("\n");
|
|
4495
|
+
return /Your answer:/i.test(text) && /enter\s+Submit(?:\s+All)?\s*[·│,]\s*esc\s+Back/i.test(text);
|
|
4496
|
+
}
|
|
4485
4497
|
function extractIdentity(headerText) {
|
|
4486
4498
|
if (!headerText) return {};
|
|
4487
4499
|
const trimmed = headerText.trim();
|
|
@@ -5207,13 +5219,15 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
|
|
|
5207
5219
|
const qOptTexts = (targetQ.options || []).map((o) => (typeof o === "string" ? o : o.text).toLowerCase().trim());
|
|
5208
5220
|
const cOptTexts = (candidate.options || []).map((o) => (typeof o === "string" ? o : o.text).toLowerCase().trim());
|
|
5209
5221
|
if (cOptTexts.length > 0 && qOptTexts.length > 0) {
|
|
5210
|
-
const
|
|
5211
|
-
const
|
|
5222
|
+
const hasTrailingWriteIn = cOptTexts.length === qOptTexts.length + 1 && /^\s*write-?in\b/i.test(cOptTexts[cOptTexts.length - 1]);
|
|
5223
|
+
const cEffectiveTexts = hasTrailingWriteIn ? cOptTexts.slice(0, -1) : cOptTexts;
|
|
5224
|
+
const matchedQCount = qOptTexts.filter((q) => cEffectiveTexts.includes(q)).length;
|
|
5225
|
+
const matchedCCount = cEffectiveTexts.filter((c) => qOptTexts.includes(c)).length;
|
|
5212
5226
|
const qRatio = matchedQCount / qOptTexts.length;
|
|
5213
|
-
if (
|
|
5227
|
+
if (cEffectiveTexts.length !== qOptTexts.length || matchedQCount !== qOptTexts.length || matchedCCount !== cEffectiveTexts.length) {
|
|
5214
5228
|
return { score: -1, textMatch: true };
|
|
5215
5229
|
}
|
|
5216
|
-
const posMatches = qOptTexts.filter((q, idx) => idx <
|
|
5230
|
+
const posMatches = qOptTexts.filter((q, idx) => idx < cEffectiveTexts.length && cEffectiveTexts[idx] === q).length;
|
|
5217
5231
|
const posRatio = posMatches / qOptTexts.length;
|
|
5218
5232
|
const score = qRatio * 0.5 + posRatio * 0.5;
|
|
5219
5233
|
return { score, textMatch: true };
|
|
@@ -5246,13 +5260,19 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
|
|
|
5246
5260
|
});
|
|
5247
5261
|
return null;
|
|
5248
5262
|
}
|
|
5263
|
+
const targetStepQ = sqs[bestStepIdx];
|
|
5264
|
+
if (targetStepQ?.isMultiSelect) {
|
|
5265
|
+
candidate.submitMap = {};
|
|
5266
|
+
}
|
|
5249
5267
|
const isAdvancement = bestStepIdx !== currentActiveIdx;
|
|
5250
5268
|
if (!isAdvancement) {
|
|
5251
5269
|
const existing = [...this.pendingPrompts.values()].find(
|
|
5252
5270
|
(p) => p.conversationId === conversationId && p.kind === "question"
|
|
5253
5271
|
);
|
|
5254
5272
|
if (existing) {
|
|
5255
|
-
const
|
|
5273
|
+
const targetQ = existing.pendingCall?.structuredQuestions?.[existing.wizardStepIndex ?? 0];
|
|
5274
|
+
const isAuthoritativeMultiSelect = Boolean(targetQ?.isMultiSelect);
|
|
5275
|
+
const isInitialUpgrade = existing.isHookPrompt && !isAuthoritativeMultiSelect && Object.keys(existing.submitMap || {}).length === 0 && Object.keys(candidate.submitMap || {}).length > 0;
|
|
5256
5276
|
if (isInitialUpgrade) {
|
|
5257
5277
|
existing.submitMap = candidate.submitMap;
|
|
5258
5278
|
if (candidate.options && candidate.options.length > 0) {
|
|
@@ -5271,12 +5291,30 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
|
|
|
5271
5291
|
this.emit("prompt-upgraded", existing);
|
|
5272
5292
|
return existing;
|
|
5273
5293
|
}
|
|
5274
|
-
|
|
5275
|
-
|
|
5294
|
+
const areControlsMutated = (existingOpts, candidateOpts) => {
|
|
5295
|
+
if (!existingOpts || !candidateOpts) return false;
|
|
5296
|
+
if (existingOpts.length !== candidateOpts.length) return true;
|
|
5297
|
+
return !existingOpts.every((o, idx) => o.number === candidateOpts[idx].number && (o.text || "").trim() === (candidateOpts[idx].text || "").trim());
|
|
5298
|
+
};
|
|
5299
|
+
const isControlDelta = Boolean(
|
|
5300
|
+
existing.paneOptions && existing.paneOptions.length > 0 && candidate.options && candidate.options.length > 0 && areControlsMutated(existing.paneOptions, candidate.options)
|
|
5301
|
+
);
|
|
5302
|
+
if (isControlDelta) {
|
|
5303
|
+
logger.info("Control delta observed on active question step; resolving old prompt and re-emitting", {
|
|
5304
|
+
oldPromptId: existing.promptId,
|
|
5305
|
+
oldSemanticKey: existing.paneSemanticKey,
|
|
5306
|
+
newSemanticKey: candidateSemanticKey
|
|
5307
|
+
});
|
|
5308
|
+
this.resolvePrompt(existing.promptId, { advanceWizard: false });
|
|
5309
|
+
this.emit("prompt-resolved", existing.promptId);
|
|
5310
|
+
} else {
|
|
5311
|
+
if (!existing.paneSemanticKey) {
|
|
5312
|
+
existing.paneSemanticKey = candidateSemanticKey;
|
|
5313
|
+
}
|
|
5314
|
+
if (candidate.body) existing.body = candidate.body;
|
|
5315
|
+
if (candidate.paneHash) this.paneOnlyEmittedHashes.add(candidate.paneHash);
|
|
5316
|
+
return null;
|
|
5276
5317
|
}
|
|
5277
|
-
if (candidate.body) existing.body = candidate.body;
|
|
5278
|
-
if (candidate.paneHash) this.paneOnlyEmittedHashes.add(candidate.paneHash);
|
|
5279
|
-
return null;
|
|
5280
5318
|
}
|
|
5281
5319
|
} else {
|
|
5282
5320
|
liveWizard2.activeStepIndex = bestStepIdx;
|
|
@@ -5298,12 +5336,14 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
|
|
|
5298
5336
|
const hookQuestionText = p.pendingCall?.questionText || p.matchedPaneHeader || p.paneDisplayHeader;
|
|
5299
5337
|
const isSameQuestion = questionsMatch(hookQuestionText, paneQuestionText);
|
|
5300
5338
|
if (isSameQuestion) {
|
|
5301
|
-
const
|
|
5339
|
+
const targetQ = p.pendingCall?.structuredQuestions?.[p.wizardStepIndex ?? 0];
|
|
5340
|
+
const isAuthoritativeMultiSelect = Boolean(targetQ?.isMultiSelect);
|
|
5341
|
+
const isInitialUpgrade = p.isHookPrompt && !isAuthoritativeMultiSelect && Object.keys(p.submitMap || {}).length === 0 && Object.keys(candidate.submitMap || {}).length > 0;
|
|
5302
5342
|
const isControlDelta = !isInitialUpgrade && Boolean(
|
|
5303
5343
|
p.paneSemanticKey && candidateSemanticKey !== p.paneSemanticKey
|
|
5304
5344
|
);
|
|
5305
5345
|
if (!isControlDelta) {
|
|
5306
|
-
if (
|
|
5346
|
+
if (isInitialUpgrade) {
|
|
5307
5347
|
p.submitMap = candidate.submitMap;
|
|
5308
5348
|
if (candidate.options && candidate.options.length > 0) {
|
|
5309
5349
|
p.paneOptions = candidate.options;
|
|
@@ -5424,6 +5464,16 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
|
|
|
5424
5464
|
} else {
|
|
5425
5465
|
p.pendingCall = call;
|
|
5426
5466
|
}
|
|
5467
|
+
const isHookMultiSelect = Boolean(
|
|
5468
|
+
call.isMultiSelect || call.structuredQuestions && call.structuredQuestions[0]?.isMultiSelect
|
|
5469
|
+
);
|
|
5470
|
+
if (isHookMultiSelect && Object.keys(p.submitMap || {}).length > 0) {
|
|
5471
|
+
p.submitMap = {};
|
|
5472
|
+
logger.info("emitHookQuestionPrompt: revoked actionable submitMap because incoming hook is multi-select", {
|
|
5473
|
+
promptId: p.promptId
|
|
5474
|
+
});
|
|
5475
|
+
this.emit("prompt-upgraded", p);
|
|
5476
|
+
}
|
|
5427
5477
|
p.isHookPrompt = true;
|
|
5428
5478
|
call.matchedPromptId = p.promptId;
|
|
5429
5479
|
if (call.structuredQuestions && call.structuredQuestions.length > 0) {
|
|
@@ -5961,23 +6011,22 @@ var PromptResponder = class {
|
|
|
5961
6011
|
}
|
|
5962
6012
|
/**
|
|
5963
6013
|
* Resolve a pending approval prompt with the mobile-user's chosen
|
|
5964
|
-
* option number
|
|
5965
|
-
*
|
|
5966
|
-
*
|
|
6014
|
+
* option number and optional follow-up text for write-in questions.
|
|
6015
|
+
* Uses the submitMap from the PendingPromptState to translate
|
|
6016
|
+
* option-number → terminal keystrokes.
|
|
5967
6017
|
*
|
|
5968
6018
|
* Active-prompt-identity guard (DESIGN.md §3.4):
|
|
5969
6019
|
* 1. Detector check: is `promptId` still in pendingPrompts? If not
|
|
5970
6020
|
* (expired or already-resolved), drop with `prompt-expired`.
|
|
5971
|
-
* 2.
|
|
5972
|
-
*
|
|
5973
|
-
*
|
|
5974
|
-
*
|
|
5975
|
-
*
|
|
5976
|
-
*
|
|
5977
|
-
*
|
|
5978
|
-
* 4. Send the keystrokes via tmux send-keys.
|
|
6021
|
+
* 2. submitMap lookup: is `optionNumber` a valid key? If not, drop with `invalid-option`.
|
|
6022
|
+
* If `followUpText` is provided, verify option is Write-in; otherwise drop with `follow-up-not-allowed`.
|
|
6023
|
+
* If bare Write-in option has no text, drop with `write-in-text-required`.
|
|
6024
|
+
* 3. Pane-observer check: captureSnapshot() — does the pane STILL show
|
|
6025
|
+
* an approval UI matching our pending prompt's semantic key?
|
|
6026
|
+
* 4. Send option keystrokes via tmux send-keys.
|
|
6027
|
+
* 5. If write-in followUpText is present: wait 250ms, verify pane state, type literal text, send Enter.
|
|
5979
6028
|
*/
|
|
5980
|
-
async sendApprovalReply(promptId, optionNumber) {
|
|
6029
|
+
async sendApprovalReply(promptId, optionNumber, followUpText, beforeTypeText) {
|
|
5981
6030
|
const target = this.resolveTmuxTarget();
|
|
5982
6031
|
if (!target) {
|
|
5983
6032
|
return { ok: false, reason: "no-tmux-target" };
|
|
@@ -5992,15 +6041,6 @@ var PromptResponder = class {
|
|
|
5992
6041
|
details: `promptId ${promptId} is not in pendingPrompts`
|
|
5993
6042
|
};
|
|
5994
6043
|
}
|
|
5995
|
-
if (this.paneObserver) {
|
|
5996
|
-
if (!state.paneSemanticKey) {
|
|
5997
|
-
return {
|
|
5998
|
-
ok: false,
|
|
5999
|
-
reason: "prompt-probe-failed",
|
|
6000
|
-
details: "pending prompt has no exact semantic identity"
|
|
6001
|
-
};
|
|
6002
|
-
}
|
|
6003
|
-
}
|
|
6004
6044
|
const keys = state.submitMap[optionNumber];
|
|
6005
6045
|
if (!keys || keys.length === 0) {
|
|
6006
6046
|
return {
|
|
@@ -6009,21 +6049,86 @@ var PromptResponder = class {
|
|
|
6009
6049
|
details: `option '${optionNumber}' not in submitMap (valid: ${Object.keys(state.submitMap).join(",")})`
|
|
6010
6050
|
};
|
|
6011
6051
|
}
|
|
6052
|
+
const isQuestionPrompt = state.kind === "question";
|
|
6053
|
+
const expectedStep = (state.paneDisplayHeader || "").match(/\bQuestion\s+(\d+(?:[\/:of\s]+\d+)?):/i)?.[1];
|
|
6054
|
+
const matchingOpt = state.paneOptions?.find((o) => String(o.number) === String(optionNumber));
|
|
6055
|
+
const isWriteInOption = matchingOpt ? /^\s*write-?in\b/i.test(matchingOpt.text) : false;
|
|
6056
|
+
if (isQuestionPrompt) {
|
|
6057
|
+
if (state.paneOptions && state.paneOptions.length > 0 && !matchingOpt) {
|
|
6058
|
+
return {
|
|
6059
|
+
ok: false,
|
|
6060
|
+
reason: "invalid-option",
|
|
6061
|
+
details: `option '${optionNumber}' not found in advertised options`,
|
|
6062
|
+
deliveryPhase: "none"
|
|
6063
|
+
};
|
|
6064
|
+
}
|
|
6065
|
+
if (isWriteInOption && (!followUpText || followUpText.trim().length === 0)) {
|
|
6066
|
+
return {
|
|
6067
|
+
ok: false,
|
|
6068
|
+
reason: "write-in-text-required",
|
|
6069
|
+
details: 'Please provide your write-in text along with the option number (e.g. "4, your response")',
|
|
6070
|
+
deliveryPhase: "none"
|
|
6071
|
+
};
|
|
6072
|
+
}
|
|
6073
|
+
if (followUpText && followUpText.trim().length > 0 && !isWriteInOption) {
|
|
6074
|
+
return {
|
|
6075
|
+
ok: false,
|
|
6076
|
+
reason: "follow-up-not-allowed",
|
|
6077
|
+
details: `Option '${optionNumber}' does not accept follow-up text`,
|
|
6078
|
+
deliveryPhase: "none"
|
|
6079
|
+
};
|
|
6080
|
+
}
|
|
6081
|
+
}
|
|
6082
|
+
if (this.paneObserver) {
|
|
6083
|
+
if (!state.paneSemanticKey) {
|
|
6084
|
+
return {
|
|
6085
|
+
ok: false,
|
|
6086
|
+
reason: "prompt-probe-failed",
|
|
6087
|
+
details: "pending prompt has no exact semantic identity",
|
|
6088
|
+
deliveryPhase: "none"
|
|
6089
|
+
};
|
|
6090
|
+
}
|
|
6091
|
+
}
|
|
6092
|
+
let deliveryPhase = "none";
|
|
6012
6093
|
try {
|
|
6013
6094
|
for (const key of keys) {
|
|
6014
6095
|
if (!this.detector || !this.paneObserver) {
|
|
6015
6096
|
return {
|
|
6016
6097
|
ok: false,
|
|
6017
6098
|
reason: "prompt-probe-failed",
|
|
6018
|
-
details: "approval responder is missing its detector or pane observer"
|
|
6099
|
+
details: "approval responder is missing its detector or pane observer",
|
|
6100
|
+
deliveryPhase
|
|
6019
6101
|
};
|
|
6020
6102
|
}
|
|
6021
|
-
|
|
6022
|
-
|
|
6103
|
+
const currentPrompt = this.detector.getPendingPrompt(promptId);
|
|
6104
|
+
if (currentPrompt !== state || !currentPrompt.submitMap || !currentPrompt.submitMap[optionNumber]) {
|
|
6105
|
+
return {
|
|
6106
|
+
ok: false,
|
|
6107
|
+
reason: "prompt-expired",
|
|
6108
|
+
details: "option is no longer actionable on active prompt (controls revoked or updated)",
|
|
6109
|
+
deliveryPhase
|
|
6110
|
+
};
|
|
6023
6111
|
}
|
|
6024
6112
|
let liveSemanticKey = null;
|
|
6025
6113
|
try {
|
|
6026
6114
|
const snapshot = await this.paneObserver.captureSnapshot();
|
|
6115
|
+
const postCapturePrompt = this.detector.getPendingPrompt(promptId);
|
|
6116
|
+
if (postCapturePrompt !== state || !postCapturePrompt.submitMap || !postCapturePrompt.submitMap[optionNumber]) {
|
|
6117
|
+
return {
|
|
6118
|
+
ok: false,
|
|
6119
|
+
reason: "prompt-expired",
|
|
6120
|
+
details: "option is no longer actionable on active prompt (controls revoked or updated)",
|
|
6121
|
+
deliveryPhase
|
|
6122
|
+
};
|
|
6123
|
+
}
|
|
6124
|
+
if (isQuestionPrompt && isWriteInInputActive(snapshot, state.matchedPaneHeader, expectedStep)) {
|
|
6125
|
+
return {
|
|
6126
|
+
ok: false,
|
|
6127
|
+
reason: "write-in-already-active",
|
|
6128
|
+
details: "write-in input field is already open on desktop; finish or cancel on desktop before selecting an option",
|
|
6129
|
+
deliveryPhase: "none"
|
|
6130
|
+
};
|
|
6131
|
+
}
|
|
6027
6132
|
const live = parseApprovalSnapshot(snapshot);
|
|
6028
6133
|
if (!live) {
|
|
6029
6134
|
if (detectActivePromptKind(snapshot) === null) {
|
|
@@ -6031,13 +6136,15 @@ var PromptResponder = class {
|
|
|
6031
6136
|
return {
|
|
6032
6137
|
ok: false,
|
|
6033
6138
|
reason: "prompt-superseded",
|
|
6034
|
-
details: "the approval chooser is no longer active"
|
|
6139
|
+
details: "the approval chooser is no longer active",
|
|
6140
|
+
deliveryPhase
|
|
6035
6141
|
};
|
|
6036
6142
|
}
|
|
6037
6143
|
return {
|
|
6038
6144
|
ok: false,
|
|
6039
6145
|
reason: "prompt-probe-failed",
|
|
6040
|
-
details: "the active chooser could not be parsed exactly"
|
|
6146
|
+
details: "the active chooser could not be parsed exactly",
|
|
6147
|
+
deliveryPhase
|
|
6041
6148
|
};
|
|
6042
6149
|
}
|
|
6043
6150
|
liveSemanticKey = live ? buildApprovalSemanticKey(live) : null;
|
|
@@ -6045,7 +6152,8 @@ var PromptResponder = class {
|
|
|
6045
6152
|
return {
|
|
6046
6153
|
ok: false,
|
|
6047
6154
|
reason: "prompt-probe-failed",
|
|
6048
|
-
details: error instanceof Error ? error.message : String(error)
|
|
6155
|
+
details: error instanceof Error ? error.message : String(error),
|
|
6156
|
+
deliveryPhase
|
|
6049
6157
|
};
|
|
6050
6158
|
}
|
|
6051
6159
|
if (liveSemanticKey !== state.paneSemanticKey) {
|
|
@@ -6053,32 +6161,147 @@ var PromptResponder = class {
|
|
|
6053
6161
|
return {
|
|
6054
6162
|
ok: false,
|
|
6055
6163
|
reason: "prompt-superseded",
|
|
6056
|
-
details: "active chooser semantics differ from the mobile prompt"
|
|
6164
|
+
details: "active chooser semantics differ from the mobile prompt",
|
|
6165
|
+
deliveryPhase
|
|
6057
6166
|
};
|
|
6058
6167
|
}
|
|
6168
|
+
deliveryPhase = "option_attempted";
|
|
6059
6169
|
if (isNamedKey(key)) {
|
|
6060
6170
|
await this.sendKey(target, key);
|
|
6061
6171
|
} else {
|
|
6062
6172
|
await this.typeLiteral(target, key);
|
|
6063
6173
|
}
|
|
6174
|
+
deliveryPhase = "option_sent";
|
|
6175
|
+
await delay(50);
|
|
6176
|
+
}
|
|
6177
|
+
if (isQuestionPrompt && isWriteInOption && followUpText && followUpText.trim().length > 0) {
|
|
6178
|
+
let writeInReady = false;
|
|
6179
|
+
if (this.paneObserver) {
|
|
6180
|
+
for (let probeAttempt = 0; probeAttempt < 10; probeAttempt++) {
|
|
6181
|
+
await delay(100);
|
|
6182
|
+
try {
|
|
6183
|
+
const liveSnap = await this.paneObserver.captureSnapshot();
|
|
6184
|
+
if (liveSnap && isWriteInInputActive(liveSnap, state.matchedPaneHeader, expectedStep)) {
|
|
6185
|
+
writeInReady = true;
|
|
6186
|
+
break;
|
|
6187
|
+
}
|
|
6188
|
+
} catch {
|
|
6189
|
+
}
|
|
6190
|
+
}
|
|
6191
|
+
if (!writeInReady) {
|
|
6192
|
+
return {
|
|
6193
|
+
ok: false,
|
|
6194
|
+
reason: "prompt-probe-failed",
|
|
6195
|
+
details: "terminal pane failed to transition to write-in input line",
|
|
6196
|
+
deliveryPhase
|
|
6197
|
+
};
|
|
6198
|
+
}
|
|
6199
|
+
} else {
|
|
6200
|
+
await delay(250);
|
|
6201
|
+
}
|
|
6202
|
+
if (beforeTypeText) {
|
|
6203
|
+
try {
|
|
6204
|
+
await beforeTypeText();
|
|
6205
|
+
} catch (err) {
|
|
6206
|
+
return {
|
|
6207
|
+
ok: false,
|
|
6208
|
+
reason: "tmux-failed",
|
|
6209
|
+
details: err instanceof Error ? err.message : String(err),
|
|
6210
|
+
deliveryPhase: "option_sent"
|
|
6211
|
+
// Do not advance phase if hook throws (Claude R4-N2)
|
|
6212
|
+
};
|
|
6213
|
+
}
|
|
6214
|
+
}
|
|
6215
|
+
const postHookPrompt = this.detector.getPendingPrompt(promptId);
|
|
6216
|
+
if (postHookPrompt !== state || !postHookPrompt.submitMap || !postHookPrompt.submitMap[optionNumber]) {
|
|
6217
|
+
return { ok: false, reason: "prompt-expired", deliveryPhase: "option_sent" };
|
|
6218
|
+
}
|
|
6219
|
+
if (this.paneObserver) {
|
|
6220
|
+
try {
|
|
6221
|
+
const postHookSnap = await this.paneObserver.captureSnapshot();
|
|
6222
|
+
if (!postHookSnap || !isWriteInInputActive(postHookSnap, state.matchedPaneHeader, expectedStep)) {
|
|
6223
|
+
return {
|
|
6224
|
+
ok: false,
|
|
6225
|
+
reason: "prompt-probe-failed",
|
|
6226
|
+
details: "write-in input field disappeared during callback before typing",
|
|
6227
|
+
deliveryPhase: "option_sent"
|
|
6228
|
+
};
|
|
6229
|
+
}
|
|
6230
|
+
} catch (err) {
|
|
6231
|
+
return {
|
|
6232
|
+
ok: false,
|
|
6233
|
+
reason: "prompt-probe-failed",
|
|
6234
|
+
details: err instanceof Error ? err.message : String(err),
|
|
6235
|
+
deliveryPhase: "option_sent"
|
|
6236
|
+
};
|
|
6237
|
+
}
|
|
6238
|
+
const postSnapPrompt1 = this.detector.getPendingPrompt(promptId);
|
|
6239
|
+
if (postSnapPrompt1 !== state || !postSnapPrompt1.submitMap || !postSnapPrompt1.submitMap[optionNumber]) {
|
|
6240
|
+
return { ok: false, reason: "prompt-expired", deliveryPhase: "option_sent" };
|
|
6241
|
+
}
|
|
6242
|
+
}
|
|
6243
|
+
deliveryPhase = "text_attempted";
|
|
6244
|
+
const cleanText = followUpText.replace(/[\r\n]+/g, " ").trim();
|
|
6245
|
+
await this.typeLiteral(target, cleanText);
|
|
6246
|
+
deliveryPhase = "text_typed";
|
|
6247
|
+
await delay(50);
|
|
6248
|
+
if (this.paneObserver) {
|
|
6249
|
+
try {
|
|
6250
|
+
const preEnterSnap = await this.paneObserver.captureSnapshot();
|
|
6251
|
+
if (!preEnterSnap || !isWriteInInputActive(preEnterSnap, state.matchedPaneHeader, expectedStep)) {
|
|
6252
|
+
return {
|
|
6253
|
+
ok: false,
|
|
6254
|
+
reason: "prompt-probe-failed",
|
|
6255
|
+
details: "write-in input field disappeared before confirmation",
|
|
6256
|
+
deliveryPhase: "text_typed"
|
|
6257
|
+
};
|
|
6258
|
+
}
|
|
6259
|
+
} catch (err) {
|
|
6260
|
+
return {
|
|
6261
|
+
ok: false,
|
|
6262
|
+
reason: "prompt-probe-failed",
|
|
6263
|
+
details: err instanceof Error ? err.message : String(err),
|
|
6264
|
+
deliveryPhase: "text_typed"
|
|
6265
|
+
};
|
|
6266
|
+
}
|
|
6267
|
+
}
|
|
6268
|
+
const postSnapPrompt = this.detector.getPendingPrompt(promptId);
|
|
6269
|
+
if (postSnapPrompt !== state || !postSnapPrompt.submitMap || !postSnapPrompt.submitMap[optionNumber]) {
|
|
6270
|
+
return { ok: false, reason: "prompt-expired", deliveryPhase: "text_typed" };
|
|
6271
|
+
}
|
|
6272
|
+
deliveryPhase = "enter_attempted";
|
|
6273
|
+
await this.sendKey(target, "Enter");
|
|
6274
|
+
deliveryPhase = "completed";
|
|
6064
6275
|
}
|
|
6065
6276
|
this.detector.resolvePrompt(promptId);
|
|
6066
6277
|
logger.info("Sent approval reply via tmux", {
|
|
6067
6278
|
promptId,
|
|
6068
6279
|
optionNumber,
|
|
6069
|
-
keysSent: keys
|
|
6280
|
+
keysSent: keys,
|
|
6281
|
+
hasFollowUpText: Boolean(followUpText),
|
|
6282
|
+
deliveryPhase
|
|
6070
6283
|
});
|
|
6071
|
-
return { ok: true };
|
|
6284
|
+
return { ok: true, deliveryPhase: "completed" };
|
|
6072
6285
|
} catch (error) {
|
|
6073
6286
|
return {
|
|
6074
6287
|
ok: false,
|
|
6075
6288
|
reason: "tmux-failed",
|
|
6076
|
-
details: error instanceof Error ? error.message : String(error)
|
|
6289
|
+
details: error instanceof Error ? error.message : String(error),
|
|
6290
|
+
deliveryPhase
|
|
6077
6291
|
};
|
|
6078
6292
|
}
|
|
6079
6293
|
}
|
|
6080
6294
|
try {
|
|
6081
6295
|
await this.typeLiteral(target, optionNumber);
|
|
6296
|
+
await delay(50);
|
|
6297
|
+
await this.sendKey(target, "Enter");
|
|
6298
|
+
if (followUpText && followUpText.trim().length > 0) {
|
|
6299
|
+
await delay(250);
|
|
6300
|
+
const cleanText = followUpText.replace(/[\r\n]+/g, " ").trim();
|
|
6301
|
+
await this.typeLiteral(target, cleanText);
|
|
6302
|
+
await delay(50);
|
|
6303
|
+
await this.sendKey(target, "Enter");
|
|
6304
|
+
}
|
|
6082
6305
|
return { ok: true };
|
|
6083
6306
|
} catch (error) {
|
|
6084
6307
|
return {
|
|
@@ -9086,9 +9309,7 @@ var McpServer = class _McpServer {
|
|
|
9086
9309
|
let e1rec;
|
|
9087
9310
|
if (e1existing) {
|
|
9088
9311
|
e1rec = e1existing.record;
|
|
9089
|
-
|
|
9090
|
-
e1rec.mobileActionable = true;
|
|
9091
|
-
}
|
|
9312
|
+
e1rec.mobileActionable = !isDisplayOnly;
|
|
9092
9313
|
} else {
|
|
9093
9314
|
e1rec = this.newE1Raise(session.sessionId, !isDisplayOnly);
|
|
9094
9315
|
const oldPromptId = state.promptId;
|
|
@@ -9372,22 +9593,119 @@ var McpServer = class _McpServer {
|
|
|
9372
9593
|
}
|
|
9373
9594
|
}
|
|
9374
9595
|
await this.trackDurableMobileEcho(session, evt, intent.option, "primary");
|
|
9375
|
-
const
|
|
9376
|
-
|
|
9377
|
-
|
|
9378
|
-
|
|
9379
|
-
|
|
9380
|
-
|
|
9596
|
+
const isQuestionPrompt = activePrompt.kind === "question";
|
|
9597
|
+
if (isQuestionPrompt) {
|
|
9598
|
+
let armedWriteInEcho = false;
|
|
9599
|
+
const optionResult = intent.followUpText ? await this.promptResponder.sendApprovalReply(
|
|
9600
|
+
activePrompt.promptId,
|
|
9601
|
+
intent.option,
|
|
9602
|
+
intent.followUpText,
|
|
9603
|
+
async () => {
|
|
9604
|
+
armedWriteInEcho = true;
|
|
9605
|
+
await this.trackDurableMobileEcho(session, evt, intent.followUpText, "free_form");
|
|
9606
|
+
}
|
|
9607
|
+
) : await this.promptResponder.sendApprovalReply(
|
|
9608
|
+
activePrompt.promptId,
|
|
9609
|
+
intent.option
|
|
9610
|
+
);
|
|
9611
|
+
if (!optionResult.ok) {
|
|
9612
|
+
if (optionResult.reason === "write-in-text-required") {
|
|
9613
|
+
await this.emitPromptSafetyNotification(
|
|
9614
|
+
session,
|
|
9615
|
+
optionResult.details || 'Please provide your write-in text along with the option number (e.g. "4, your response").'
|
|
9616
|
+
);
|
|
9617
|
+
} else if (optionResult.reason === "write-in-already-active") {
|
|
9618
|
+
await this.emitPromptSafetyNotification(
|
|
9619
|
+
session,
|
|
9620
|
+
optionResult.details || "Write-in input field is already open on desktop; finish or cancel on desktop before selecting an option."
|
|
9621
|
+
);
|
|
9622
|
+
} else if (optionResult.reason === "follow-up-not-allowed") {
|
|
9623
|
+
await this.emitPromptSafetyNotification(
|
|
9624
|
+
session,
|
|
9625
|
+
optionResult.details || `Option ${intent.option} does not accept follow-up text.`
|
|
9626
|
+
);
|
|
9627
|
+
} else {
|
|
9628
|
+
const phase = optionResult.deliveryPhase ?? "none";
|
|
9629
|
+
let message;
|
|
9630
|
+
if (phase !== "none") {
|
|
9631
|
+
message = "Your answer was partially entered on desktop. Please complete or cancel the prompt directly on desktop.";
|
|
9632
|
+
} else if (optionResult.reason === "prompt-expired" || optionResult.reason === "prompt-superseded") {
|
|
9633
|
+
message = "The question prompt on desktop has expired or changed. Please review on desktop.";
|
|
9634
|
+
} else if (optionResult.reason === "prompt-probe-failed") {
|
|
9635
|
+
message = "Write-in input field was not ready on desktop. Please try again or respond on desktop.";
|
|
9636
|
+
} else if (optionResult.reason === "no-tmux-target" || optionResult.reason === "tmux-failed") {
|
|
9637
|
+
message = optionResult.details || "Desktop terminal session is unreachable. Please respond directly on desktop.";
|
|
9638
|
+
} else if (optionResult.reason === "display-only-question-active") {
|
|
9639
|
+
message = "This question requires desktop input. Please respond directly on desktop.";
|
|
9640
|
+
} else if (optionResult.reason === "invalid-option") {
|
|
9641
|
+
message = optionResult.details || `Option '${intent.option}' is not valid for the current question.`;
|
|
9642
|
+
} else {
|
|
9643
|
+
message = optionResult.details || `Failed to submit response to question prompt (${optionResult.reason}).`;
|
|
9644
|
+
}
|
|
9645
|
+
await this.emitPromptSafetyNotification(session, message);
|
|
9646
|
+
}
|
|
9647
|
+
if (!(0, import_codevibe_core7.requiresSyncInputDeliveryReceipt)(evt)) {
|
|
9648
|
+
const phase = optionResult.deliveryPhase ?? "none";
|
|
9649
|
+
if (phase === "none") {
|
|
9650
|
+
this.mobileDeduper.forget(session.sessionId, intent.option);
|
|
9651
|
+
if (armedWriteInEcho && intent.followUpText) {
|
|
9652
|
+
this.mobileDeduper.forget(session.sessionId, intent.followUpText);
|
|
9653
|
+
}
|
|
9654
|
+
} else if (phase === "option_sent" && armedWriteInEcho && intent.followUpText) {
|
|
9655
|
+
this.mobileDeduper.forget(session.sessionId, intent.followUpText);
|
|
9656
|
+
}
|
|
9657
|
+
}
|
|
9658
|
+
return;
|
|
9659
|
+
}
|
|
9660
|
+
await this.resolveE1PluginApproval(session.sessionId, activePrompt.promptId);
|
|
9661
|
+
await this.markExecuted(evt);
|
|
9381
9662
|
return;
|
|
9382
|
-
}
|
|
9383
|
-
|
|
9384
|
-
|
|
9663
|
+
} else {
|
|
9664
|
+
const optionResult = await this.promptResponder.sendApprovalReply(
|
|
9665
|
+
activePrompt.promptId,
|
|
9666
|
+
intent.option
|
|
9667
|
+
);
|
|
9668
|
+
if (!optionResult.ok) {
|
|
9669
|
+
if (!(0, import_codevibe_core7.requiresSyncInputDeliveryReceipt)(evt)) {
|
|
9670
|
+
const phase = optionResult.deliveryPhase ?? "none";
|
|
9671
|
+
if (phase === "none") {
|
|
9672
|
+
this.mobileDeduper.forget(session.sessionId, intent.option);
|
|
9673
|
+
}
|
|
9674
|
+
}
|
|
9675
|
+
return;
|
|
9676
|
+
}
|
|
9677
|
+
await this.resolveE1PluginApproval(session.sessionId, activePrompt.promptId);
|
|
9678
|
+
if (intent.followUpText) {
|
|
9679
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
9680
|
+
const freeFormResult = await this.promptResponder.sendFreeFormWhenNoApprovalActive(
|
|
9681
|
+
intent.followUpText,
|
|
9682
|
+
() => this.trackDurableMobileEcho(session, evt, intent.followUpText, "free_form")
|
|
9683
|
+
);
|
|
9684
|
+
if (!freeFormResult.ok) {
|
|
9685
|
+
if (!(0, import_codevibe_core7.requiresSyncInputDeliveryReceipt)(evt)) {
|
|
9686
|
+
this.mobileDeduper.forget(session.sessionId, intent.followUpText);
|
|
9687
|
+
}
|
|
9688
|
+
logger.warn("Follow-up text send failed for approval", { reason: freeFormResult.reason });
|
|
9689
|
+
return;
|
|
9690
|
+
}
|
|
9691
|
+
}
|
|
9385
9692
|
await this.markExecuted(evt);
|
|
9386
9693
|
return;
|
|
9387
9694
|
}
|
|
9388
|
-
promptContent = intent.followUpText;
|
|
9389
|
-
await new Promise((resolve2) => setTimeout(resolve2, 250));
|
|
9390
9695
|
} else {
|
|
9696
|
+
const activeQuestionPrompt = pendingPrompts.find((p) => p.kind === "question");
|
|
9697
|
+
if (activeQuestionPrompt) {
|
|
9698
|
+
const isDisplayOnly = Object.keys(activeQuestionPrompt.submitMap || {}).length === 0;
|
|
9699
|
+
logger.warn("Non-option input received while an interactive question prompt is active; rejecting dismissal", {
|
|
9700
|
+
sessionId: session.sessionId,
|
|
9701
|
+
promptId: activeQuestionPrompt.promptId,
|
|
9702
|
+
isDisplayOnly,
|
|
9703
|
+
content: promptContent.slice(0, 64)
|
|
9704
|
+
});
|
|
9705
|
+
const message = isDisplayOnly ? "The active terminal question prompt is display-only on mobile. Please select an option directly in your terminal." : "An interactive question prompt is active in your terminal. Please select one of the advertised options or answer directly in your terminal.";
|
|
9706
|
+
await this.emitPromptSafetyNotification(session, message);
|
|
9707
|
+
return;
|
|
9708
|
+
}
|
|
9391
9709
|
const hasDisplayOnlyPrompt = pendingPrompts.some(
|
|
9392
9710
|
(p) => Object.keys(p.submitMap || {}).length === 0
|
|
9393
9711
|
);
|
|
@@ -10057,6 +10375,7 @@ var __testing = {
|
|
|
10057
10375
|
0 && (module.exports = {
|
|
10058
10376
|
ApprovalDetector,
|
|
10059
10377
|
McpServer,
|
|
10378
|
+
PromptResponder,
|
|
10060
10379
|
SessionNotFoundError,
|
|
10061
10380
|
StoredAuthFailureError,
|
|
10062
10381
|
TERMINAL_SHUTDOWN_SIGNALS,
|
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.86",
|
|
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": {
|