@quantiya/codevibe-antigravity-plugin 2.0.102 → 2.0.104
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 +75 -19
- package/dist/server.js +598 -129
- package/package.json +1 -1
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();
|
|
@@ -120,57 +144,72 @@ function serializeStructuredArg(value, maxChars = MAX_STRUCTURED_ARG_CHARS, keyH
|
|
|
120
144
|
(item) => typeof item === "string" || typeof item === "number" || item && typeof item === "object" && !("question" in item)
|
|
121
145
|
);
|
|
122
146
|
if (isOptionArray) {
|
|
147
|
+
const wasSlicedAtCap = parsed.length > 30;
|
|
123
148
|
const boundedOptions = parsed.slice(0, 30).map((opt, idx) => {
|
|
124
|
-
if (typeof opt === "string") return { number: String(idx + 1), text: opt
|
|
149
|
+
if (typeof opt === "string") return { number: String(idx + 1), text: opt };
|
|
125
150
|
if (typeof opt === "number") return { number: String(idx + 1), text: String(opt) };
|
|
126
151
|
if (opt && typeof opt === "object") {
|
|
127
152
|
const optObj = opt;
|
|
128
153
|
const num = typeof optObj.number !== "undefined" ? String(optObj.number).slice(0, 16) : String(idx + 1);
|
|
129
|
-
const txt = typeof optObj.text === "string" ? optObj.text
|
|
154
|
+
const txt = typeof optObj.text === "string" ? optObj.text : String(optObj.text ?? optObj.label ?? optObj.value ?? "");
|
|
130
155
|
return { number: num, text: txt };
|
|
131
156
|
}
|
|
132
|
-
return { number: String(idx + 1), text: String(opt ?? "")
|
|
157
|
+
return { number: String(idx + 1), text: String(opt ?? "") };
|
|
133
158
|
});
|
|
159
|
+
if (wasSlicedAtCap && boundedOptions.length > 0) {
|
|
160
|
+
boundedOptions[0].options_truncated = true;
|
|
161
|
+
}
|
|
134
162
|
const str2 = JSON.stringify(boundedOptions);
|
|
135
163
|
if (str2.length <= maxChars) return str2;
|
|
136
164
|
for (let i = boundedOptions.length - 1; i >= 1; i--) {
|
|
137
|
-
const
|
|
165
|
+
const prefix = boundedOptions.slice(0, i).map(
|
|
166
|
+
(o, idx) => idx === 0 ? { ...o, options_truncated: true } : o
|
|
167
|
+
);
|
|
168
|
+
const sub = JSON.stringify(prefix);
|
|
138
169
|
if (sub.length <= maxChars) return sub;
|
|
139
170
|
}
|
|
140
171
|
const firstOpt = boundedOptions[0];
|
|
141
172
|
if (firstOpt) {
|
|
142
|
-
|
|
143
|
-
|
|
173
|
+
return sliceToSerializedBudget(
|
|
174
|
+
firstOpt.text,
|
|
175
|
+
(safeText) => JSON.stringify([{ number: firstOpt.number, text: safeText, options_truncated: true }]),
|
|
176
|
+
maxChars
|
|
177
|
+
);
|
|
144
178
|
}
|
|
145
|
-
return
|
|
179
|
+
return "[]".length <= maxChars ? "[]" : null;
|
|
146
180
|
}
|
|
147
181
|
const bounded = parsed.slice(0, 20).map((item) => {
|
|
148
|
-
if (typeof item === "string") return { question: item
|
|
182
|
+
if (typeof item === "string") return { question: item, options: [] };
|
|
149
183
|
if (item && typeof item === "object") {
|
|
150
184
|
const res = {};
|
|
151
185
|
const obj = item;
|
|
152
|
-
if (typeof obj.question === "string") res.question = obj.question
|
|
186
|
+
if (typeof obj.question === "string") res.question = obj.question;
|
|
153
187
|
if (typeof obj.is_multi_select !== "undefined") res.is_multi_select = decodeBoolean(obj.is_multi_select);
|
|
154
188
|
if (typeof obj.isMultiSelect !== "undefined") res.isMultiSelect = decodeBoolean(obj.isMultiSelect);
|
|
189
|
+
if (typeof obj.options_truncated !== "undefined") res.options_truncated = decodeBoolean(obj.options_truncated);
|
|
190
|
+
if (typeof obj.optionsTruncated !== "undefined") res.optionsTruncated = decodeBoolean(obj.optionsTruncated);
|
|
155
191
|
if (Array.isArray(obj.options)) {
|
|
156
|
-
|
|
157
|
-
|
|
192
|
+
if (obj.options.length > 25) {
|
|
193
|
+
res.options_truncated = true;
|
|
194
|
+
}
|
|
195
|
+
res.options = obj.options.slice(0, 25).map((opt) => {
|
|
196
|
+
if (typeof opt === "string") return opt;
|
|
158
197
|
if (typeof opt === "number") return String(opt);
|
|
159
198
|
if (opt && typeof opt === "object") {
|
|
160
199
|
const optObj = opt;
|
|
161
200
|
return {
|
|
162
201
|
...typeof optObj.number !== "undefined" ? { number: String(optObj.number).slice(0, 16) } : {},
|
|
163
|
-
text: typeof optObj.text === "string" ? optObj.text
|
|
202
|
+
text: typeof optObj.text === "string" ? optObj.text : String(optObj.text ?? optObj.label ?? optObj.value ?? "")
|
|
164
203
|
};
|
|
165
204
|
}
|
|
166
|
-
return String(opt ?? "")
|
|
205
|
+
return String(opt ?? "");
|
|
167
206
|
});
|
|
168
207
|
}
|
|
169
208
|
return res;
|
|
170
209
|
}
|
|
171
|
-
return { question: String(item)
|
|
210
|
+
return { question: String(item), options: [] };
|
|
172
211
|
});
|
|
173
|
-
|
|
212
|
+
const str = JSON.stringify(bounded);
|
|
174
213
|
if (str.length <= maxChars) return str;
|
|
175
214
|
for (let i = bounded.length - 1; i >= 1; i--) {
|
|
176
215
|
const sub = JSON.stringify(bounded.slice(0, i));
|
|
@@ -178,11 +217,26 @@ function serializeStructuredArg(value, maxChars = MAX_STRUCTURED_ARG_CHARS, keyH
|
|
|
178
217
|
}
|
|
179
218
|
const firstQ = bounded[0];
|
|
180
219
|
if (firstQ && typeof firstQ === "object") {
|
|
181
|
-
const
|
|
182
|
-
const
|
|
183
|
-
|
|
220
|
+
const qObj = firstQ;
|
|
221
|
+
const qText = typeof qObj.question === "string" ? qObj.question : "";
|
|
222
|
+
const opts = Array.isArray(qObj.options) ? qObj.options : [];
|
|
223
|
+
for (let k = opts.length - 1; k >= 1; k--) {
|
|
224
|
+
const subQ = JSON.stringify([{ ...qObj, options: opts.slice(0, k), options_truncated: true }]);
|
|
225
|
+
if (subQ.length <= maxChars) return subQ;
|
|
226
|
+
}
|
|
227
|
+
return sliceToSerializedBudget(
|
|
228
|
+
qText,
|
|
229
|
+
(safeQText) => JSON.stringify([
|
|
230
|
+
{
|
|
231
|
+
question: safeQText,
|
|
232
|
+
options: [],
|
|
233
|
+
...opts.length > 0 ? { options_truncated: true } : {}
|
|
234
|
+
}
|
|
235
|
+
]),
|
|
236
|
+
maxChars
|
|
237
|
+
);
|
|
184
238
|
}
|
|
185
|
-
return
|
|
239
|
+
return "[]".length <= maxChars ? "[]" : null;
|
|
186
240
|
}
|
|
187
241
|
if (parsed && typeof parsed === "object") {
|
|
188
242
|
const raw = JSON.stringify(parsed);
|
|
@@ -216,6 +270,8 @@ function buildBody(raw) {
|
|
|
216
270
|
if (key === "questions" || key === "options") {
|
|
217
271
|
const serialized = serializeStructuredArg(value, MAX_STRUCTURED_ARG_CHARS, key);
|
|
218
272
|
if (serialized !== null) kept[key] = serialized;
|
|
273
|
+
} else if (key === "question" || key === "prompt") {
|
|
274
|
+
kept[key] = value.slice(0, MAX_STRUCTURED_ARG_CHARS);
|
|
219
275
|
} else {
|
|
220
276
|
kept[key] = value.slice(0, MAX_TOOL_ARG_CHARS);
|
|
221
277
|
}
|
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 >=
|
|
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 (
|
|
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❯]
|
|
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
|
|
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❯]
|
|
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
|
-
|
|
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 (
|
|
5058
|
-
|
|
5059
|
-
|
|
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,
|
|
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
|
|
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
|
}
|
|
@@ -5586,28 +5711,61 @@ function extractAllQuestionsFromArgs(toolName, args) {
|
|
|
5586
5711
|
const qObj = qItem;
|
|
5587
5712
|
const qText = typeof qObj.question === "string" ? stripWrappingQuotes2(qObj.question.trim()) : "Question";
|
|
5588
5713
|
const opts = [];
|
|
5714
|
+
let optItemTruncated = false;
|
|
5589
5715
|
if (Array.isArray(qObj.options)) {
|
|
5590
5716
|
for (let i = 0; i < qObj.options.length; i++) {
|
|
5591
5717
|
const opt = qObj.options[i];
|
|
5592
5718
|
if (typeof opt === "string" && opt.trim().length > 0) {
|
|
5593
5719
|
opts.push({ number: String(i + 1), text: stripWrappingQuotes2(opt.trim()) });
|
|
5594
5720
|
} else if (opt && typeof opt === "object" && typeof opt.text === "string") {
|
|
5721
|
+
const optRecord = opt;
|
|
5722
|
+
if (decodeBoolean(optRecord.options_truncated ?? optRecord.optionsTruncated)) {
|
|
5723
|
+
optItemTruncated = true;
|
|
5724
|
+
}
|
|
5595
5725
|
opts.push({
|
|
5596
|
-
number: String(
|
|
5597
|
-
text: stripWrappingQuotes2(String(
|
|
5726
|
+
number: String(optRecord.number ?? i + 1),
|
|
5727
|
+
text: stripWrappingQuotes2(String(optRecord.text).trim())
|
|
5598
5728
|
});
|
|
5599
5729
|
}
|
|
5600
5730
|
}
|
|
5601
5731
|
}
|
|
5602
5732
|
const isMulti = decodeBoolean(qObj.is_multi_select ?? qObj.isMultiSelect);
|
|
5603
|
-
|
|
5733
|
+
const isTruncated = optItemTruncated || decodeBoolean(qObj.options_truncated ?? qObj.optionsTruncated);
|
|
5734
|
+
items.push({
|
|
5735
|
+
question: qText,
|
|
5736
|
+
options: opts,
|
|
5737
|
+
isMultiSelect: isMulti,
|
|
5738
|
+
...isTruncated ? { optionsTruncated: true } : {}
|
|
5739
|
+
});
|
|
5604
5740
|
}
|
|
5605
5741
|
}
|
|
5606
5742
|
} else if (typeof a.question === "string" || typeof a.prompt === "string") {
|
|
5607
5743
|
const qText = stripWrappingQuotes2(String(a.question ?? a.prompt).trim());
|
|
5608
5744
|
const opts = extractQuestionOptionsFromArgs(toolName, args) ?? [];
|
|
5609
5745
|
const isMulti = decodeBoolean(a.is_multi_select ?? a.isMultiSelect);
|
|
5610
|
-
|
|
5746
|
+
let topOptTruncated = decodeBoolean(a.options_truncated ?? a.optionsTruncated);
|
|
5747
|
+
if (!topOptTruncated && (Array.isArray(a.options) || typeof a.options === "string")) {
|
|
5748
|
+
let topOptions = a.options;
|
|
5749
|
+
if (typeof topOptions === "string") {
|
|
5750
|
+
try {
|
|
5751
|
+
topOptions = JSON.parse(topOptions.trim());
|
|
5752
|
+
} catch {
|
|
5753
|
+
}
|
|
5754
|
+
}
|
|
5755
|
+
if (Array.isArray(topOptions)) {
|
|
5756
|
+
topOptTruncated = topOptions.some(
|
|
5757
|
+
(o) => o !== null && typeof o === "object" && decodeBoolean(
|
|
5758
|
+
o.options_truncated ?? o.optionsTruncated
|
|
5759
|
+
)
|
|
5760
|
+
);
|
|
5761
|
+
}
|
|
5762
|
+
}
|
|
5763
|
+
items.push({
|
|
5764
|
+
question: qText,
|
|
5765
|
+
options: opts,
|
|
5766
|
+
isMultiSelect: isMulti,
|
|
5767
|
+
...topOptTruncated ? { optionsTruncated: true } : {}
|
|
5768
|
+
});
|
|
5611
5769
|
}
|
|
5612
5770
|
return items.length > 0 ? items : void 0;
|
|
5613
5771
|
}
|
|
@@ -5709,6 +5867,104 @@ function matchPaneQuestionLines(hookText, candidate) {
|
|
|
5709
5867
|
function paneCandidateMatchesQuestion(hookText, candidate) {
|
|
5710
5868
|
return matchPaneQuestionLines(hookText, candidate) !== null;
|
|
5711
5869
|
}
|
|
5870
|
+
function buildHookQuestionOptionsAndSubmitMap(rawOptions, isMultiSelect, optionsTruncated = false) {
|
|
5871
|
+
if (!rawOptions || rawOptions.length === 0) {
|
|
5872
|
+
return { paneOptions: void 0, submitMap: {} };
|
|
5873
|
+
}
|
|
5874
|
+
const normalized = rawOptions.map((opt, idx) => {
|
|
5875
|
+
const entry = {
|
|
5876
|
+
number: String(opt.number ?? idx + 1),
|
|
5877
|
+
text: opt.text
|
|
5878
|
+
};
|
|
5879
|
+
if (opt.isGeneratedWriteIn) {
|
|
5880
|
+
Object.defineProperty(entry, "isGeneratedWriteIn", {
|
|
5881
|
+
value: true,
|
|
5882
|
+
enumerable: false,
|
|
5883
|
+
configurable: true
|
|
5884
|
+
});
|
|
5885
|
+
}
|
|
5886
|
+
return entry;
|
|
5887
|
+
});
|
|
5888
|
+
if (isMultiSelect || optionsTruncated || normalized.length >= 9) {
|
|
5889
|
+
return { paneOptions: normalized, submitMap: {} };
|
|
5890
|
+
}
|
|
5891
|
+
const lastOpt = normalized[normalized.length - 1];
|
|
5892
|
+
const hasTrailingGeneratedWriteIn = Boolean(lastOpt?.isGeneratedWriteIn);
|
|
5893
|
+
let paneOptions;
|
|
5894
|
+
if (hasTrailingGeneratedWriteIn) {
|
|
5895
|
+
paneOptions = normalized;
|
|
5896
|
+
} else {
|
|
5897
|
+
const writeInOpt = {
|
|
5898
|
+
number: String(normalized.length + 1),
|
|
5899
|
+
text: "Write-in..."
|
|
5900
|
+
};
|
|
5901
|
+
Object.defineProperty(writeInOpt, "isGeneratedWriteIn", {
|
|
5902
|
+
value: true,
|
|
5903
|
+
enumerable: false,
|
|
5904
|
+
configurable: true
|
|
5905
|
+
});
|
|
5906
|
+
paneOptions = [...normalized, writeInOpt];
|
|
5907
|
+
}
|
|
5908
|
+
const submitMap = {};
|
|
5909
|
+
for (const opt of paneOptions) {
|
|
5910
|
+
submitMap[opt.number] = [opt.number];
|
|
5911
|
+
}
|
|
5912
|
+
return { paneOptions, submitMap };
|
|
5913
|
+
}
|
|
5914
|
+
function hookOptionsMatchPaneOptions(hookOpts, paneOpts) {
|
|
5915
|
+
if (!hookOpts || hookOpts.length === 0 || !paneOpts || paneOpts.length === 0) {
|
|
5916
|
+
return false;
|
|
5917
|
+
}
|
|
5918
|
+
const normOpt = (s) => s.normalize("NFKC").toLowerCase().replace(/\s+/g, " ").trim();
|
|
5919
|
+
const hookNormalized = hookOpts.map((o, idx) => ({
|
|
5920
|
+
number: String(o.number ?? idx + 1).trim(),
|
|
5921
|
+
text: normOpt(o.text),
|
|
5922
|
+
isGeneratedWriteIn: Boolean(o.isGeneratedWriteIn)
|
|
5923
|
+
}));
|
|
5924
|
+
const hookEffective = hookNormalized.length > 0 && hookNormalized[hookNormalized.length - 1].isGeneratedWriteIn ? hookNormalized.slice(0, -1) : hookNormalized;
|
|
5925
|
+
const paneNormalized = paneOpts.map((o, idx) => ({
|
|
5926
|
+
number: String(o.number ?? idx + 1).trim(),
|
|
5927
|
+
text: normOpt(o.text),
|
|
5928
|
+
isGeneratedWriteIn: Boolean(o.isGeneratedWriteIn)
|
|
5929
|
+
}));
|
|
5930
|
+
const lastPane = paneNormalized[paneNormalized.length - 1];
|
|
5931
|
+
const paneHasTrailingWriteIn = Boolean(lastPane) && (lastPane.isGeneratedWriteIn || paneNormalized.length === hookEffective.length + 1 && /^\s*write-?in(?:\.{3}|…)?\s*$/i.test(lastPane.text));
|
|
5932
|
+
const paneEffective = paneHasTrailingWriteIn ? paneNormalized.slice(0, -1) : paneNormalized;
|
|
5933
|
+
if (hookEffective.length === 0 || hookEffective.length !== paneEffective.length) {
|
|
5934
|
+
return false;
|
|
5935
|
+
}
|
|
5936
|
+
return hookEffective.every(
|
|
5937
|
+
(h, i) => paneEffective[i].text === h.text && paneEffective[i].number === h.number
|
|
5938
|
+
);
|
|
5939
|
+
}
|
|
5940
|
+
function tagPaneGeneratedWriteInOption(paneOpts, hookStepOpts) {
|
|
5941
|
+
if (!paneOpts || paneOpts.length === 0) return paneOpts;
|
|
5942
|
+
const hookBaseOpts = hookStepOpts ? hookStepOpts.filter((o) => !o.isGeneratedWriteIn) : void 0;
|
|
5943
|
+
for (let i = 0; i < paneOpts.length - 1; i++) {
|
|
5944
|
+
const opt = paneOpts[i];
|
|
5945
|
+
if (opt?.isGeneratedWriteIn) {
|
|
5946
|
+
delete opt.isGeneratedWriteIn;
|
|
5947
|
+
}
|
|
5948
|
+
}
|
|
5949
|
+
const lastOpt = paneOpts[paneOpts.length - 1];
|
|
5950
|
+
if (!lastOpt || !/^\s*write-?in(?:\.{3}|…)?\s*$/i.test(lastOpt.text)) {
|
|
5951
|
+
if (lastOpt?.isGeneratedWriteIn) {
|
|
5952
|
+
delete lastOpt.isGeneratedWriteIn;
|
|
5953
|
+
}
|
|
5954
|
+
return paneOpts;
|
|
5955
|
+
}
|
|
5956
|
+
const isGenerated = !hookBaseOpts || hookBaseOpts.length === 0 || paneOpts.length === hookBaseOpts.length + 1;
|
|
5957
|
+
if (isGenerated) {
|
|
5958
|
+
Object.defineProperty(lastOpt, "isGeneratedWriteIn", {
|
|
5959
|
+
value: true,
|
|
5960
|
+
enumerable: false,
|
|
5961
|
+
configurable: true
|
|
5962
|
+
});
|
|
5963
|
+
} else if (lastOpt.isGeneratedWriteIn) {
|
|
5964
|
+
delete lastOpt.isGeneratedWriteIn;
|
|
5965
|
+
}
|
|
5966
|
+
return paneOpts;
|
|
5967
|
+
}
|
|
5712
5968
|
function correlatesQuestionInvocation(call, delivery) {
|
|
5713
5969
|
if (!call) return false;
|
|
5714
5970
|
const tn = (delivery.toolName ?? "").toLowerCase().trim();
|
|
@@ -5898,6 +6154,9 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
|
|
|
5898
6154
|
return null;
|
|
5899
6155
|
}
|
|
5900
6156
|
}
|
|
6157
|
+
let replacedHookCall;
|
|
6158
|
+
let replacedWasHookPrompt = false;
|
|
6159
|
+
let replacedHookWasDisplayOnly = false;
|
|
5901
6160
|
if (candidate.kind === "question") {
|
|
5902
6161
|
const paneQuestionText = candidate.headerText || candidate.fullHeaderLine;
|
|
5903
6162
|
const candidateSemanticKey = buildApprovalSemanticKey(candidate);
|
|
@@ -5914,7 +6173,7 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
|
|
|
5914
6173
|
const qOptTexts = (targetQ.options || []).map((o) => normOpt(typeof o === "string" ? o : o.text));
|
|
5915
6174
|
const cOptTexts = (candidate.options || []).map((o) => normOpt(typeof o === "string" ? o : o.text));
|
|
5916
6175
|
if (cOptTexts.length > 0 && qOptTexts.length > 0) {
|
|
5917
|
-
const hasTrailingWriteIn = cOptTexts.length === qOptTexts.length + 1 && /^\s*write-?in
|
|
6176
|
+
const hasTrailingWriteIn = cOptTexts.length === qOptTexts.length + 1 && /^\s*write-?in(?:\.{3}|…)?\s*$/i.test(cOptTexts[cOptTexts.length - 1]);
|
|
5918
6177
|
const cEffectiveTexts = hasTrailingWriteIn ? cOptTexts.slice(0, -1) : cOptTexts;
|
|
5919
6178
|
const matchedQCount = qOptTexts.filter((q) => cEffectiveTexts.includes(q)).length;
|
|
5920
6179
|
const matchedCCount = cEffectiveTexts.filter((c) => qOptTexts.includes(c)).length;
|
|
@@ -5948,6 +6207,20 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
|
|
|
5948
6207
|
}
|
|
5949
6208
|
}
|
|
5950
6209
|
if (bestStepIdx === -1) {
|
|
6210
|
+
const currentStepEval = scoreStep(currentActiveIdx);
|
|
6211
|
+
if (currentStepEval.textMatch) {
|
|
6212
|
+
const existing = [...this.pendingPrompts.values()].find(
|
|
6213
|
+
(p) => p.conversationId === conversationId && p.kind === "question"
|
|
6214
|
+
);
|
|
6215
|
+
if (existing && Object.keys(existing.submitMap || {}).length > 0) {
|
|
6216
|
+
existing.submitMap = {};
|
|
6217
|
+
if (existing.paneOptions && existing.paneOptions.length > 0 && existing.paneOptions[existing.paneOptions.length - 1]?.isGeneratedWriteIn) {
|
|
6218
|
+
existing.paneOptions = existing.paneOptions.slice(0, -1);
|
|
6219
|
+
}
|
|
6220
|
+
existing.paneSemanticKey = candidateSemanticKey;
|
|
6221
|
+
this.emit("prompt-upgraded", existing);
|
|
6222
|
+
}
|
|
6223
|
+
}
|
|
5951
6224
|
logger.debug("Pane candidate did not match any step of active live wizard", {
|
|
5952
6225
|
paneQuestionText,
|
|
5953
6226
|
currentActiveIdx,
|
|
@@ -5966,47 +6239,42 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
|
|
|
5966
6239
|
(p) => p.conversationId === conversationId && p.kind === "question"
|
|
5967
6240
|
);
|
|
5968
6241
|
if (existing) {
|
|
5969
|
-
const
|
|
5970
|
-
const
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
existing.paneOptions = candidate.options;
|
|
5976
|
-
}
|
|
5977
|
-
existing.paneSemanticKey = candidateSemanticKey;
|
|
5978
|
-
existing.paneHash = candidate.paneHash;
|
|
5979
|
-
existing.matchedPaneHeader = stepLineMatch?.stitchedHeaderText ?? candidate.headerText ?? existing.matchedPaneHeader;
|
|
5980
|
-
existing.paneDisplayHeader = stepLineMatch?.stitchedFullHeaderLine ?? candidate.fullHeaderLine ?? existing.paneDisplayHeader;
|
|
5981
|
-
existing.body = stepLineMatch ? stepLineMatch.remainingBody || void 0 : candidate.body;
|
|
5982
|
-
if (candidate.paneHash) this.paneOnlyEmittedHashes.add(candidate.paneHash);
|
|
5983
|
-
logger.info("Upgraded hook-driven question prompt to actionable from pane observer", {
|
|
5984
|
-
promptId: existing.promptId,
|
|
5985
|
-
optionsCount: existing.paneOptions?.length ?? 0
|
|
5986
|
-
});
|
|
5987
|
-
this.emit("prompt-upgraded", existing);
|
|
5988
|
-
return existing;
|
|
5989
|
-
}
|
|
5990
|
-
const areControlsMutated = (existingOpts, candidateOpts) => {
|
|
5991
|
-
if (!existingOpts || !candidateOpts) return false;
|
|
5992
|
-
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());
|
|
5994
|
-
};
|
|
5995
|
-
const isControlDelta = Boolean(
|
|
5996
|
-
existing.paneOptions && existing.paneOptions.length > 0 && candidate.options && candidate.options.length > 0 && areControlsMutated(existing.paneOptions, candidate.options)
|
|
5997
|
-
);
|
|
5998
|
-
if (isControlDelta) {
|
|
5999
|
-
logger.info("Control delta observed on active question step; resolving old prompt and re-emitting", {
|
|
6000
|
-
oldPromptId: existing.promptId,
|
|
6001
|
-
oldSemanticKey: existing.paneSemanticKey,
|
|
6002
|
-
newSemanticKey: candidateSemanticKey
|
|
6003
|
-
});
|
|
6242
|
+
const nextBody = stepLineMatch ? stepLineMatch.remainingBody || void 0 : candidate.body || void 0;
|
|
6243
|
+
const bodyChangedAfterObservation = Boolean(existing.paneBodyObserved) && existing.body !== nextBody;
|
|
6244
|
+
if (bodyChangedAfterObservation) {
|
|
6245
|
+
replacedHookCall = existing.pendingCall;
|
|
6246
|
+
replacedWasHookPrompt = Boolean(existing.isHookPrompt);
|
|
6247
|
+
replacedHookWasDisplayOnly = Object.keys(existing.submitMap || {}).length === 0;
|
|
6004
6248
|
this.resolvePrompt(existing.promptId, { advanceWizard: false });
|
|
6005
6249
|
this.emit("prompt-resolved", existing.promptId);
|
|
6006
6250
|
} else {
|
|
6007
|
-
|
|
6008
|
-
|
|
6251
|
+
const targetQ = existing.pendingCall?.structuredQuestions?.[existing.wizardStepIndex ?? 0];
|
|
6252
|
+
const isAuthoritativeMultiSelect = Boolean(
|
|
6253
|
+
existing.pendingCall?.isMultiSelect || targetQ?.isMultiSelect
|
|
6254
|
+
);
|
|
6255
|
+
const isOptionsTruncated = Boolean(
|
|
6256
|
+
existing.pendingCall?.optionsTruncated || targetQ?.optionsTruncated
|
|
6257
|
+
);
|
|
6258
|
+
const hasExplicitEmptyOptions = Boolean(
|
|
6259
|
+
targetQ && Array.isArray(targetQ.options) && targetQ.options.length === 0
|
|
6260
|
+
);
|
|
6261
|
+
let adoptedFallbackOptions = false;
|
|
6262
|
+
if (!isAuthoritativeMultiSelect && !isOptionsTruncated && !hasExplicitEmptyOptions && Object.keys(existing.submitMap || {}).length === 0 && (!existing.paneOptions || existing.paneOptions.length === 0) && Object.keys(candidate.submitMap || {}).length > 0) {
|
|
6263
|
+
existing.submitMap = candidate.submitMap;
|
|
6264
|
+
if (candidate.options && candidate.options.length > 0) {
|
|
6265
|
+
existing.paneOptions = tagPaneGeneratedWriteInOption(candidate.options, targetQ?.options);
|
|
6266
|
+
}
|
|
6267
|
+
adoptedFallbackOptions = true;
|
|
6268
|
+
}
|
|
6269
|
+
const candidateIsActionable = Object.keys(candidate.submitMap || {}).length > 0;
|
|
6270
|
+
const optionsMatch = hookOptionsMatchPaneOptions(existing.paneOptions, candidate.options);
|
|
6271
|
+
const exactControlsMatch = adoptedFallbackOptions || candidateIsActionable && optionsMatch && (existing.paneOptions?.length ?? 0) === (candidate.options?.length ?? 0) && (existing.paneOptions ?? []).every(
|
|
6272
|
+
(o, i) => o.number === candidate.options[i]?.number && JSON.stringify(existing.submitMap?.[o.number]) === JSON.stringify(candidate.submitMap?.[o.number])
|
|
6273
|
+
);
|
|
6274
|
+
if (existing.paneHash && existing.paneHash !== candidate.paneHash) {
|
|
6275
|
+
this.paneOnlyEmittedHashes.delete(existing.paneHash);
|
|
6009
6276
|
}
|
|
6277
|
+
existing.paneHash = candidate.paneHash;
|
|
6010
6278
|
if (stepLineMatch) {
|
|
6011
6279
|
existing.matchedPaneHeader = stepLineMatch.stitchedHeaderText;
|
|
6012
6280
|
existing.paneDisplayHeader = stepLineMatch.stitchedFullHeaderLine;
|
|
@@ -6014,8 +6282,19 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
|
|
|
6014
6282
|
if (existing.pendingCall && targetStepQ?.question) {
|
|
6015
6283
|
existing.pendingCall.questionText = stepLineMatch.stitchedHeaderText;
|
|
6016
6284
|
}
|
|
6017
|
-
} else
|
|
6018
|
-
existing.body =
|
|
6285
|
+
} else {
|
|
6286
|
+
existing.body = nextBody;
|
|
6287
|
+
}
|
|
6288
|
+
existing.paneBodyObserved = true;
|
|
6289
|
+
if (exactControlsMatch) {
|
|
6290
|
+
existing.paneSemanticKey = candidateSemanticKey;
|
|
6291
|
+
} else if ((!candidateIsActionable || !optionsMatch) && Object.keys(existing.submitMap || {}).length > 0) {
|
|
6292
|
+
existing.submitMap = {};
|
|
6293
|
+
if (existing.paneOptions && existing.paneOptions.length > 0 && existing.paneOptions[existing.paneOptions.length - 1]?.isGeneratedWriteIn) {
|
|
6294
|
+
existing.paneOptions = existing.paneOptions.slice(0, -1);
|
|
6295
|
+
}
|
|
6296
|
+
existing.paneSemanticKey = candidateSemanticKey;
|
|
6297
|
+
this.emit("prompt-upgraded", existing);
|
|
6019
6298
|
}
|
|
6020
6299
|
if (candidate.paneHash) this.paneOnlyEmittedHashes.add(candidate.paneHash);
|
|
6021
6300
|
return null;
|
|
@@ -6042,44 +6321,86 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
|
|
|
6042
6321
|
const lineMatch = matchPaneQuestionLines(hookQuestionText, candidate);
|
|
6043
6322
|
const isSameQuestion = lineMatch !== null;
|
|
6044
6323
|
if (isSameQuestion) {
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6324
|
+
if (p.isHookPrompt) {
|
|
6325
|
+
const nextBody = lineMatch ? lineMatch.remainingBody || void 0 : candidate.body || void 0;
|
|
6326
|
+
const bodyChangedAfterObservation = Boolean(p.paneBodyObserved) && p.body !== nextBody;
|
|
6327
|
+
if (!bodyChangedAfterObservation) {
|
|
6328
|
+
const targetQ = p.pendingCall?.structuredQuestions?.[p.wizardStepIndex ?? 0];
|
|
6329
|
+
const isAuthoritativeMultiSelect = Boolean(
|
|
6330
|
+
p.pendingCall?.isMultiSelect || targetQ?.isMultiSelect
|
|
6331
|
+
);
|
|
6332
|
+
const isOptionsTruncated = Boolean(
|
|
6333
|
+
p.pendingCall?.optionsTruncated || targetQ?.optionsTruncated
|
|
6334
|
+
);
|
|
6335
|
+
let adoptedFallbackOptions = false;
|
|
6336
|
+
if (!isAuthoritativeMultiSelect && !isOptionsTruncated && Object.keys(p.submitMap || {}).length === 0 && (!p.paneOptions || p.paneOptions.length === 0) && Object.keys(candidate.submitMap || {}).length > 0) {
|
|
6337
|
+
p.submitMap = candidate.submitMap;
|
|
6338
|
+
if (candidate.options && candidate.options.length > 0) {
|
|
6339
|
+
p.paneOptions = tagPaneGeneratedWriteInOption(
|
|
6340
|
+
candidate.options,
|
|
6341
|
+
targetQ?.options ?? p.pendingCall?.questionOptions
|
|
6342
|
+
);
|
|
6343
|
+
}
|
|
6344
|
+
adoptedFallbackOptions = true;
|
|
6345
|
+
}
|
|
6346
|
+
const candidateIsActionable = Object.keys(candidate.submitMap || {}).length > 0;
|
|
6347
|
+
const optionsMatch = hookOptionsMatchPaneOptions(p.paneOptions, candidate.options);
|
|
6348
|
+
const exactControlsMatch = adoptedFallbackOptions || candidateIsActionable && optionsMatch && (p.paneOptions?.length ?? 0) === (candidate.options?.length ?? 0) && (p.paneOptions ?? []).every(
|
|
6349
|
+
(o, i) => o.number === candidate.options[i]?.number && JSON.stringify(p.submitMap?.[o.number]) === JSON.stringify(candidate.submitMap?.[o.number])
|
|
6350
|
+
);
|
|
6351
|
+
if (p.paneHash && p.paneHash !== candidate.paneHash) {
|
|
6352
|
+
this.paneOnlyEmittedHashes.delete(p.paneHash);
|
|
6056
6353
|
}
|
|
6057
|
-
p.paneSemanticKey = candidateSemanticKey;
|
|
6058
6354
|
p.paneHash = candidate.paneHash;
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6355
|
+
if (lineMatch) {
|
|
6356
|
+
p.matchedPaneHeader = lineMatch.stitchedHeaderText;
|
|
6357
|
+
p.paneDisplayHeader = lineMatch.stitchedFullHeaderLine;
|
|
6358
|
+
p.body = lineMatch.remainingBody || void 0;
|
|
6359
|
+
if (p.pendingCall) {
|
|
6360
|
+
p.pendingCall.questionText = lineMatch.stitchedHeaderText;
|
|
6361
|
+
}
|
|
6362
|
+
} else {
|
|
6363
|
+
p.body = nextBody;
|
|
6364
|
+
}
|
|
6365
|
+
p.paneBodyObserved = true;
|
|
6366
|
+
if (exactControlsMatch) {
|
|
6367
|
+
p.paneSemanticKey = candidateSemanticKey;
|
|
6368
|
+
} else if ((!candidateIsActionable || !optionsMatch) && Object.keys(p.submitMap || {}).length > 0) {
|
|
6369
|
+
p.submitMap = {};
|
|
6370
|
+
if (p.paneOptions && p.paneOptions.length > 0 && p.paneOptions[p.paneOptions.length - 1]?.isGeneratedWriteIn) {
|
|
6371
|
+
p.paneOptions = p.paneOptions.slice(0, -1);
|
|
6372
|
+
}
|
|
6373
|
+
p.paneSemanticKey = candidateSemanticKey;
|
|
6374
|
+
this.emit("prompt-upgraded", p);
|
|
6375
|
+
}
|
|
6062
6376
|
if (candidate.paneHash) this.paneOnlyEmittedHashes.add(candidate.paneHash);
|
|
6063
|
-
|
|
6064
|
-
promptId: p.promptId,
|
|
6065
|
-
optionsCount: p.paneOptions?.length ?? 0
|
|
6066
|
-
});
|
|
6067
|
-
this.emit("prompt-upgraded", p);
|
|
6068
|
-
return p;
|
|
6377
|
+
return null;
|
|
6069
6378
|
}
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
p.
|
|
6077
|
-
|
|
6078
|
-
p.pendingCall.questionText = lineMatch.stitchedHeaderText;
|
|
6379
|
+
}
|
|
6380
|
+
if (!p.isHookPrompt) {
|
|
6381
|
+
const isControlDelta = Boolean(
|
|
6382
|
+
p.paneSemanticKey && candidateSemanticKey !== p.paneSemanticKey
|
|
6383
|
+
);
|
|
6384
|
+
if (!isControlDelta) {
|
|
6385
|
+
if (!p.paneSemanticKey) {
|
|
6386
|
+
p.paneSemanticKey = candidateSemanticKey;
|
|
6079
6387
|
}
|
|
6388
|
+
if (lineMatch) {
|
|
6389
|
+
p.matchedPaneHeader = lineMatch.stitchedHeaderText;
|
|
6390
|
+
p.paneDisplayHeader = lineMatch.stitchedFullHeaderLine;
|
|
6391
|
+
p.body = lineMatch.remainingBody || void 0;
|
|
6392
|
+
if (p.pendingCall) {
|
|
6393
|
+
p.pendingCall.questionText = lineMatch.stitchedHeaderText;
|
|
6394
|
+
}
|
|
6395
|
+
}
|
|
6396
|
+
p.paneBodyObserved = true;
|
|
6397
|
+
if (candidate.paneHash) this.paneOnlyEmittedHashes.add(candidate.paneHash);
|
|
6398
|
+
return null;
|
|
6080
6399
|
}
|
|
6081
|
-
|
|
6082
|
-
|
|
6400
|
+
} else {
|
|
6401
|
+
replacedHookCall = p.pendingCall;
|
|
6402
|
+
replacedWasHookPrompt = true;
|
|
6403
|
+
replacedHookWasDisplayOnly = Object.keys(p.submitMap || {}).length === 0;
|
|
6083
6404
|
}
|
|
6084
6405
|
}
|
|
6085
6406
|
this.resolvePrompt(p.promptId);
|
|
@@ -6090,14 +6411,35 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
|
|
|
6090
6411
|
}
|
|
6091
6412
|
const promptId = forcedPromptId ?? (0, import_uuid2.v4)();
|
|
6092
6413
|
const liveWizard = candidate.kind === "question" ? this.liveWizardInvocations.get(conversationId) : void 0;
|
|
6093
|
-
const matchingCall = liveWizard ? liveWizard.call : candidate.kind === "question" ? this.findMatchingPendingCall(candidate, conversationId) : null;
|
|
6414
|
+
const matchingCall = liveWizard ? liveWizard.call : candidate.kind === "question" ? this.findMatchingPendingCall(candidate, conversationId) ?? replacedHookCall ?? null : null;
|
|
6094
6415
|
if (liveWizard) {
|
|
6095
6416
|
liveWizard.promptId = promptId;
|
|
6096
6417
|
} else if (matchingCall) {
|
|
6097
6418
|
matchingCall.matchedPromptId = promptId;
|
|
6098
6419
|
}
|
|
6099
|
-
const
|
|
6420
|
+
const matchedStructuredQuestion = candidate.kind === "question" ? liveWizard?.call.structuredQuestions?.[liveWizard.activeStepIndex ?? 0] ?? matchingCall?.structuredQuestions?.find((sq) => paneCandidateMatchesQuestion(sq.question, candidate)) : void 0;
|
|
6421
|
+
const matchedStepQuestionText = candidate.kind === "question" ? matchedStructuredQuestion?.question ?? matchingCall?.questionText : void 0;
|
|
6100
6422
|
const synthLineMatch = candidate.kind === "question" && matchedStepQuestionText ? matchPaneQuestionLines(matchedStepQuestionText, candidate) : null;
|
|
6423
|
+
const activeHookStepOpts = candidate.kind === "question" ? matchedStructuredQuestion?.options ?? matchingCall?.questionOptions : void 0;
|
|
6424
|
+
const isHookRestrictedMultiSelect = Boolean(
|
|
6425
|
+
matchingCall?.isMultiSelect || matchedStructuredQuestion?.isMultiSelect
|
|
6426
|
+
);
|
|
6427
|
+
const isHookRestrictedTruncated = Boolean(
|
|
6428
|
+
matchingCall?.optionsTruncated || matchedStructuredQuestion?.optionsTruncated
|
|
6429
|
+
);
|
|
6430
|
+
const hasExplicitEmptyHookStepOpts = Boolean(
|
|
6431
|
+
matchedStructuredQuestion && Array.isArray(matchedStructuredQuestion.options) && matchedStructuredQuestion.options.length === 0
|
|
6432
|
+
);
|
|
6433
|
+
const hookRawStepOpts = (activeHookStepOpts ?? []).map(
|
|
6434
|
+
(o, idx) => typeof o === "string" ? { number: String(idx + 1), text: o } : { number: String(o.number ?? idx + 1), text: o.text }
|
|
6435
|
+
);
|
|
6436
|
+
const hasHookStepOptionMismatch = candidate.kind === "question" && candidate.options.length > 0 && (hasExplicitEmptyHookStepOpts || hookRawStepOpts.length > 0 && !hookOptionsMatchPaneOptions(hookRawStepOpts, candidate.options));
|
|
6437
|
+
const isHookCall = Boolean(
|
|
6438
|
+
replacedWasHookPrompt || matchingCall?.structuredQuestions && matchingCall.structuredQuestions.length > 0
|
|
6439
|
+
);
|
|
6440
|
+
const isHookRestricted = candidate.kind === "question" && isHookCall && (replacedHookWasDisplayOnly || isHookRestrictedMultiSelect || isHookRestrictedTruncated || hasHookStepOptionMismatch);
|
|
6441
|
+
const effectiveSubmitMap = isHookRestricted ? {} : candidate.submitMap;
|
|
6442
|
+
const taggedCandidateOptions = candidate.kind === "question" ? isHookRestricted ? candidate.options : tagPaneGeneratedWriteInOption(candidate.options, activeHookStepOpts) : candidate.options;
|
|
6101
6443
|
const syntheticCall = {
|
|
6102
6444
|
conversationId,
|
|
6103
6445
|
intentStepIndex: matchingCall?.intentStepIndex ?? -1,
|
|
@@ -6115,26 +6457,26 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
|
|
|
6115
6457
|
// NOTE (Codex R3-F1 / Claude R3-F2): Pane options are authoritative ground truth
|
|
6116
6458
|
// that directly correspond to desktop keystrokes (submitMap). Transcript options
|
|
6117
6459
|
// serve solely as fallback when pane options cannot be parsed.
|
|
6118
|
-
questionOptions: candidate.kind === "question" &&
|
|
6460
|
+
questionOptions: candidate.kind === "question" && taggedCandidateOptions && taggedCandidateOptions.length > 0 ? taggedCandidateOptions : matchingCall?.questionOptions,
|
|
6119
6461
|
structuredQuestions: matchingCall?.structuredQuestions,
|
|
6462
|
+
isMultiSelect: matchingCall?.isMultiSelect ?? matchedStructuredQuestion?.isMultiSelect,
|
|
6463
|
+
optionsTruncated: matchingCall?.optionsTruncated ?? matchedStructuredQuestion?.optionsTruncated,
|
|
6120
6464
|
promptId,
|
|
6121
6465
|
startedAt: matchingCall?.startedAt ?? Date.now(),
|
|
6122
6466
|
intentCreatedAt: matchingCall?.intentCreatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
6123
6467
|
};
|
|
6124
|
-
const isHookCall = Boolean(
|
|
6125
|
-
matchingCall?.structuredQuestions && matchingCall.structuredQuestions.length > 0
|
|
6126
|
-
);
|
|
6127
6468
|
const state = {
|
|
6128
6469
|
promptId,
|
|
6129
6470
|
conversationId,
|
|
6130
6471
|
pendingCall: syntheticCall,
|
|
6131
|
-
submitMap:
|
|
6472
|
+
submitMap: effectiveSubmitMap,
|
|
6132
6473
|
matchedPaneHeader: synthLineMatch?.stitchedHeaderText ?? candidate.headerText,
|
|
6133
6474
|
paneDisplayHeader: synthLineMatch?.stitchedFullHeaderLine ?? candidate.fullHeaderLine,
|
|
6134
6475
|
body: synthLineMatch ? synthLineMatch.remainingBody || void 0 : candidate.body,
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6476
|
+
paneBodyObserved: true,
|
|
6477
|
+
paneOptions: taggedCandidateOptions,
|
|
6478
|
+
paneSemanticKey: buildApprovalSemanticKey({ ...candidate, submitMap: effectiveSubmitMap }),
|
|
6479
|
+
paneRedrawSemanticKey: buildApprovalRedrawSemanticKey({ ...candidate, submitMap: effectiveSubmitMap }),
|
|
6138
6480
|
kind: candidate.kind,
|
|
6139
6481
|
isHookPrompt: isHookCall,
|
|
6140
6482
|
// Carry hook ownership forward so pane redraws do not drop it! (Codex S2-Codex-R6-F15)
|
|
@@ -6184,6 +6526,15 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
|
|
|
6184
6526
|
if (p.pendingCall) {
|
|
6185
6527
|
p.pendingCall.questionText = hookLineMatch?.stitchedHeaderText ?? call.questionText ?? p.pendingCall.questionText;
|
|
6186
6528
|
p.pendingCall.structuredQuestions = call.structuredQuestions;
|
|
6529
|
+
if (call.questionOptions) {
|
|
6530
|
+
p.pendingCall.questionOptions = call.questionOptions;
|
|
6531
|
+
}
|
|
6532
|
+
if (call.isMultiSelect !== void 0) {
|
|
6533
|
+
p.pendingCall.isMultiSelect = call.isMultiSelect;
|
|
6534
|
+
}
|
|
6535
|
+
if (call.optionsTruncated !== void 0) {
|
|
6536
|
+
p.pendingCall.optionsTruncated = call.optionsTruncated;
|
|
6537
|
+
}
|
|
6187
6538
|
if (call.intentStepIndex !== -1) {
|
|
6188
6539
|
p.pendingCall.intentStepIndex = call.intentStepIndex;
|
|
6189
6540
|
}
|
|
@@ -6192,25 +6543,38 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
|
|
|
6192
6543
|
} else {
|
|
6193
6544
|
p.pendingCall = call;
|
|
6194
6545
|
}
|
|
6195
|
-
const
|
|
6546
|
+
const isHookMultiSelect2 = Boolean(
|
|
6196
6547
|
call.isMultiSelect || call.structuredQuestions && call.structuredQuestions[0]?.isMultiSelect
|
|
6197
6548
|
);
|
|
6198
|
-
const
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
const
|
|
6202
|
-
|
|
6203
|
-
|
|
6549
|
+
const isHookOptionsTruncated2 = Boolean(
|
|
6550
|
+
call.optionsTruncated || call.structuredQuestions && call.structuredQuestions[0]?.optionsTruncated
|
|
6551
|
+
);
|
|
6552
|
+
const hookRawOpts = (call.structuredQuestions?.[0]?.options ?? call.questionOptions ?? []).map(
|
|
6553
|
+
(o, idx) => typeof o === "string" ? { number: String(idx + 1), text: o } : { number: String(o.number ?? idx + 1), text: o.text }
|
|
6554
|
+
);
|
|
6555
|
+
const paneOptsNormalized = (p.paneOptions || []).map(
|
|
6556
|
+
(o, idx) => typeof o === "string" ? { number: String(idx + 1), text: o } : {
|
|
6557
|
+
number: String(o.number ?? idx + 1),
|
|
6558
|
+
text: o.text,
|
|
6559
|
+
isGeneratedWriteIn: Boolean(o.isGeneratedWriteIn)
|
|
6560
|
+
}
|
|
6561
|
+
);
|
|
6204
6562
|
const hasStructuredQuestion = Boolean(call.structuredQuestions && call.structuredQuestions.length > 0);
|
|
6205
|
-
const hasHookOptionMismatch =
|
|
6206
|
-
if ((
|
|
6563
|
+
const hasHookOptionMismatch = paneOptsNormalized.length > 0 && (hasStructuredQuestion && hookRawOpts.length === 0 || hookRawOpts.length > 0 && !hookOptionsMatchPaneOptions(hookRawOpts, paneOptsNormalized));
|
|
6564
|
+
if ((isHookMultiSelect2 || isHookOptionsTruncated2 || hasHookOptionMismatch) && Object.keys(p.submitMap || {}).length > 0) {
|
|
6207
6565
|
p.submitMap = {};
|
|
6208
|
-
logger.info("emitHookQuestionPrompt: revoked actionable submitMap due to multi-select or option mismatch", {
|
|
6566
|
+
logger.info("emitHookQuestionPrompt: revoked actionable submitMap due to multi-select, truncation, or option mismatch", {
|
|
6209
6567
|
promptId: p.promptId,
|
|
6210
|
-
isHookMultiSelect,
|
|
6568
|
+
isHookMultiSelect: isHookMultiSelect2,
|
|
6569
|
+
isHookOptionsTruncated: isHookOptionsTruncated2,
|
|
6211
6570
|
hasHookOptionMismatch
|
|
6212
6571
|
});
|
|
6213
6572
|
this.emit("prompt-upgraded", p);
|
|
6573
|
+
} else if (p.paneOptions && p.paneOptions.length > 0 && Object.keys(p.submitMap || {}).length > 0) {
|
|
6574
|
+
p.paneOptions = tagPaneGeneratedWriteInOption(
|
|
6575
|
+
p.paneOptions,
|
|
6576
|
+
hookRawOpts.length > 0 ? hookRawOpts : void 0
|
|
6577
|
+
);
|
|
6214
6578
|
}
|
|
6215
6579
|
p.isHookPrompt = true;
|
|
6216
6580
|
call.matchedPromptId = p.promptId;
|
|
@@ -6241,21 +6605,38 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
|
|
|
6241
6605
|
const promptId = forcedPromptId ?? (0, import_uuid2.v4)();
|
|
6242
6606
|
this.registerPendingCall(call);
|
|
6243
6607
|
call.matchedPromptId = promptId;
|
|
6608
|
+
const firstStructured = call.structuredQuestions?.[0];
|
|
6609
|
+
const isHookMultiSelect = Boolean(
|
|
6610
|
+
call.isMultiSelect || firstStructured?.isMultiSelect
|
|
6611
|
+
);
|
|
6612
|
+
const isHookOptionsTruncated = Boolean(
|
|
6613
|
+
call.optionsTruncated || firstStructured?.optionsTruncated
|
|
6614
|
+
);
|
|
6615
|
+
const hasExplicitEmptyStructuredOptions = Boolean(
|
|
6616
|
+
firstStructured && Array.isArray(firstStructured.options) && firstStructured.options.length === 0
|
|
6617
|
+
);
|
|
6618
|
+
const rawOptions = hasExplicitEmptyStructuredOptions ? [] : firstStructured?.options && firstStructured.options.length > 0 ? firstStructured.options : call.questionOptions;
|
|
6619
|
+
const { paneOptions, submitMap } = buildHookQuestionOptionsAndSubmitMap(
|
|
6620
|
+
rawOptions,
|
|
6621
|
+
isHookMultiSelect,
|
|
6622
|
+
isHookOptionsTruncated
|
|
6623
|
+
);
|
|
6244
6624
|
const state = {
|
|
6245
6625
|
promptId,
|
|
6246
6626
|
conversationId: call.conversationId,
|
|
6247
6627
|
pendingCall: call,
|
|
6248
|
-
submitMap
|
|
6628
|
+
submitMap,
|
|
6249
6629
|
matchedPaneHeader: call.questionText ?? "Question",
|
|
6250
6630
|
paneDisplayHeader: call.questionText ?? "Question",
|
|
6251
6631
|
body: void 0,
|
|
6252
|
-
|
|
6632
|
+
paneBodyObserved: false,
|
|
6633
|
+
paneOptions,
|
|
6253
6634
|
paneSemanticKey: buildApprovalSemanticKey({
|
|
6254
6635
|
kind: "question",
|
|
6255
6636
|
headerText: call.questionText ?? "Question",
|
|
6256
6637
|
fullHeaderLine: call.questionText ?? "Question",
|
|
6257
|
-
options:
|
|
6258
|
-
submitMap
|
|
6638
|
+
options: paneOptions ?? [],
|
|
6639
|
+
submitMap,
|
|
6259
6640
|
body: "",
|
|
6260
6641
|
paneHash: ""
|
|
6261
6642
|
}),
|
|
@@ -6278,7 +6659,8 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
|
|
|
6278
6659
|
promptId,
|
|
6279
6660
|
conversationId: call.conversationId,
|
|
6280
6661
|
questionText: call.questionText,
|
|
6281
|
-
optionsCount:
|
|
6662
|
+
optionsCount: paneOptions?.length ?? 0,
|
|
6663
|
+
mobileActionable: Object.keys(submitMap).length > 0
|
|
6282
6664
|
});
|
|
6283
6665
|
this.emit("pending-prompt", state);
|
|
6284
6666
|
return state;
|
|
@@ -6789,9 +7171,13 @@ var PromptResponder = class {
|
|
|
6789
7171
|
};
|
|
6790
7172
|
}
|
|
6791
7173
|
const isQuestionPrompt = state.kind === "question";
|
|
6792
|
-
|
|
7174
|
+
let expectedStep = (state.paneDisplayHeader || "").match(/\bQuestion\s+(\d+(?:[\/:of\s]+\d+)?):/i)?.[1];
|
|
6793
7175
|
const matchingOpt = state.paneOptions?.find((o) => String(o.number) === String(optionNumber));
|
|
6794
|
-
const
|
|
7176
|
+
const activeStepHookOpts = state.pendingCall?.structuredQuestions?.[state.wizardStepIndex ?? 0]?.options ?? state.pendingCall?.questionOptions;
|
|
7177
|
+
const hookBaseOptCount = activeStepHookOpts ? activeStepHookOpts.filter((o) => !o.isGeneratedWriteIn).length : 0;
|
|
7178
|
+
const isWriteInOption = matchingOpt ? Boolean(
|
|
7179
|
+
matchingOpt.isGeneratedWriteIn || matchingOpt === state.paneOptions?.[state.paneOptions.length - 1] && /^\s*write-?in(?:\.{3}|…)?\s*$/i.test(matchingOpt.text) && (!state.isHookPrompt || hookBaseOptCount === 0 || (state.paneOptions?.length ?? 0) === hookBaseOptCount + 1)
|
|
7180
|
+
) : false;
|
|
6795
7181
|
if (isQuestionPrompt) {
|
|
6796
7182
|
if (state.paneOptions && state.paneOptions.length > 0 && !matchingOpt) {
|
|
6797
7183
|
return {
|
|
@@ -6849,9 +7235,10 @@ var PromptResponder = class {
|
|
|
6849
7235
|
};
|
|
6850
7236
|
}
|
|
6851
7237
|
let liveSemanticKey = null;
|
|
7238
|
+
let liveCandidate = null;
|
|
6852
7239
|
try {
|
|
6853
|
-
|
|
6854
|
-
|
|
7240
|
+
let snapshot = await this.paneObserver.captureSnapshot();
|
|
7241
|
+
let postCapturePrompt = this.detector.getPendingPrompt(promptId);
|
|
6855
7242
|
if (postCapturePrompt !== state || !postCapturePrompt.submitMap || !postCapturePrompt.submitMap[optionNumber]) {
|
|
6856
7243
|
return {
|
|
6857
7244
|
ok: false,
|
|
@@ -6868,7 +7255,35 @@ var PromptResponder = class {
|
|
|
6868
7255
|
deliveryPhase: "none"
|
|
6869
7256
|
};
|
|
6870
7257
|
}
|
|
6871
|
-
|
|
7258
|
+
let live = parseApprovalSnapshot(snapshot);
|
|
7259
|
+
if (!live && isQuestionPrompt && state.isHookPrompt && !state.paneHash && detectActivePromptKind(snapshot) === null) {
|
|
7260
|
+
const waitDeadline = Date.now() + 250;
|
|
7261
|
+
while (!live && Date.now() < waitDeadline) {
|
|
7262
|
+
await delay(50);
|
|
7263
|
+
snapshot = await this.paneObserver.captureSnapshot();
|
|
7264
|
+
postCapturePrompt = this.detector.getPendingPrompt(promptId);
|
|
7265
|
+
if (postCapturePrompt !== state || !postCapturePrompt.submitMap || !postCapturePrompt.submitMap[optionNumber]) {
|
|
7266
|
+
return {
|
|
7267
|
+
ok: false,
|
|
7268
|
+
reason: "prompt-expired",
|
|
7269
|
+
details: "option is no longer actionable on active prompt (controls revoked or updated)",
|
|
7270
|
+
deliveryPhase
|
|
7271
|
+
};
|
|
7272
|
+
}
|
|
7273
|
+
live = parseApprovalSnapshot(snapshot);
|
|
7274
|
+
if (!live && detectActivePromptKind(snapshot) !== null) {
|
|
7275
|
+
break;
|
|
7276
|
+
}
|
|
7277
|
+
}
|
|
7278
|
+
if (!live && detectActivePromptKind(snapshot) === null && !state.paneHash) {
|
|
7279
|
+
return {
|
|
7280
|
+
ok: false,
|
|
7281
|
+
reason: "prompt-probe-failed",
|
|
7282
|
+
details: "the question chooser has not rendered on the terminal yet; please retry in a moment",
|
|
7283
|
+
deliveryPhase
|
|
7284
|
+
};
|
|
7285
|
+
}
|
|
7286
|
+
}
|
|
6872
7287
|
if (!live) {
|
|
6873
7288
|
if (detectActivePromptKind(snapshot) === null) {
|
|
6874
7289
|
this.detector.resolvePrompt(promptId);
|
|
@@ -6886,6 +7301,7 @@ var PromptResponder = class {
|
|
|
6886
7301
|
deliveryPhase
|
|
6887
7302
|
};
|
|
6888
7303
|
}
|
|
7304
|
+
liveCandidate = live;
|
|
6889
7305
|
liveSemanticKey = live ? buildApprovalSemanticKey(live) : null;
|
|
6890
7306
|
} catch (error) {
|
|
6891
7307
|
return {
|
|
@@ -6895,15 +7311,55 @@ var PromptResponder = class {
|
|
|
6895
7311
|
deliveryPhase
|
|
6896
7312
|
};
|
|
6897
7313
|
}
|
|
6898
|
-
if (
|
|
7314
|
+
if (!liveCandidate || !liveCandidate.submitMap?.[optionNumber]?.length) {
|
|
6899
7315
|
this.detector.resolvePrompt(promptId);
|
|
6900
7316
|
return {
|
|
6901
7317
|
ok: false,
|
|
6902
7318
|
reason: "prompt-superseded",
|
|
6903
|
-
details: "active chooser
|
|
7319
|
+
details: "active chooser does not allow actionable submission for this option",
|
|
6904
7320
|
deliveryPhase
|
|
6905
7321
|
};
|
|
6906
7322
|
}
|
|
7323
|
+
if (liveSemanticKey !== state.paneSemanticKey) {
|
|
7324
|
+
let hookQuestionMatchesLive = false;
|
|
7325
|
+
if (isQuestionPrompt && state.isHookPrompt && liveCandidate && liveCandidate.kind === "question") {
|
|
7326
|
+
const activeStepQ = state.pendingCall?.structuredQuestions?.[state.wizardStepIndex ?? 0];
|
|
7327
|
+
const hookQuestionText = activeStepQ?.question ?? state.pendingCall?.questionText ?? state.matchedPaneHeader;
|
|
7328
|
+
const lineMatch = matchPaneQuestionLines(hookQuestionText, liveCandidate);
|
|
7329
|
+
const liveRemainingBody = lineMatch ? lineMatch.remainingBody || void 0 : liveCandidate.body || void 0;
|
|
7330
|
+
const bodyConsistent = !state.paneBodyObserved || state.body === liveRemainingBody;
|
|
7331
|
+
const normOpt = (s) => s.normalize("NFKC").toLowerCase().replace(/\s+/g, " ").trim();
|
|
7332
|
+
const optionsMatch = hookOptionsMatchPaneOptions(state.paneOptions, liveCandidate.options);
|
|
7333
|
+
const selectedLiveOpt = (liveCandidate.options ?? []).find(
|
|
7334
|
+
(o) => String(o.number) === String(optionNumber)
|
|
7335
|
+
);
|
|
7336
|
+
const selectedOptMatches = isWriteInOption ? Boolean(selectedLiveOpt && /^\s*write-?in(?:\.{3}|…)?\s*$/i.test(selectedLiveOpt.text)) : Boolean(
|
|
7337
|
+
selectedLiveOpt && matchingOpt && normOpt(selectedLiveOpt.text) === normOpt(matchingOpt.text)
|
|
7338
|
+
);
|
|
7339
|
+
if (lineMatch !== null && bodyConsistent && optionsMatch && selectedOptMatches && Boolean(liveCandidate.submitMap[optionNumber]?.length)) {
|
|
7340
|
+
hookQuestionMatchesLive = true;
|
|
7341
|
+
state.matchedPaneHeader = lineMatch.stitchedHeaderText;
|
|
7342
|
+
state.paneDisplayHeader = lineMatch.stitchedFullHeaderLine;
|
|
7343
|
+
state.body = liveRemainingBody;
|
|
7344
|
+
state.paneBodyObserved = true;
|
|
7345
|
+
if (liveSemanticKey) {
|
|
7346
|
+
state.paneSemanticKey = liveSemanticKey;
|
|
7347
|
+
}
|
|
7348
|
+
expectedStep = (state.paneDisplayHeader || "").match(/\bQuestion\s+(\d+(?:[\/:of\s]+\d+)?):/i)?.[1];
|
|
7349
|
+
}
|
|
7350
|
+
}
|
|
7351
|
+
if (!hookQuestionMatchesLive) {
|
|
7352
|
+
this.detector.resolvePrompt(promptId);
|
|
7353
|
+
return {
|
|
7354
|
+
ok: false,
|
|
7355
|
+
reason: "prompt-superseded",
|
|
7356
|
+
details: "active chooser semantics differ from the mobile prompt",
|
|
7357
|
+
deliveryPhase
|
|
7358
|
+
};
|
|
7359
|
+
}
|
|
7360
|
+
} else if (!state.paneBodyObserved) {
|
|
7361
|
+
state.paneBodyObserved = true;
|
|
7362
|
+
}
|
|
6907
7363
|
deliveryPhase = "option_attempted";
|
|
6908
7364
|
if (isNamedKey(key)) {
|
|
6909
7365
|
await this.sendKey(target, key);
|
|
@@ -8970,6 +9426,7 @@ var McpServer = class _McpServer {
|
|
|
8970
9426
|
questionText,
|
|
8971
9427
|
questionOptions,
|
|
8972
9428
|
structuredQuestions,
|
|
9429
|
+
optionsTruncated: Boolean(structuredQuestions?.[0]?.optionsTruncated),
|
|
8973
9430
|
startedAt: Date.now(),
|
|
8974
9431
|
intentCreatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8975
9432
|
};
|
|
@@ -8997,7 +9454,19 @@ var McpServer = class _McpServer {
|
|
|
8997
9454
|
});
|
|
8998
9455
|
return;
|
|
8999
9456
|
}
|
|
9000
|
-
const
|
|
9457
|
+
const firstStructured = pendingCall.structuredQuestions?.[0];
|
|
9458
|
+
const isMultiSelect = Boolean(
|
|
9459
|
+
pendingCall.isMultiSelect || firstStructured?.isMultiSelect
|
|
9460
|
+
);
|
|
9461
|
+
const optionsTruncated = Boolean(
|
|
9462
|
+
pendingCall.optionsTruncated || firstStructured?.optionsTruncated
|
|
9463
|
+
);
|
|
9464
|
+
const hasExplicitEmptyStructuredOptions = Boolean(
|
|
9465
|
+
firstStructured && Array.isArray(firstStructured.options) && firstStructured.options.length === 0
|
|
9466
|
+
);
|
|
9467
|
+
const rawOpts = hasExplicitEmptyStructuredOptions ? [] : firstStructured?.options && firstStructured.options.length > 0 ? firstStructured.options : pendingCall.questionOptions;
|
|
9468
|
+
const isInitiallyActionable = !isMultiSelect && !optionsTruncated && Boolean(rawOpts && rawOpts.length > 0 && rawOpts.length <= 8);
|
|
9469
|
+
const e1rec = this.newE1Raise(session.sessionId, isInitiallyActionable);
|
|
9001
9470
|
const emitted = this.approvalDetector.emitHookQuestionPrompt(pendingCall, e1rec.promptId);
|
|
9002
9471
|
if (!emitted) {
|
|
9003
9472
|
this.e1PromptRaises.delete(e1rec.promptId);
|
|
@@ -10437,7 +10906,7 @@ var McpServer = class _McpServer {
|
|
|
10437
10906
|
} else if (optionResult.reason === "prompt-expired" || optionResult.reason === "prompt-superseded") {
|
|
10438
10907
|
message = "The question prompt on desktop has expired or changed. Please review on desktop.";
|
|
10439
10908
|
} else if (optionResult.reason === "prompt-probe-failed") {
|
|
10440
|
-
message = "Write-in input field was not ready on desktop. Please try again or respond on desktop.";
|
|
10909
|
+
message = optionResult.details?.includes("has not rendered on the terminal yet") ? optionResult.details : intent.followUpText ? "Write-in input field was not ready on desktop. Please try again or respond on desktop." : "Question prompt was not ready on desktop. Please try again or respond on desktop.";
|
|
10441
10910
|
} else if (optionResult.reason === "no-tmux-target" || optionResult.reason === "tmux-failed") {
|
|
10442
10911
|
message = optionResult.details || "Desktop terminal session is unreachable. Please respond directly on desktop.";
|
|
10443
10912
|
} else if (optionResult.reason === "display-only-question-active") {
|
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.104",
|
|
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": {
|