@quantiya/codevibe-antigravity-plugin 2.0.78 → 2.0.80
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 +959 -163
- package/package.json +2 -2
package/dist/server.js
CHANGED
|
@@ -34,16 +34,19 @@ __export(server_exports, {
|
|
|
34
34
|
SessionNotFoundError: () => SessionNotFoundError,
|
|
35
35
|
StoredAuthFailureError: () => StoredAuthFailureError,
|
|
36
36
|
TERMINAL_SHUTDOWN_SIGNALS: () => TERMINAL_SHUTDOWN_SIGNALS,
|
|
37
|
+
TmuxPaneObserver: () => TmuxPaneObserver,
|
|
37
38
|
__testing: () => __testing,
|
|
38
39
|
classifyEntrypoint: () => classifyEntrypoint,
|
|
39
40
|
classifyTmuxHasSessionError: () => classifyTmuxHasSessionError,
|
|
40
41
|
commandDigest: () => commandDigest,
|
|
41
42
|
detectActivePromptKind: () => detectActivePromptKind,
|
|
43
|
+
extractFileAccessPrompt: () => extractFileAccessPrompt,
|
|
42
44
|
extractSystemErrorPrompt: () => extractSystemErrorPrompt,
|
|
43
45
|
generateLaunchSessionId: () => generateLaunchSessionId,
|
|
44
46
|
getActiveConversationFromCliLog: () => getActiveConversationFromCliLog,
|
|
45
47
|
isAgyStatusFooterLine: () => isAgyStatusFooterLine,
|
|
46
48
|
isComposedStatusFooter: () => isComposedStatusFooter,
|
|
49
|
+
isPaneInputSafetyBlocked: () => isPaneInputSafetyBlocked,
|
|
47
50
|
isSystemErrorActiveFromIndex: () => isSystemErrorActiveFromIndex,
|
|
48
51
|
looksLikePromptSnapshot: () => looksLikePromptSnapshot,
|
|
49
52
|
parseApprovalSnapshot: () => parseApprovalSnapshot,
|
|
@@ -59,7 +62,7 @@ var fs7 = __toESM(require("fs"));
|
|
|
59
62
|
var os7 = __toESM(require("os"));
|
|
60
63
|
var import_child_process3 = require("child_process");
|
|
61
64
|
var import_util3 = require("util");
|
|
62
|
-
var
|
|
65
|
+
var import_codevibe_core7 = require("@quantiya/codevibe-core");
|
|
63
66
|
|
|
64
67
|
// src/logger.ts
|
|
65
68
|
var import_os = __toESM(require("os"));
|
|
@@ -400,6 +403,7 @@ var fs2 = __toESM(require("fs"));
|
|
|
400
403
|
var os3 = __toESM(require("os"));
|
|
401
404
|
var path3 = __toESM(require("path"));
|
|
402
405
|
var import_chokidar = require("chokidar");
|
|
406
|
+
var import_codevibe_core4 = require("@quantiya/codevibe-core");
|
|
403
407
|
|
|
404
408
|
// src/shutdown-bounds.ts
|
|
405
409
|
var TEARDOWN_STEP_TIMEOUT_MS = 3e3;
|
|
@@ -466,7 +470,12 @@ function readAdmittedTranscript(options) {
|
|
|
466
470
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) && Number.isFinite(parsed.step_index) && typeof parsed.type === "string" ? parsed : null;
|
|
467
471
|
},
|
|
468
472
|
admission: {
|
|
469
|
-
restore: (source, initial) => options.admission.restore(stream(source), initial)
|
|
473
|
+
restore: (source, initial) => options.admission.restore(stream(source), initial, () => {
|
|
474
|
+
const expected = options.initialFileIdentity;
|
|
475
|
+
if (expected && (source.fileIdentity.dev !== expected.dev || source.fileIdentity.ino !== expected.ino)) {
|
|
476
|
+
throw Error("SYNC_TOOL_INITIAL_SOURCE_CHANGED");
|
|
477
|
+
}
|
|
478
|
+
}),
|
|
470
479
|
skip: (gap, source) => options.admission.skip(gap, stream(source)),
|
|
471
480
|
admit: (event, source) => options.admission.admit({
|
|
472
481
|
conversationId: options.conversationId,
|
|
@@ -553,6 +562,11 @@ var TranscriptTailer = class extends import_events.EventEmitter {
|
|
|
553
562
|
/** Last swept stat of each unfollowed conversation's transcript (null when
|
|
554
563
|
* absent), for the resume sweep. */
|
|
555
564
|
this.transcriptSnapshots = /* @__PURE__ */ new Map();
|
|
565
|
+
/** Immutable startup boundary; discovery snapshots keep moving. */
|
|
566
|
+
this.nativeLaunchBaselines = /* @__PURE__ */ new Map();
|
|
567
|
+
this.nativeLaunchTime = null;
|
|
568
|
+
this.nativeInitialBoundaries = /* @__PURE__ */ new Map();
|
|
569
|
+
this.nativeSeededPaths = /* @__PURE__ */ new Set();
|
|
556
570
|
this.rootMtimeMs = 0;
|
|
557
571
|
this.discoveryTimer = null;
|
|
558
572
|
this.discoveryTickCount = 0;
|
|
@@ -590,12 +604,34 @@ var TranscriptTailer = class extends import_events.EventEmitter {
|
|
|
590
604
|
* snapshots the brain, then runs the discovery tick. Opens no directory
|
|
591
605
|
* watch (see the file header).
|
|
592
606
|
*/
|
|
607
|
+
/** Capture only; no readers, listeners, timers or cloud activity. The server
|
|
608
|
+
* calls this before its first startup await, then activates the tailer later. */
|
|
609
|
+
captureLaunchBaselines() {
|
|
610
|
+
this.nativeLaunchTime = Date.now();
|
|
611
|
+
this.nativeLaunchBaselines.clear();
|
|
612
|
+
this.nativeSeededPaths.clear();
|
|
613
|
+
for (const id of this.listConversationDirs() ?? []) {
|
|
614
|
+
this.nativeLaunchBaselines.set(id, this.snapshotTranscript(this.transcriptPathFor(id)));
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
/** Called after the retired owner's readers are joined, before its successor
|
|
618
|
+
* starts cloud bootstrap. Keep old scoped journals; only fresh seeds change. */
|
|
619
|
+
refreshNativeOwnerBoundary() {
|
|
620
|
+
this.captureLaunchBaselines();
|
|
621
|
+
this.nativeInitialBoundaries.clear();
|
|
622
|
+
for (const timer of this.retryTimers.values()) clearTimeout(timer);
|
|
623
|
+
this.retryTimers.clear();
|
|
624
|
+
this.readGeneration++;
|
|
625
|
+
this.activeReads.clear();
|
|
626
|
+
this.pendingReads.clear();
|
|
627
|
+
}
|
|
593
628
|
start() {
|
|
594
629
|
if (this.isWatching) {
|
|
595
630
|
logger.warn("TranscriptTailer.start called while already watching");
|
|
596
631
|
return;
|
|
597
632
|
}
|
|
598
|
-
this.
|
|
633
|
+
if (this.nativeLaunchTime === null) this.captureLaunchBaselines();
|
|
634
|
+
this.startTime = this.nativeLaunchTime;
|
|
599
635
|
this.readGeneration++;
|
|
600
636
|
for (const timer of this.retryTimers.values()) clearTimeout(timer);
|
|
601
637
|
this.retryTimers.clear();
|
|
@@ -615,15 +651,14 @@ var TranscriptTailer = class extends import_events.EventEmitter {
|
|
|
615
651
|
this.knownConversationDirs.clear();
|
|
616
652
|
this.pendingTranscripts.clear();
|
|
617
653
|
this.transcriptSnapshots.clear();
|
|
654
|
+
this.nativeInitialBoundaries.clear();
|
|
655
|
+
this.nativeSeededPaths.clear();
|
|
618
656
|
this.releasedConversations.clear();
|
|
619
657
|
this.discoveryTickCount = 0;
|
|
620
658
|
this.rootMtimeMs = this.statMtimeMs(this.brainRoot) ?? 0;
|
|
621
659
|
for (const conversationId of this.listConversationDirs() ?? []) {
|
|
622
660
|
this.knownConversationDirs.add(conversationId);
|
|
623
|
-
this.transcriptSnapshots.set(
|
|
624
|
-
conversationId,
|
|
625
|
-
this.snapshotTranscript(this.transcriptPathFor(conversationId))
|
|
626
|
-
);
|
|
661
|
+
this.transcriptSnapshots.set(conversationId, this.nativeLaunchBaselines.get(conversationId) ?? null);
|
|
627
662
|
}
|
|
628
663
|
logger.info("Starting transcript tailer", {
|
|
629
664
|
brainRoot: this.brainRoot,
|
|
@@ -677,6 +712,10 @@ var TranscriptTailer = class extends import_events.EventEmitter {
|
|
|
677
712
|
* Idempotent.
|
|
678
713
|
*/
|
|
679
714
|
async stop() {
|
|
715
|
+
this.nativeLaunchBaselines.clear();
|
|
716
|
+
this.nativeLaunchTime = null;
|
|
717
|
+
this.nativeInitialBoundaries.clear();
|
|
718
|
+
this.nativeSeededPaths.clear();
|
|
680
719
|
if (!this.isWatching) return;
|
|
681
720
|
this.isShuttingDown = true;
|
|
682
721
|
this.readGeneration++;
|
|
@@ -1034,7 +1073,7 @@ var TranscriptTailer = class extends import_events.EventEmitter {
|
|
|
1034
1073
|
snapshotTranscript(transcriptPath) {
|
|
1035
1074
|
try {
|
|
1036
1075
|
const stat = fs2.statSync(transcriptPath);
|
|
1037
|
-
return stat.isFile() ? { size: stat.size, mtimeMs: stat.mtimeMs, ino: stat.ino } : null;
|
|
1076
|
+
return stat.isFile() ? { size: stat.size, mtimeMs: stat.mtimeMs, ino: stat.ino, dev: stat.dev } : null;
|
|
1038
1077
|
} catch {
|
|
1039
1078
|
return null;
|
|
1040
1079
|
}
|
|
@@ -1225,6 +1264,7 @@ var TranscriptTailer = class extends import_events.EventEmitter {
|
|
|
1225
1264
|
}
|
|
1226
1265
|
async readNewLines(filePath, conversationId, generation) {
|
|
1227
1266
|
if (generation !== this.readGeneration) return;
|
|
1267
|
+
if (this.admission && !this.nativeSeededPaths.has(filePath)) this.ensureOffset(filePath);
|
|
1228
1268
|
const entry = this.offsets.get(filePath);
|
|
1229
1269
|
if (!entry) {
|
|
1230
1270
|
logger.warn("No offset entry \u2014 skipping read", { filePath });
|
|
@@ -1235,6 +1275,7 @@ var TranscriptTailer = class extends import_events.EventEmitter {
|
|
|
1235
1275
|
filePath,
|
|
1236
1276
|
conversationId,
|
|
1237
1277
|
initialOffset: entry.byteOffset,
|
|
1278
|
+
initialFileIdentity: this.nativeInitialBoundaries.get(filePath)?.fileIdentity,
|
|
1238
1279
|
admission: this.admission,
|
|
1239
1280
|
isCurrent: () => generation === this.readGeneration && !this.isShuttingDown
|
|
1240
1281
|
});
|
|
@@ -1361,8 +1402,8 @@ var TranscriptTailer = class extends import_events.EventEmitter {
|
|
|
1361
1402
|
}
|
|
1362
1403
|
// ─── Offset persistence ─────────────────────────────────────────────────
|
|
1363
1404
|
ensureOffset(filePath) {
|
|
1364
|
-
if (this.offsets.has(filePath)) return;
|
|
1365
|
-
if (this.offsets.size >= LRU_CAP) {
|
|
1405
|
+
if (this.offsets.has(filePath) && (!this.admission || this.nativeSeededPaths.has(filePath))) return;
|
|
1406
|
+
if (!this.offsets.has(filePath) && this.offsets.size >= LRU_CAP) {
|
|
1366
1407
|
this.evictOldestOffset();
|
|
1367
1408
|
}
|
|
1368
1409
|
let stat = null;
|
|
@@ -1370,12 +1411,24 @@ var TranscriptTailer = class extends import_events.EventEmitter {
|
|
|
1370
1411
|
stat = fs2.statSync(filePath);
|
|
1371
1412
|
} catch {
|
|
1372
1413
|
}
|
|
1414
|
+
const conversationId = this.extractConversationId(filePath);
|
|
1415
|
+
const baseline = conversationId ? this.nativeLaunchBaselines.get(conversationId) : void 0;
|
|
1416
|
+
let initialOffset = 0;
|
|
1417
|
+
if (this.admission && stat && baseline && baseline.dev === stat.dev && baseline.ino === stat.ino && stat.size >= baseline.size) {
|
|
1418
|
+
const boundary = (0, import_codevibe_core4.captureToolReplayBoundary)(filePath, baseline.size);
|
|
1419
|
+
if (boundary.fileIdentity.dev !== baseline.dev || boundary.fileIdentity.ino !== baseline.ino) {
|
|
1420
|
+
throw Error("SYNC_TOOL_INITIAL_SOURCE_CHANGED");
|
|
1421
|
+
}
|
|
1422
|
+
initialOffset = boundary.byteOffset;
|
|
1423
|
+
this.nativeInitialBoundaries.set(filePath, boundary);
|
|
1424
|
+
}
|
|
1373
1425
|
this.offsets.set(filePath, {
|
|
1374
|
-
byteOffset:
|
|
1426
|
+
byteOffset: initialOffset,
|
|
1375
1427
|
carryover: "",
|
|
1376
1428
|
lastKnownSize: stat?.size ?? 0,
|
|
1377
1429
|
lastModifiedMs: stat?.mtimeMs ?? Date.now()
|
|
1378
1430
|
});
|
|
1431
|
+
this.nativeSeededPaths.add(filePath);
|
|
1379
1432
|
}
|
|
1380
1433
|
/**
|
|
1381
1434
|
* Pick the oldest-mtime offset entry that is NOT for a currently-active
|
|
@@ -2293,7 +2346,9 @@ function promptMatchesCompletedTool(state, toolName, toolArgs) {
|
|
|
2293
2346
|
const promptPaths = /* @__PURE__ */ new Set();
|
|
2294
2347
|
const declared = normalizePath(state.pendingCall?.filePath);
|
|
2295
2348
|
if (declared) promptPaths.add(declared);
|
|
2296
|
-
|
|
2349
|
+
if (!isFileAccessPrompt(state) || !declared) {
|
|
2350
|
+
for (const link of fileLinks(state.paneDisplayHeader)) promptPaths.add(link);
|
|
2351
|
+
}
|
|
2297
2352
|
if (promptPaths.size === 0) return false;
|
|
2298
2353
|
for (const key of TOOL_PATH_KEYS) {
|
|
2299
2354
|
const toolPath = normalizePath(toolArgs[key]);
|
|
@@ -2553,8 +2608,24 @@ var TmuxPaneObserver = class _TmuxPaneObserver extends import_events2.EventEmitt
|
|
|
2553
2608
|
headerText = extractHeader(snapshot);
|
|
2554
2609
|
}
|
|
2555
2610
|
}
|
|
2556
|
-
|
|
2557
|
-
|
|
2611
|
+
let isChooser = hasPrompt && kind !== "system_error";
|
|
2612
|
+
let reportedKind = kind;
|
|
2613
|
+
let isAmbiguous = false;
|
|
2614
|
+
if (!isChooser && kind !== "system_error") {
|
|
2615
|
+
const safety = isPaneInputSafetyBlocked(snapshot);
|
|
2616
|
+
if (safety.blocked) {
|
|
2617
|
+
isChooser = true;
|
|
2618
|
+
reportedKind = safety.kind;
|
|
2619
|
+
isAmbiguous = true;
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
return {
|
|
2623
|
+
active: isChooser,
|
|
2624
|
+
headerText,
|
|
2625
|
+
probeSucceeded: true,
|
|
2626
|
+
kind: reportedKind,
|
|
2627
|
+
...isAmbiguous ? { ambiguous: true } : {}
|
|
2628
|
+
};
|
|
2558
2629
|
} catch {
|
|
2559
2630
|
return { active: false, headerText: null, probeSucceeded: false, kind: null };
|
|
2560
2631
|
}
|
|
@@ -2814,10 +2885,203 @@ var TmuxPaneObserver = class _TmuxPaneObserver extends import_events2.EventEmitt
|
|
|
2814
2885
|
}
|
|
2815
2886
|
}
|
|
2816
2887
|
};
|
|
2888
|
+
function isComposerBoxAt(lines, idx) {
|
|
2889
|
+
if (idx < 0 || idx >= lines.length) return false;
|
|
2890
|
+
const line0 = lines[idx].trim();
|
|
2891
|
+
if (!/^[─━│╭╰╮╯\s_-]{3,}$/.test(line0)) return false;
|
|
2892
|
+
let cursorIdx = -1;
|
|
2893
|
+
for (let k = idx + 1; k < Math.min(lines.length, idx + 4); k++) {
|
|
2894
|
+
const t = lines[k].trim();
|
|
2895
|
+
if (!t) continue;
|
|
2896
|
+
if (/^[│\s]*[❯>]\s*$/.test(t)) {
|
|
2897
|
+
cursorIdx = k;
|
|
2898
|
+
break;
|
|
2899
|
+
}
|
|
2900
|
+
break;
|
|
2901
|
+
}
|
|
2902
|
+
if (cursorIdx < 0) return false;
|
|
2903
|
+
for (let k = cursorIdx + 1; k < Math.min(lines.length, cursorIdx + 4); k++) {
|
|
2904
|
+
const t = lines[k].trim();
|
|
2905
|
+
if (!t) continue;
|
|
2906
|
+
if (/^[─━│╭╰╮╯\s_-]{3,}$/.test(t)) {
|
|
2907
|
+
return true;
|
|
2908
|
+
}
|
|
2909
|
+
break;
|
|
2910
|
+
}
|
|
2911
|
+
return false;
|
|
2912
|
+
}
|
|
2913
|
+
function isPaneInputSafetyBlocked(input) {
|
|
2914
|
+
const rawText = Array.isArray(input) ? input.join("\n") : input;
|
|
2915
|
+
if (!rawText) return { blocked: false, kind: null };
|
|
2916
|
+
const plainText = rawText.replace(ANSI_ESCAPE_REGEX, "").trimEnd();
|
|
2917
|
+
if (!plainText) return { blocked: false, kind: null };
|
|
2918
|
+
const lines = plainText.split(/\r?\n/);
|
|
2919
|
+
if (lines.length === 0) return { blocked: false, kind: null };
|
|
2920
|
+
const startIdx = Math.max(0, lines.length - 40);
|
|
2921
|
+
let footerIdx = -1;
|
|
2922
|
+
for (let i = lines.length - 1; i >= startIdx; i--) {
|
|
2923
|
+
const trimmed = lines[i].trim();
|
|
2924
|
+
if (!trimmed) continue;
|
|
2925
|
+
if (/^(`{3,}|~{3,})\s*$/.test(lines[i])) {
|
|
2926
|
+
break;
|
|
2927
|
+
}
|
|
2928
|
+
if (isQuestionFooterLine(trimmed)) {
|
|
2929
|
+
footerIdx = i;
|
|
2930
|
+
break;
|
|
2931
|
+
} else if (/\besc to cancel\b/i.test(trimmed) && !isAgyStatusFooterLine(trimmed)) {
|
|
2932
|
+
footerIdx = i;
|
|
2933
|
+
break;
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
if (footerIdx < 0) {
|
|
2937
|
+
const candidateHeaderIndices = [];
|
|
2938
|
+
for (let i = lines.length - 1; i >= startIdx; i--) {
|
|
2939
|
+
const trimmed = lines[i].trim();
|
|
2940
|
+
if (!trimmed) continue;
|
|
2941
|
+
if (isQuestionHeaderLine(trimmed) && /\((?:Use\s+arrow\s+keys|Use\s+\S+\s+keys)[^)]*\)/i.test(trimmed)) {
|
|
2942
|
+
candidateHeaderIndices.push(i);
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2945
|
+
for (const headerIdx of candidateHeaderIndices) {
|
|
2946
|
+
let optCount = 0;
|
|
2947
|
+
let cursorCount = 0;
|
|
2948
|
+
let lastOptionIdx = -1;
|
|
2949
|
+
let inInnerFence = false;
|
|
2950
|
+
let innerFenceChar = "";
|
|
2951
|
+
let innerFenceLen = 0;
|
|
2952
|
+
let composerEncountered = false;
|
|
2953
|
+
for (let j = headerIdx + 1; j < lines.length; j++) {
|
|
2954
|
+
const lineJ = lines[j];
|
|
2955
|
+
const trimmed = lineJ.trim();
|
|
2956
|
+
if (!trimmed) continue;
|
|
2957
|
+
const fenceMatch = lineJ.match(/^(`{3,}|~{3,})(.*)$/);
|
|
2958
|
+
if (fenceMatch) {
|
|
2959
|
+
const matchedFence = fenceMatch[1];
|
|
2960
|
+
const matchedChar = matchedFence[0];
|
|
2961
|
+
const matchedLen = matchedFence.length;
|
|
2962
|
+
const fenceInfo = fenceMatch[2].trim();
|
|
2963
|
+
if (inInnerFence) {
|
|
2964
|
+
if (matchedChar === innerFenceChar && matchedLen >= innerFenceLen && fenceInfo === "") {
|
|
2965
|
+
inInnerFence = false;
|
|
2966
|
+
innerFenceChar = "";
|
|
2967
|
+
innerFenceLen = 0;
|
|
2968
|
+
optCount = 0;
|
|
2969
|
+
cursorCount = 0;
|
|
2970
|
+
lastOptionIdx = -1;
|
|
2971
|
+
}
|
|
2972
|
+
continue;
|
|
2973
|
+
}
|
|
2974
|
+
inInnerFence = true;
|
|
2975
|
+
innerFenceChar = matchedChar;
|
|
2976
|
+
innerFenceLen = matchedLen;
|
|
2977
|
+
optCount = 0;
|
|
2978
|
+
cursorCount = 0;
|
|
2979
|
+
lastOptionIdx = -1;
|
|
2980
|
+
continue;
|
|
2981
|
+
}
|
|
2982
|
+
if (inInnerFence) {
|
|
2983
|
+
continue;
|
|
2984
|
+
}
|
|
2985
|
+
if (isAgyStatusFooterLine(trimmed) || /^\s*(?:[❯>]\s*)?\?\s+for\s+(?:shortcuts|help)\b/i.test(trimmed) || isComposerBoxAt(lines, j)) {
|
|
2986
|
+
composerEncountered = true;
|
|
2987
|
+
break;
|
|
2988
|
+
}
|
|
2989
|
+
if (/^[>\s❯]*[1-9][.)]\s+\S/.test(trimmed)) {
|
|
2990
|
+
if (/^[>\s❯]*1[.)]/.test(trimmed) && optCount > 0) {
|
|
2991
|
+
optCount = 0;
|
|
2992
|
+
cursorCount = 0;
|
|
2993
|
+
}
|
|
2994
|
+
optCount++;
|
|
2995
|
+
if (/^[>❯]/.test(trimmed)) {
|
|
2996
|
+
cursorCount++;
|
|
2997
|
+
}
|
|
2998
|
+
lastOptionIdx = j;
|
|
2999
|
+
continue;
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
const isValidOptionGroup = optCount >= 2 && cursorCount <= 1;
|
|
3003
|
+
if (!inInnerFence && !composerEncountered && isValidOptionGroup && lastOptionIdx >= 0 && !hasTrailingAgyComposer(lines, lastOptionIdx)) {
|
|
3004
|
+
return { blocked: true, kind: "question" };
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
return { blocked: false, kind: null };
|
|
3008
|
+
}
|
|
3009
|
+
const candidateHeaders = [];
|
|
3010
|
+
for (let i = footerIdx - 1; i >= startIdx; i--) {
|
|
3011
|
+
const trimmed = lines[i].trim();
|
|
3012
|
+
if (!trimmed) continue;
|
|
3013
|
+
if (isQuestionHeaderLine(trimmed)) {
|
|
3014
|
+
candidateHeaders.push({ lineIdx: i, kind: "question" });
|
|
3015
|
+
} else if (/Allow access to this file\?/i.test(trimmed)) {
|
|
3016
|
+
candidateHeaders.push({ lineIdx: i, kind: "file_access" });
|
|
3017
|
+
} else if (/Requesting permission for:/i.test(trimmed) || /Do you want to (?:proceed|run|execute)/i.test(trimmed)) {
|
|
3018
|
+
candidateHeaders.push({ lineIdx: i, kind: "approval" });
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
for (const candidate of candidateHeaders) {
|
|
3022
|
+
let optCount = 0;
|
|
3023
|
+
let cursorCount = 0;
|
|
3024
|
+
let inInnerFence = false;
|
|
3025
|
+
let innerFenceChar = "";
|
|
3026
|
+
let innerFenceLen = 0;
|
|
3027
|
+
let composerEncountered = false;
|
|
3028
|
+
for (let j = candidate.lineIdx + 1; j < footerIdx; j++) {
|
|
3029
|
+
const lineJ = lines[j];
|
|
3030
|
+
const trimmed = lineJ.trim();
|
|
3031
|
+
if (!trimmed) continue;
|
|
3032
|
+
const fenceMatch = lineJ.match(/^(`{3,}|~{3,})(.*)$/);
|
|
3033
|
+
if (fenceMatch) {
|
|
3034
|
+
const marker = fenceMatch[1];
|
|
3035
|
+
const rest = fenceMatch[2].trim();
|
|
3036
|
+
const fChar = marker[0];
|
|
3037
|
+
const fLen = marker.length;
|
|
3038
|
+
if (inInnerFence) {
|
|
3039
|
+
if (fChar === innerFenceChar && fLen >= innerFenceLen && rest === "") {
|
|
3040
|
+
inInnerFence = false;
|
|
3041
|
+
innerFenceChar = "";
|
|
3042
|
+
innerFenceLen = 0;
|
|
3043
|
+
optCount = 0;
|
|
3044
|
+
cursorCount = 0;
|
|
3045
|
+
}
|
|
3046
|
+
continue;
|
|
3047
|
+
}
|
|
3048
|
+
inInnerFence = true;
|
|
3049
|
+
innerFenceChar = fChar;
|
|
3050
|
+
innerFenceLen = fLen;
|
|
3051
|
+
optCount = 0;
|
|
3052
|
+
cursorCount = 0;
|
|
3053
|
+
continue;
|
|
3054
|
+
}
|
|
3055
|
+
if (inInnerFence) {
|
|
3056
|
+
continue;
|
|
3057
|
+
}
|
|
3058
|
+
if (isAgyStatusFooterLine(trimmed) || /^\s*(?:[❯>]\s*)?\?\s+for\s+(?:shortcuts|help)\b/i.test(trimmed) || isComposerBoxAt(lines, j)) {
|
|
3059
|
+
composerEncountered = true;
|
|
3060
|
+
break;
|
|
3061
|
+
}
|
|
3062
|
+
if (/^[>\s❯]*[1-9][.)]\s+\S/.test(trimmed)) {
|
|
3063
|
+
if (/^[>\s❯]*1[.)]/.test(trimmed) && optCount > 0) {
|
|
3064
|
+
optCount = 0;
|
|
3065
|
+
cursorCount = 0;
|
|
3066
|
+
}
|
|
3067
|
+
optCount++;
|
|
3068
|
+
if (/^[>❯]/.test(trimmed)) {
|
|
3069
|
+
cursorCount++;
|
|
3070
|
+
}
|
|
3071
|
+
continue;
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
const isValidOptionGroup = optCount >= 2 && cursorCount <= 1;
|
|
3075
|
+
if (!inInnerFence && !composerEncountered && isValidOptionGroup && !hasTrailingAgyComposer(lines, footerIdx)) {
|
|
3076
|
+
return { blocked: true, kind: candidate.kind };
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
return { blocked: false, kind: null };
|
|
3080
|
+
}
|
|
2817
3081
|
function looksLikePromptDelta(chunk) {
|
|
2818
3082
|
return (
|
|
2819
3083
|
// Approval UI signatures
|
|
2820
|
-
/Requesting permission for:/i.test(chunk) || /Do you want to proceed\?/i.test(chunk) || /\besc to cancel\b/i.test(chunk) || /^\s*[1-9]\. (?:Yes|No)\b/im.test(chunk) || /\[(?:y\/n|Y\/n|y\/N)\]/.test(chunk) || /Question
|
|
3084
|
+
/Requesting permission for:/i.test(chunk) || /Do you want to proceed\?/i.test(chunk) || /\besc to cancel\b/i.test(chunk) || /^\s*[1-9]\. (?:Yes|No)\b/im.test(chunk) || /\[(?:y\/n|Y\/n|y\/N)\]/.test(chunk) || /^\s*(?:[❯>]\s*)?\?\s+(?!for\s+(?:shortcuts|help|commands))\S/im.test(chunk) || /Question\b(?:\s+\d+(?:[\/:of\s]+\d+)?)?:/i.test(chunk) || /\benter\s+(?:to\s+)?select\b/i.test(chunk) || /\b(?:navigate|use\s+arrow\s+keys)\b/i.test(chunk) || /^File access\s*$/im.test(chunk) || /^\s*Allow access to this file\?\s*$/im.test(chunk) || /(?:Yes, allow access|No, deny access)/i.test(chunk) || /(?:↑\/↓\s+Navigate|ctrl\+r\s+Review)/i.test(chunk) || /Our servers are experiencing/i.test(chunk) || /Error ID:\s*[0-9a-fA-F-]*/i.test(chunk) || /(?:⚠️|⚠\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(chunk)
|
|
2821
3085
|
);
|
|
2822
3086
|
}
|
|
2823
3087
|
function isNonSystemErrorWarningLine(trimmed) {
|
|
@@ -2886,7 +3150,13 @@ function isComposedStatusFooter(trimmed) {
|
|
|
2886
3150
|
}
|
|
2887
3151
|
function isAgyStatusFooterLine(trimmed) {
|
|
2888
3152
|
if (!trimmed) return false;
|
|
2889
|
-
if (
|
|
3153
|
+
if (/(?:\benter|<enter>)\s+(?:to\s+)?select\b/i.test(trimmed) || /\bNavigate\b/i.test(trimmed)) {
|
|
3154
|
+
return false;
|
|
3155
|
+
}
|
|
3156
|
+
if (/^\s*(?:[❯>]\s*)?\?\s+for shortcuts\b/i.test(trimmed)) {
|
|
3157
|
+
return true;
|
|
3158
|
+
}
|
|
3159
|
+
if (/^\s*esc to cancel\b/i.test(trimmed)) {
|
|
2890
3160
|
return true;
|
|
2891
3161
|
}
|
|
2892
3162
|
if (/^(?:\(initializing\.\.\.\)|\d+\s+task\(s\)\s+·\s+\/tasks|\d+\s+subagent\s*\(s\)|AI:\s+(?:\d+\s+\(\/credits to add more\)|Out of credits)|AI Credits:\s+\d+|Quota:\s*\d+(?:\.\d+)?%)$/i.test(trimmed)) {
|
|
@@ -2897,6 +3167,68 @@ function isAgyStatusFooterLine(trimmed) {
|
|
|
2897
3167
|
}
|
|
2898
3168
|
return false;
|
|
2899
3169
|
}
|
|
3170
|
+
function computeFenceMask(lines) {
|
|
3171
|
+
const inFence = new Array(lines.length).fill(false);
|
|
3172
|
+
let inside = false;
|
|
3173
|
+
let activeFenceChar = "";
|
|
3174
|
+
let activeFenceLen = 0;
|
|
3175
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3176
|
+
const line = lines[i];
|
|
3177
|
+
const match = line.match(/^(`{3,}|~{3,})(.*)$/);
|
|
3178
|
+
if (match) {
|
|
3179
|
+
const marker = match[1];
|
|
3180
|
+
const rest = match[2];
|
|
3181
|
+
const fenceChar = marker[0];
|
|
3182
|
+
const fenceLen = marker.length;
|
|
3183
|
+
if (!inside) {
|
|
3184
|
+
inside = true;
|
|
3185
|
+
activeFenceChar = fenceChar;
|
|
3186
|
+
activeFenceLen = fenceLen;
|
|
3187
|
+
inFence[i] = true;
|
|
3188
|
+
continue;
|
|
3189
|
+
} else {
|
|
3190
|
+
if (fenceChar === activeFenceChar && fenceLen >= activeFenceLen && rest.trim() === "") {
|
|
3191
|
+
inside = false;
|
|
3192
|
+
activeFenceChar = "";
|
|
3193
|
+
activeFenceLen = 0;
|
|
3194
|
+
inFence[i] = true;
|
|
3195
|
+
continue;
|
|
3196
|
+
}
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
inFence[i] = inside;
|
|
3200
|
+
}
|
|
3201
|
+
return inFence;
|
|
3202
|
+
}
|
|
3203
|
+
function hasTrailingAgyComposer(lines, afterLineIdx, fenceMask) {
|
|
3204
|
+
if (afterLineIdx < -1 || afterLineIdx >= lines.length - 1) return false;
|
|
3205
|
+
const trailingLines = [];
|
|
3206
|
+
for (let i = afterLineIdx + 1; i < lines.length; i++) {
|
|
3207
|
+
if (fenceMask && fenceMask[i]) continue;
|
|
3208
|
+
const trimmed = lines[i].trim();
|
|
3209
|
+
if (trimmed.length > 0) {
|
|
3210
|
+
trailingLines.push(trimmed);
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
if (trailingLines.length === 0) return false;
|
|
3214
|
+
if (trailingLines.some((l) => /^\s*(?:[❯>]\s*)?\?\s+for\s+(?:shortcuts|help)\b/i.test(l))) {
|
|
3215
|
+
return true;
|
|
3216
|
+
}
|
|
3217
|
+
for (let i = 0; i < trailingLines.length; i++) {
|
|
3218
|
+
const l = trailingLines[i];
|
|
3219
|
+
if (/^[─━│╭╰╮╯\s_-]{3,}$/.test(l)) {
|
|
3220
|
+
if (i + 1 < trailingLines.length && /^[│\s]*[❯>]\s*$/.test(trailingLines[i + 1])) {
|
|
3221
|
+
if (i + 2 < trailingLines.length && /^[─━│╭╰╮╯\s_-]{3,}$/.test(trailingLines[i + 2])) {
|
|
3222
|
+
return true;
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3227
|
+
if (trailingLines.some((l) => isAgyStatusFooterLine(l) && /(?:Gemini|Flash|Pro|Claude|Sonnet|Opus|GPT|auto|high|low|medium)/i.test(l))) {
|
|
3228
|
+
return true;
|
|
3229
|
+
}
|
|
3230
|
+
return false;
|
|
3231
|
+
}
|
|
2900
3232
|
function reconstructFooter(rawLines) {
|
|
2901
3233
|
const cleaned = rawLines.map((l) => l.trim()).filter(Boolean);
|
|
2902
3234
|
if (cleaned.length === 0) return "";
|
|
@@ -3048,17 +3380,62 @@ function looksLikePromptSnapshot(snapshot) {
|
|
|
3048
3380
|
return hasApprovalHeader && hasApprovalOptions && hasApprovalFooter;
|
|
3049
3381
|
}
|
|
3050
3382
|
if (activeKind === "question") {
|
|
3051
|
-
const
|
|
3052
|
-
const
|
|
3053
|
-
const
|
|
3054
|
-
|
|
3383
|
+
const allLines = stripped.trimEnd().split("\n");
|
|
3384
|
+
const fenceMask = computeFenceMask(allLines);
|
|
3385
|
+
const windowStart = Math.max(0, allLines.length - 30);
|
|
3386
|
+
let headerIdx = -1;
|
|
3387
|
+
for (let i = allLines.length - 1; i >= windowStart; i--) {
|
|
3388
|
+
if (!fenceMask[i] && isQuestionHeaderLine(allLines[i])) {
|
|
3389
|
+
headerIdx = i;
|
|
3390
|
+
break;
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
if (headerIdx < 0) return false;
|
|
3394
|
+
const headerLine = allLines[headerIdx].trim();
|
|
3395
|
+
const optionIndices = [];
|
|
3396
|
+
for (let i = headerIdx + 1; i < allLines.length; i++) {
|
|
3397
|
+
if (!fenceMask[i] && /^[>\s❯]*[1-9][.)]\s+\S/.test(allLines[i].trim())) {
|
|
3398
|
+
optionIndices.push(i);
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
if (optionIndices.length < 2) return false;
|
|
3402
|
+
const lastOptionIdx = optionIndices[optionIndices.length - 1];
|
|
3403
|
+
const hasHeaderHint = /\((?:Use\s+arrow\s+keys|Use\s+\S+\s+keys)[^)]*\)/i.test(headerLine);
|
|
3404
|
+
let hasQuestionFooter = hasHeaderHint;
|
|
3405
|
+
let footerIdx = lastOptionIdx;
|
|
3406
|
+
if (!hasQuestionFooter) {
|
|
3407
|
+
for (let i = lastOptionIdx + 1; i < allLines.length; i++) {
|
|
3408
|
+
if (!fenceMask[i] && isQuestionFooterLine(allLines[i].trim())) {
|
|
3409
|
+
hasQuestionFooter = true;
|
|
3410
|
+
footerIdx = i;
|
|
3411
|
+
break;
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
if (!hasQuestionFooter) return false;
|
|
3416
|
+
if (hasTrailingAgyComposer(allLines, footerIdx, fenceMask)) {
|
|
3417
|
+
return false;
|
|
3418
|
+
}
|
|
3419
|
+
return true;
|
|
3055
3420
|
}
|
|
3056
3421
|
if (activeKind === "file_access") {
|
|
3057
|
-
const hasFileAccessHeader = /^File access\s*$/im.test(recent);
|
|
3058
|
-
const
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3422
|
+
const hasFileAccessHeader = /^File access\s*$/im.test(recent) || /^\s*(?:Read|Write|Edit|Access):\s*\S/im.test(recent);
|
|
3423
|
+
const questionMatches = Array.from(recent.matchAll(/^\s*Allow access to this file\?\s*$/gim));
|
|
3424
|
+
if (!hasFileAccessHeader || questionMatches.length === 0) {
|
|
3425
|
+
return false;
|
|
3426
|
+
}
|
|
3427
|
+
const lastQuestionMatch = questionMatches[questionMatches.length - 1];
|
|
3428
|
+
if (lastQuestionMatch.index === void 0) {
|
|
3429
|
+
return false;
|
|
3430
|
+
}
|
|
3431
|
+
const belowQuestion = recent.slice(lastQuestionMatch.index);
|
|
3432
|
+
if (/\?\s+for shortcuts/i.test(belowQuestion)) {
|
|
3433
|
+
return false;
|
|
3434
|
+
}
|
|
3435
|
+
const hasApprovalOptions = /^[>\s]*[1-9]\. (?:Yes|No)\b/im.test(belowQuestion);
|
|
3436
|
+
const hasApprovalFooter = /^\s*(?:esc to cancel\b|[↑↓/\s]*Navigate\b|ctrl\+r\s+Review\b)/im.test(belowQuestion);
|
|
3437
|
+
const hasQuestionFooter = /\benter\s+Select\b/i.test(belowQuestion);
|
|
3438
|
+
return hasApprovalOptions && hasApprovalFooter && !hasQuestionFooter;
|
|
3062
3439
|
}
|
|
3063
3440
|
return true;
|
|
3064
3441
|
}
|
|
@@ -3069,21 +3446,62 @@ function looksLikePromptSnapshot(snapshot) {
|
|
|
3069
3446
|
function extractFileAccessPrompt(snapshot) {
|
|
3070
3447
|
const stripped = snapshot.replace(ANSI_ESCAPE_REGEX, "");
|
|
3071
3448
|
const lines = stripped.split("\n");
|
|
3072
|
-
let
|
|
3449
|
+
let questionIndex = -1;
|
|
3073
3450
|
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
3074
|
-
if (/^\s*
|
|
3075
|
-
|
|
3451
|
+
if (/^\s*Allow access to this file\?\s*$/i.test(lines[index])) {
|
|
3452
|
+
questionIndex = index;
|
|
3076
3453
|
break;
|
|
3077
3454
|
}
|
|
3078
3455
|
}
|
|
3079
|
-
if (headingIndex < 0) return null;
|
|
3080
|
-
const block = lines.slice(headingIndex, headingIndex + 20);
|
|
3081
|
-
const questionIndex = block.findIndex((line) => /Allow access to this file\?/i.test(line));
|
|
3082
3456
|
if (questionIndex < 0) return null;
|
|
3083
|
-
const
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3457
|
+
const scanStart = Math.max(0, questionIndex - 15);
|
|
3458
|
+
let headingIndex = -1;
|
|
3459
|
+
let actionIndex = -1;
|
|
3460
|
+
let actionLine = null;
|
|
3461
|
+
let targetPath = null;
|
|
3462
|
+
for (let index = questionIndex - 1; index >= scanStart; index -= 1) {
|
|
3463
|
+
const line = lines[index].trim();
|
|
3464
|
+
if (!line) continue;
|
|
3465
|
+
const actionMatch = line.match(/^\s*((?:Read|Write|Edit|Access):\s*(\S.*))$/i);
|
|
3466
|
+
if (actionMatch && actionIndex < 0) {
|
|
3467
|
+
actionIndex = index;
|
|
3468
|
+
actionLine = actionMatch[1].trim();
|
|
3469
|
+
targetPath = actionMatch[2].trim();
|
|
3470
|
+
for (let prevIdx = index - 1; prevIdx >= scanStart; prevIdx -= 1) {
|
|
3471
|
+
const prevLine = lines[prevIdx].trim();
|
|
3472
|
+
if (!prevLine) continue;
|
|
3473
|
+
if (/^\s*File access\s*$/i.test(prevLine)) {
|
|
3474
|
+
headingIndex = prevIdx;
|
|
3475
|
+
}
|
|
3476
|
+
break;
|
|
3477
|
+
}
|
|
3478
|
+
break;
|
|
3479
|
+
}
|
|
3480
|
+
if (/^\s*Reason:\s*\S.*/i.test(line) && actionIndex < 0) {
|
|
3481
|
+
continue;
|
|
3482
|
+
}
|
|
3483
|
+
if (/^[⚠⚠️]\s*/.test(line) && actionIndex < 0) {
|
|
3484
|
+
continue;
|
|
3485
|
+
}
|
|
3486
|
+
if (/^\s*File access\s*$/i.test(line)) {
|
|
3487
|
+
headingIndex = index;
|
|
3488
|
+
break;
|
|
3489
|
+
}
|
|
3490
|
+
if (/^[─━│╭╰╮╯\s_-]*$/.test(line) || /^[│\s]*[❯>]\s+\S/.test(line) || /^\s*(?:Requesting permission for:|Question \d+\/\d+:|Allow access to this file\?|Do you want to proceed\?)/i.test(line) || /^[▶▸\s]*Thought|^●\s*/i.test(line) || /^\s*(?:\?\s+for shortcuts|esc to cancel)\b/i.test(line) || /^\s*(?:[↑↓/\s]*Navigate\b|ctrl\+r\s+Review\b)/i.test(line)) {
|
|
3491
|
+
break;
|
|
3492
|
+
}
|
|
3493
|
+
break;
|
|
3494
|
+
}
|
|
3495
|
+
const identity = targetPath || "non-workspace file access";
|
|
3496
|
+
const bodyStart = headingIndex >= 0 ? headingIndex + 1 : actionIndex >= 0 ? actionIndex : questionIndex;
|
|
3497
|
+
const contextLines = lines.slice(bodyStart, questionIndex).map((line) => line.trim()).filter((line) => {
|
|
3498
|
+
if (!line) return false;
|
|
3499
|
+
if (/^\s*File access\s*$/i.test(line)) return false;
|
|
3500
|
+
if (actionIndex < 0 && /^\s*Reason:\s*/i.test(line)) return false;
|
|
3501
|
+
return true;
|
|
3502
|
+
});
|
|
3503
|
+
const body = contextLines.join("\n").trim();
|
|
3504
|
+
const fullLine = actionLine ? `Allow access to this file? (${actionLine})` : lines[questionIndex].trim();
|
|
3087
3505
|
return { identity, fullLine, body };
|
|
3088
3506
|
}
|
|
3089
3507
|
function extractHeader(snapshot) {
|
|
@@ -3098,13 +3516,177 @@ function extractFullHeaderLine(snapshot) {
|
|
|
3098
3516
|
if (matches.length === 0) return null;
|
|
3099
3517
|
return matches[matches.length - 1][0].trim();
|
|
3100
3518
|
}
|
|
3519
|
+
function isOpenEnclosingContext(lines, checkTop, topAnchor, boundaryIdx) {
|
|
3520
|
+
let activeFence = null;
|
|
3521
|
+
for (let k = checkTop; k < topAnchor; k++) {
|
|
3522
|
+
const raw = lines[k];
|
|
3523
|
+
if (!activeFence) {
|
|
3524
|
+
const openMatch = raw.match(/^ {0,3}(`{3,}|~{3,})(.*)$/);
|
|
3525
|
+
if (openMatch) {
|
|
3526
|
+
const fenceStr = openMatch[1];
|
|
3527
|
+
const rest = openMatch[2];
|
|
3528
|
+
const char = fenceStr[0];
|
|
3529
|
+
if (char === "`" && rest.includes("`")) {
|
|
3530
|
+
} else {
|
|
3531
|
+
activeFence = { char, length: fenceStr.length };
|
|
3532
|
+
}
|
|
3533
|
+
}
|
|
3534
|
+
} else {
|
|
3535
|
+
const closeMatch = raw.match(/^ {0,3}(`{3,}|~{3,})\s*$/);
|
|
3536
|
+
if (closeMatch) {
|
|
3537
|
+
const fenceStr = closeMatch[1];
|
|
3538
|
+
if (fenceStr[0] === activeFence.char && fenceStr.length >= activeFence.length) {
|
|
3539
|
+
activeFence = null;
|
|
3540
|
+
}
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
if (activeFence !== null) {
|
|
3545
|
+
return true;
|
|
3546
|
+
}
|
|
3547
|
+
for (let k = boundaryIdx - 1; k >= checkTop; k--) {
|
|
3548
|
+
const l = lines[k].trim();
|
|
3549
|
+
if (!l) continue;
|
|
3550
|
+
if (/Requesting permission for:/i.test(l) || /Do you want to proceed\?/i.test(l) || /Question \d+\/\d+:/i.test(l)) {
|
|
3551
|
+
const sliceBetween = lines.slice(k + 1, boundaryIdx);
|
|
3552
|
+
let optCount = 0;
|
|
3553
|
+
let hasFooter = false;
|
|
3554
|
+
let inDiff = false;
|
|
3555
|
+
for (const raw of sliceBetween) {
|
|
3556
|
+
const s = raw.trim();
|
|
3557
|
+
if (!s) continue;
|
|
3558
|
+
if (inDiff) {
|
|
3559
|
+
if (/^[ +\-\\]/.test(raw)) {
|
|
3560
|
+
continue;
|
|
3561
|
+
}
|
|
3562
|
+
if (/^(?:diff\s+--git\b|@@\s+-\d+|---\s+(?:[ab]\/|\/dev\/null)|\+\+\+\s+(?:[ab]\/|\/dev\/null)|index\s+[0-9a-f]{7,})/i.test(s)) {
|
|
3563
|
+
continue;
|
|
3564
|
+
}
|
|
3565
|
+
if (/^●\s+(?:Edit completed|[A-Z])/i.test(raw)) {
|
|
3566
|
+
inDiff = false;
|
|
3567
|
+
continue;
|
|
3568
|
+
}
|
|
3569
|
+
if (/^Do you want to proceed\?/i.test(raw)) {
|
|
3570
|
+
inDiff = false;
|
|
3571
|
+
continue;
|
|
3572
|
+
}
|
|
3573
|
+
inDiff = false;
|
|
3574
|
+
}
|
|
3575
|
+
if (/^(?:diff\s+--git\b|@@\s+-\d+|---\s+(?:[ab]\/|\/dev\/null)|\+\+\+\s+(?:[ab]\/|\/dev\/null)|index\s+[0-9a-f]{7,})/i.test(s)) {
|
|
3576
|
+
inDiff = true;
|
|
3577
|
+
continue;
|
|
3578
|
+
}
|
|
3579
|
+
if (/^●\s+(?:Edit completed|[A-Z])/i.test(raw)) {
|
|
3580
|
+
continue;
|
|
3581
|
+
}
|
|
3582
|
+
if (/^[>\s]*[1-9]\.\s+\S/.test(s)) optCount++;
|
|
3583
|
+
if (/esc to cancel\b|enter Select\b|[↑↓/\s]*Navigate\b/i.test(s)) hasFooter = true;
|
|
3584
|
+
}
|
|
3585
|
+
if (optCount < 2 || !hasFooter) {
|
|
3586
|
+
return true;
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3589
|
+
}
|
|
3590
|
+
return false;
|
|
3591
|
+
}
|
|
3592
|
+
function isStandaloneFileAccessContext(lines, questionIdx) {
|
|
3593
|
+
const windowEnd = Math.min(lines.length, questionIdx + 15);
|
|
3594
|
+
let hasFileAccessOption = false;
|
|
3595
|
+
for (let j = questionIdx + 1; j < windowEnd; j++) {
|
|
3596
|
+
const l = lines[j].trim();
|
|
3597
|
+
if (!l) continue;
|
|
3598
|
+
if (/\benter\s+Select\b/i.test(l) || /\benter\s+to\s+(?:select|submit)\b/i.test(l)) {
|
|
3599
|
+
return false;
|
|
3600
|
+
}
|
|
3601
|
+
if (/Do you want to proceed\?/i.test(l) || /Requesting permission for:/i.test(l)) {
|
|
3602
|
+
return false;
|
|
3603
|
+
}
|
|
3604
|
+
if (/\b(?:allow|deny)\s+access\b/i.test(l)) {
|
|
3605
|
+
hasFileAccessOption = true;
|
|
3606
|
+
}
|
|
3607
|
+
if (/^[─━│╭╰╮╯\s]*$/.test(l) && l.length > 5) {
|
|
3608
|
+
break;
|
|
3609
|
+
}
|
|
3610
|
+
if (/^[▶▸\s]*Thought|^●\s*/i.test(l) || /^[│\s]*[❯>]\s*$/.test(l)) {
|
|
3611
|
+
break;
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
const start = Math.max(0, questionIdx - 15);
|
|
3615
|
+
let actionIdx = -1;
|
|
3616
|
+
let headingIdx = -1;
|
|
3617
|
+
for (let j = questionIdx - 1; j >= start; j--) {
|
|
3618
|
+
const l = lines[j].trim();
|
|
3619
|
+
if (!l) continue;
|
|
3620
|
+
if (/^(?:diff\s+--git|index\s+[0-9a-f]{7,}|---\s+(?:[ab]\/|\/dev\/null)|\+\+\+\s+(?:[ab]\/|\/dev\/null)|@@\s+-\d+)/i.test(l) || /^ {0,3}(`{3,}|~{3,})(?:[a-z0-9_+-]+)?\s*$/i.test(l) || /Requesting permission for:/i.test(l) || /Do you want to proceed\?/i.test(l) || /Question \d+\/\d+:/i.test(l)) {
|
|
3621
|
+
return false;
|
|
3622
|
+
}
|
|
3623
|
+
if (/^[─━│╭╰╮╯\s]*$/.test(l) && l.length > 5) {
|
|
3624
|
+
break;
|
|
3625
|
+
}
|
|
3626
|
+
if (/^[│\s]*[❯>]\s*$/.test(l)) {
|
|
3627
|
+
break;
|
|
3628
|
+
}
|
|
3629
|
+
if (/^\s*(?:Read|Write|Edit|Access):\s*\S/i.test(l)) {
|
|
3630
|
+
actionIdx = j;
|
|
3631
|
+
for (let k = j - 1; k >= start; k--) {
|
|
3632
|
+
const prev = lines[k].trim();
|
|
3633
|
+
if (!prev) continue;
|
|
3634
|
+
if (/^\s*File access\s*$/i.test(prev)) {
|
|
3635
|
+
headingIdx = k;
|
|
3636
|
+
}
|
|
3637
|
+
break;
|
|
3638
|
+
}
|
|
3639
|
+
break;
|
|
3640
|
+
}
|
|
3641
|
+
if (/^\s*Reason:\s*\S/i.test(l)) {
|
|
3642
|
+
continue;
|
|
3643
|
+
}
|
|
3644
|
+
if (/^[⚠⚠️]\s*/.test(l)) {
|
|
3645
|
+
continue;
|
|
3646
|
+
}
|
|
3647
|
+
if (/^\s*File access\s*$/i.test(l)) {
|
|
3648
|
+
headingIdx = j;
|
|
3649
|
+
break;
|
|
3650
|
+
}
|
|
3651
|
+
return false;
|
|
3652
|
+
}
|
|
3653
|
+
if (actionIdx < 0 && headingIdx < 0) {
|
|
3654
|
+
return false;
|
|
3655
|
+
}
|
|
3656
|
+
if (headingIdx < 0 && !hasFileAccessOption) {
|
|
3657
|
+
return false;
|
|
3658
|
+
}
|
|
3659
|
+
const topAnchor = headingIdx >= 0 ? headingIdx : actionIdx;
|
|
3660
|
+
const checkTop = Math.max(0, topAnchor - 25);
|
|
3661
|
+
for (let j = topAnchor - 1; j >= checkTop; j--) {
|
|
3662
|
+
const l = lines[j].trim();
|
|
3663
|
+
if (!l) continue;
|
|
3664
|
+
if (/Question \d+\/\d+:/i.test(l)) {
|
|
3665
|
+
return false;
|
|
3666
|
+
}
|
|
3667
|
+
if (/Requesting permission for:/i.test(l) || /Do you want to proceed\?/i.test(l)) {
|
|
3668
|
+
return false;
|
|
3669
|
+
}
|
|
3670
|
+
if (/^(?:diff\s+--git|index\s+[0-9a-f]{7,}|---\s+(?:[ab]\/|\/dev\/null)|\+\+\+\s+(?:[ab]\/|\/dev\/null)|@@\s+-\d+)/i.test(l) || /^ {0,3}(`{3,}|~{3,})(?:[a-z0-9_+-]+)?\s*$/i.test(l)) {
|
|
3671
|
+
return false;
|
|
3672
|
+
}
|
|
3673
|
+
if (/^[▶▸\s]*Thought|^●\s*/i.test(l) || /^[│\s]*[❯>]\s*$/.test(l) || /^[─━│╭╰╮╯\s]*$/.test(l) && l.length > 5) {
|
|
3674
|
+
if (isOpenEnclosingContext(lines, checkTop, topAnchor, j)) {
|
|
3675
|
+
return false;
|
|
3676
|
+
}
|
|
3677
|
+
break;
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
return true;
|
|
3681
|
+
}
|
|
3101
3682
|
function detectActivePromptKind(snapshot) {
|
|
3102
3683
|
const stripped = snapshot.replace(ANSI_ESCAPE_REGEX, "");
|
|
3103
3684
|
const lines = stripped.trimEnd().split("\n");
|
|
3104
3685
|
const windowStart = Math.max(0, lines.length - 30);
|
|
3105
3686
|
const approvalRe = /Requesting permission for:/i;
|
|
3106
|
-
const questionRe = /Question \d+\/\d+:/i;
|
|
3107
3687
|
const fileAccessRe = /^\s*File access\s*$/i;
|
|
3688
|
+
const fileAccessQuestionRe = /^\s*Allow access to this file\?\s*$/i;
|
|
3689
|
+
const lineInFence = computeFenceMask(lines);
|
|
3108
3690
|
let lastApprovalLineIdx = -1;
|
|
3109
3691
|
let lastQuestionLineIdx = -1;
|
|
3110
3692
|
let lastFileAccessLineIdx = -1;
|
|
@@ -3112,14 +3694,22 @@ function detectActivePromptKind(snapshot) {
|
|
|
3112
3694
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
3113
3695
|
const line = lines[i];
|
|
3114
3696
|
if (i >= windowStart) {
|
|
3115
|
-
if (
|
|
3116
|
-
lastApprovalLineIdx
|
|
3697
|
+
if (approvalRe.test(line)) {
|
|
3698
|
+
if (!lineInFence[i] && lastApprovalLineIdx < 0) {
|
|
3699
|
+
lastApprovalLineIdx = i;
|
|
3700
|
+
}
|
|
3117
3701
|
}
|
|
3118
|
-
if (
|
|
3119
|
-
lastQuestionLineIdx
|
|
3702
|
+
if (isQuestionHeaderLine(line)) {
|
|
3703
|
+
if (!lineInFence[i] && lastQuestionLineIdx < 0) {
|
|
3704
|
+
lastQuestionLineIdx = i;
|
|
3705
|
+
}
|
|
3120
3706
|
}
|
|
3121
|
-
if (lastFileAccessLineIdx < 0 &&
|
|
3122
|
-
|
|
3707
|
+
if (lastFileAccessLineIdx < 0 && !lineInFence[i]) {
|
|
3708
|
+
if (fileAccessRe.test(line)) {
|
|
3709
|
+
lastFileAccessLineIdx = i;
|
|
3710
|
+
} else if (fileAccessQuestionRe.test(line) && isStandaloneFileAccessContext(lines, i)) {
|
|
3711
|
+
lastFileAccessLineIdx = i;
|
|
3712
|
+
}
|
|
3123
3713
|
}
|
|
3124
3714
|
}
|
|
3125
3715
|
if (lastSystemErrorLineIdx < 0 && isSystemErrorBannerLineInContext(lines, i)) {
|
|
@@ -3130,12 +3720,14 @@ function detectActivePromptKind(snapshot) {
|
|
|
3130
3720
|
if (lastApprovalLineIdx < 0 && lastQuestionLineIdx < 0) {
|
|
3131
3721
|
for (let i = lines.length - 1; i >= windowStart; i--) {
|
|
3132
3722
|
if (/Do you want to proceed\?/i.test(lines[i])) {
|
|
3133
|
-
|
|
3134
|
-
|
|
3723
|
+
if (!lineInFence[i]) {
|
|
3724
|
+
lastApprovalLineIdx = i;
|
|
3725
|
+
break;
|
|
3726
|
+
}
|
|
3135
3727
|
}
|
|
3136
3728
|
}
|
|
3137
3729
|
}
|
|
3138
|
-
|
|
3730
|
+
let latestChooserIdx = Math.max(
|
|
3139
3731
|
lastApprovalLineIdx,
|
|
3140
3732
|
lastQuestionLineIdx,
|
|
3141
3733
|
lastFileAccessLineIdx
|
|
@@ -3144,15 +3736,32 @@ function detectActivePromptKind(snapshot) {
|
|
|
3144
3736
|
if (latestChooserIdx >= 0) {
|
|
3145
3737
|
const remainingLines = lines.slice(lastSystemErrorLineIdx + 1).join("\n");
|
|
3146
3738
|
if (latestChooserIdx === lastQuestionLineIdx) {
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3739
|
+
let hasQuestionOptions = false;
|
|
3740
|
+
let hasQuestionFooter = false;
|
|
3741
|
+
let promptEndIdx = lastSystemErrorLineIdx;
|
|
3742
|
+
for (let j = lastSystemErrorLineIdx + 1; j < lines.length; j++) {
|
|
3743
|
+
if (!lineInFence[j]) {
|
|
3744
|
+
const trimmed = lines[j].trim();
|
|
3745
|
+
if (/^[>\s❯]*[1-9][.)]\s+\S/.test(trimmed)) {
|
|
3746
|
+
hasQuestionOptions = true;
|
|
3747
|
+
promptEndIdx = j;
|
|
3748
|
+
} else if (isQuestionFooterLine(lines[j])) {
|
|
3749
|
+
hasQuestionFooter = true;
|
|
3750
|
+
promptEndIdx = j;
|
|
3751
|
+
}
|
|
3752
|
+
}
|
|
3753
|
+
}
|
|
3754
|
+
const headerLine = lines[lastQuestionLineIdx].trim();
|
|
3755
|
+
const hasHeaderHint = /\((?:Use\s+arrow\s+keys|Use\s+\S+\s+keys)[^)]*\)/i.test(headerLine);
|
|
3756
|
+
const footerSatisfied = hasQuestionFooter || hasHeaderHint;
|
|
3757
|
+
const hasComposer = hasTrailingAgyComposer(lines, promptEndIdx, lineInFence);
|
|
3758
|
+
if (hasQuestionOptions && footerSatisfied && !hasComposer) {
|
|
3150
3759
|
return "question";
|
|
3151
3760
|
}
|
|
3152
3761
|
} else if (latestChooserIdx === lastFileAccessLineIdx) {
|
|
3153
|
-
const hasFileAccessQuestion =
|
|
3762
|
+
const hasFileAccessQuestion = /^\s*Allow access to this file\?\s*$/im.test(remainingLines);
|
|
3154
3763
|
const hasApprovalOptions = /^[>\s]*[1-9]\. (?:Yes|No)\b/im.test(remainingLines);
|
|
3155
|
-
const hasApprovalFooter =
|
|
3764
|
+
const hasApprovalFooter = /^\s*(?:esc to cancel\b|[↑↓/\s]*Navigate\b|ctrl\+r\s+Review\b)/im.test(remainingLines);
|
|
3156
3765
|
if (hasFileAccessQuestion && hasApprovalOptions && hasApprovalFooter) {
|
|
3157
3766
|
return "file_access";
|
|
3158
3767
|
}
|
|
@@ -3174,6 +3783,20 @@ function detectActivePromptKind(snapshot) {
|
|
|
3174
3783
|
return "file_access";
|
|
3175
3784
|
}
|
|
3176
3785
|
if (latestChooserIdx === lastQuestionLineIdx) {
|
|
3786
|
+
let promptEndIdx = lastQuestionLineIdx;
|
|
3787
|
+
for (let j = lastQuestionLineIdx + 1; j < lines.length; j++) {
|
|
3788
|
+
if (!lineInFence[j]) {
|
|
3789
|
+
if (isAgyStatusFooterLine(lines[j].trim()) || /^\s*(?:[❯>]\s*)?\?\s+for\s+(?:shortcuts|help)\b/i.test(lines[j].trim()) || isComposerBoxAt(lines, j)) {
|
|
3790
|
+
break;
|
|
3791
|
+
}
|
|
3792
|
+
if (/^[>\s❯]*[1-9][.)]\s+\S/.test(lines[j].trim()) || isQuestionFooterLine(lines[j])) {
|
|
3793
|
+
promptEndIdx = j;
|
|
3794
|
+
}
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3797
|
+
if (hasTrailingAgyComposer(lines, promptEndIdx, lineInFence)) {
|
|
3798
|
+
return null;
|
|
3799
|
+
}
|
|
3177
3800
|
return "question";
|
|
3178
3801
|
}
|
|
3179
3802
|
return "approval";
|
|
@@ -3524,12 +4147,45 @@ function extractSystemErrorPrompt(snapshot) {
|
|
|
3524
4147
|
const fullLine = errorId ? `\u26A0\uFE0F Antigravity error: ${message} (Error ID: ${errorId})` : `\u26A0\uFE0F Antigravity error: ${message}`;
|
|
3525
4148
|
return { message, errorId, fullLine };
|
|
3526
4149
|
}
|
|
4150
|
+
function isQuestionHeaderLine(line) {
|
|
4151
|
+
if (!line) return false;
|
|
4152
|
+
const trimmed = line.trim();
|
|
4153
|
+
if (isAgyStatusFooterLine(trimmed)) return false;
|
|
4154
|
+
if (/^\s*(?:[❯>]\s*)?\?\s+for\s+(?:shortcuts|help|commands|options|keybindings|menu)\b/i.test(trimmed)) {
|
|
4155
|
+
return false;
|
|
4156
|
+
}
|
|
4157
|
+
return /^\s*(?:[❯>]\s*)?\?\s+(?!for\s+(?:shortcuts|help|commands|options|keybindings|menu)\b)\S/i.test(trimmed) || /^\s*(?:[❯>]\s*)?(?:\?\s*)?Question\b(?:\s+\d+(?:[\/:of\s]+\d+)?)?:\s*\S/i.test(trimmed);
|
|
4158
|
+
}
|
|
4159
|
+
function isQuestionFooterLine(line) {
|
|
4160
|
+
if (!line) return false;
|
|
4161
|
+
const trimmed = line.trim();
|
|
4162
|
+
if (/^\s*(?:[❯>]\s*)?\?\s+for\s+(?:shortcuts|help|commands|options|keybindings|menu)\b/i.test(trimmed)) {
|
|
4163
|
+
return false;
|
|
4164
|
+
}
|
|
4165
|
+
if (isComposedStatusFooter(trimmed)) return false;
|
|
4166
|
+
if (/^\s*esc to cancel\b/i.test(trimmed) && /(?:Gemini|Flash|Pro|Claude|Sonnet|Opus|GPT|auto|high|low|medium|credits|tasks|subagents?|quota)/i.test(trimmed)) {
|
|
4167
|
+
return false;
|
|
4168
|
+
}
|
|
4169
|
+
if (/^[>\s❯]*[1-9][.)]\s+\S/.test(trimmed)) return false;
|
|
4170
|
+
if (isQuestionHeaderLine(trimmed)) return false;
|
|
4171
|
+
return /^(?:esc|<esc>)\s+(?:to\s+)?(?:cancel|skip)(?:\s*[·│,]\s*(?:enter|<enter>)\s+(?:to\s+)?select)?$/i.test(trimmed) || /^(?:enter|<enter>)\s+(?:to\s+)?select(?:\s*[·│,]\s*(?:esc|<esc>)\s+(?:to\s+)?(?:cancel|skip))?$/i.test(trimmed) || /^(?:↑\/↓|↑|↓|\^|v)\s*(?:\/|\s+)?Navigate(?:\s*[·│,]\s*(?:enter|<enter>)?\s*(?:Select|to select))?(?:\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) || /^waiting(?:\.\.\.)?$/i.test(trimmed);
|
|
4172
|
+
}
|
|
3527
4173
|
function extractQuestionHeader(snapshot) {
|
|
3528
4174
|
const stripped = snapshot.replace(ANSI_ESCAPE_REGEX, "");
|
|
3529
|
-
const
|
|
3530
|
-
|
|
3531
|
-
const
|
|
3532
|
-
|
|
4175
|
+
const lines = stripped.split("\n");
|
|
4176
|
+
const lineInFence = computeFenceMask(lines);
|
|
4177
|
+
const prefixRe = /^\s*(?:[❯>]\s*)?(?:\?\s*(?:Question\b(?:\s+\d+(?:[\/:of\s]+\d+)?)?:\s*)?|Question\b(?:\s+\d+(?:[\/:of\s]+\d+)?)?:\s*)/i;
|
|
4178
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
4179
|
+
if (lineInFence[i]) continue;
|
|
4180
|
+
const line = lines[i];
|
|
4181
|
+
if (isQuestionHeaderLine(line)) {
|
|
4182
|
+
const fullLine = line.trim();
|
|
4183
|
+
let body = fullLine.replace(prefixRe, "").trim();
|
|
4184
|
+
body = body.replace(/\s*\((?:Use\s+arrow\s+keys|Use\s+\S+\s+keys)[^)]*\)\s*$/i, "").trim();
|
|
4185
|
+
return { fullLine, body };
|
|
4186
|
+
}
|
|
4187
|
+
}
|
|
4188
|
+
return null;
|
|
3533
4189
|
}
|
|
3534
4190
|
function hashPromptSnapshot(snapshot) {
|
|
3535
4191
|
let normalized = snapshot.replace(ANSI_ESCAPE_REGEX, "").replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/[ \t]+\n/g, "\n").trim();
|
|
@@ -3549,8 +4205,7 @@ function sleep(ms) {
|
|
|
3549
4205
|
});
|
|
3550
4206
|
}
|
|
3551
4207
|
function isQuestionUISnapshot(snapshot) {
|
|
3552
|
-
|
|
3553
|
-
return /Question \d+\/\d+:/i.test(stripped);
|
|
4208
|
+
return extractQuestionHeader(snapshot) !== null;
|
|
3554
4209
|
}
|
|
3555
4210
|
|
|
3556
4211
|
// src/approval-detector.ts
|
|
@@ -3606,7 +4261,7 @@ function parseFileAccessSnapshot(stripped, originalSnapshot, prompt) {
|
|
|
3606
4261
|
const lines = stripped.split("\n");
|
|
3607
4262
|
let questionIndex = -1;
|
|
3608
4263
|
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
3609
|
-
if (
|
|
4264
|
+
if (/^\s*Allow access to this file\?\s*$/i.test(lines[index])) {
|
|
3610
4265
|
questionIndex = index;
|
|
3611
4266
|
break;
|
|
3612
4267
|
}
|
|
@@ -3671,9 +4326,11 @@ function parseApprovalUISnapshot(stripped, originalSnapshot) {
|
|
|
3671
4326
|
}
|
|
3672
4327
|
function parseQuestionSnapshot(stripped, originalSnapshot, header) {
|
|
3673
4328
|
const lines = stripped.split("\n");
|
|
4329
|
+
const lineInFence = computeFenceMask(lines);
|
|
3674
4330
|
let headerIdx = -1;
|
|
3675
4331
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
3676
|
-
if (
|
|
4332
|
+
if (lineInFence[i]) continue;
|
|
4333
|
+
if (isQuestionHeaderLine(lines[i])) {
|
|
3677
4334
|
headerIdx = i;
|
|
3678
4335
|
break;
|
|
3679
4336
|
}
|
|
@@ -3681,7 +4338,10 @@ function parseQuestionSnapshot(stripped, originalSnapshot, header) {
|
|
|
3681
4338
|
const belowHeader = headerIdx >= 0 ? lines.slice(headerIdx + 1).join("\n") : stripped;
|
|
3682
4339
|
const options = parseOptions(belowHeader);
|
|
3683
4340
|
if (options.length < 2) return null;
|
|
3684
|
-
const
|
|
4341
|
+
const normalizedHeader = header.fullLine.replace(/^[❯>\s*•-]+\s*/, "").trim();
|
|
4342
|
+
const hasArrowKeyHint = /\((?:Use\s+arrow\s+keys|Use\s+\S+\s+keys)[^)]*\)/i.test(normalizedHeader);
|
|
4343
|
+
const isBaseNumberedWizard = /^Question\s+\d+\/\d+:/i.test(normalizedHeader) && !hasArrowKeyHint;
|
|
4344
|
+
const submitMap = isBaseNumberedWizard ? buildQuestionSubmitMap(options) : {};
|
|
3685
4345
|
const body = extractBodyBetweenHeaderAndFirstOption(belowHeader);
|
|
3686
4346
|
return {
|
|
3687
4347
|
kind: "question",
|
|
@@ -3697,16 +4357,11 @@ function parseQuestionSnapshot(stripped, originalSnapshot, header) {
|
|
|
3697
4357
|
}
|
|
3698
4358
|
function extractBodyBetweenHeaderAndFirstOption(belowHeader) {
|
|
3699
4359
|
const lines = belowHeader.split("\n");
|
|
3700
|
-
const optionRegex = /^[>\s]*([1-9])
|
|
3701
|
-
const
|
|
4360
|
+
const optionRegex = /^[>\s❯]*([1-9])[.)]\s+(.+?)\s*$/;
|
|
4361
|
+
const lineInFence = computeFenceMask(lines);
|
|
3702
4362
|
let firstOptionIdx = -1;
|
|
3703
|
-
let inFence = false;
|
|
3704
4363
|
for (let i = 0; i < lines.length; i++) {
|
|
3705
|
-
if (
|
|
3706
|
-
inFence = !inFence;
|
|
3707
|
-
continue;
|
|
3708
|
-
}
|
|
3709
|
-
if (inFence) continue;
|
|
4364
|
+
if (lineInFence[i]) continue;
|
|
3710
4365
|
if (optionRegex.test(lines[i])) {
|
|
3711
4366
|
firstOptionIdx = i;
|
|
3712
4367
|
break;
|
|
@@ -3716,8 +4371,19 @@ function extractBodyBetweenHeaderAndFirstOption(belowHeader) {
|
|
|
3716
4371
|
return lines.slice(0, firstOptionIdx).join("\n").trim();
|
|
3717
4372
|
}
|
|
3718
4373
|
function parseOptions(snapshot) {
|
|
3719
|
-
const
|
|
3720
|
-
const
|
|
4374
|
+
const lines = snapshot.split("\n");
|
|
4375
|
+
const lineInFence = computeFenceMask(lines);
|
|
4376
|
+
const filteredLines = [];
|
|
4377
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4378
|
+
if (!lineInFence[i]) {
|
|
4379
|
+
filteredLines.push(lines[i]);
|
|
4380
|
+
}
|
|
4381
|
+
}
|
|
4382
|
+
while (filteredLines.length > 0 && filteredLines[filteredLines.length - 1].trim() === "") {
|
|
4383
|
+
filteredLines.pop();
|
|
4384
|
+
}
|
|
4385
|
+
const recent = filteredLines.slice(-30);
|
|
4386
|
+
const optionRegex = /^[>\s❯]*([1-9])[.)]\s+(.+?)\s*$/;
|
|
3721
4387
|
const found = /* @__PURE__ */ new Map();
|
|
3722
4388
|
for (const line of recent) {
|
|
3723
4389
|
const m = line.match(optionRegex);
|
|
@@ -3821,6 +4487,7 @@ function extractIdentity(headerText) {
|
|
|
3821
4487
|
return { command: trimmed };
|
|
3822
4488
|
}
|
|
3823
4489
|
function guessToolTypeFromIdentity(c) {
|
|
4490
|
+
if (c.kind === "question") return "ASK_QUESTION";
|
|
3824
4491
|
if (c.command) return "RUN_COMMAND";
|
|
3825
4492
|
if (c.filePath) return "CODE_ACTION";
|
|
3826
4493
|
return "GENERIC";
|
|
@@ -3927,11 +4594,12 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
|
|
|
3927
4594
|
intentByteOffset: 0,
|
|
3928
4595
|
// Best-effort tool-type inference from the candidate. For shell
|
|
3929
4596
|
// commands → RUN_COMMAND. For edit-style headers with a file
|
|
3930
|
-
// path → CODE_ACTION. Default: GENERIC.
|
|
4597
|
+
// path → CODE_ACTION. For questions → ASK_QUESTION. Default: GENERIC.
|
|
3931
4598
|
toolType: guessToolTypeFromIdentity(candidate),
|
|
3932
4599
|
command: candidate.command,
|
|
3933
4600
|
filePath: candidate.filePath,
|
|
3934
|
-
rawToolName: candidate.command ?? candidate.filePath ?? "unknown",
|
|
4601
|
+
rawToolName: candidate.kind === "question" ? "ask_question" : candidate.command ?? candidate.filePath ?? "unknown",
|
|
4602
|
+
questionText: candidate.kind === "question" ? candidate.headerText : void 0,
|
|
3935
4603
|
promptId,
|
|
3936
4604
|
startedAt: Date.now(),
|
|
3937
4605
|
intentCreatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -4253,6 +4921,21 @@ var ApprovalDetector = class extends import_events3.EventEmitter {
|
|
|
4253
4921
|
return `${call.conversationId}|${call.intentStepIndex}|${call.toolCallIndex}|${call.intentByteOffset}`;
|
|
4254
4922
|
}
|
|
4255
4923
|
findMatchingPendingCall(candidate, conversationId) {
|
|
4924
|
+
if (candidate.kind === "question") {
|
|
4925
|
+
const questionCalls = Array.from(this.pendingCalls.values()).filter((c) => c.conversationId === conversationId).filter(
|
|
4926
|
+
(c) => c.toolType === "ASK_QUESTION" || c.rawToolName?.toLowerCase() === "ask_question"
|
|
4927
|
+
).sort((a, b) => a.intentStepIndex - b.intentStepIndex);
|
|
4928
|
+
if (questionCalls.length > 0 && candidate.headerText) {
|
|
4929
|
+
const normHeader = candidate.headerText.toLowerCase().trim();
|
|
4930
|
+
const textMatch = questionCalls.find((c) => {
|
|
4931
|
+
if (!c.questionText) return false;
|
|
4932
|
+
const normQ = c.questionText.toLowerCase().trim();
|
|
4933
|
+
return normHeader === normQ || normHeader.includes(normQ) || normQ.includes(normHeader);
|
|
4934
|
+
});
|
|
4935
|
+
if (textMatch) return textMatch;
|
|
4936
|
+
}
|
|
4937
|
+
return null;
|
|
4938
|
+
}
|
|
4256
4939
|
if (candidate.command) {
|
|
4257
4940
|
const matches = Array.from(this.pendingCalls.values()).filter((c) => c.conversationId === conversationId).filter((c) => c.command && c.command === candidate.command).sort((a, b) => a.intentStepIndex - b.intentStepIndex);
|
|
4258
4941
|
if (matches.length > 0) return matches[0];
|
|
@@ -4555,6 +5238,33 @@ var PromptResponder = class {
|
|
|
4555
5238
|
const before = await this.paneObserver.probeApprovalUIActive();
|
|
4556
5239
|
if (!before.probeSucceeded) return { ok: false, reason: "prompt-probe-failed" };
|
|
4557
5240
|
if (!before.active) return { ok: true };
|
|
5241
|
+
if (before.ambiguous) {
|
|
5242
|
+
return {
|
|
5243
|
+
ok: false,
|
|
5244
|
+
reason: "ambiguous-prompt-active",
|
|
5245
|
+
details: "masked/ambiguous prompt cannot be dismissed via Escape from mobile"
|
|
5246
|
+
};
|
|
5247
|
+
}
|
|
5248
|
+
if (before.kind === "question") {
|
|
5249
|
+
let isActionableWizard = false;
|
|
5250
|
+
try {
|
|
5251
|
+
const snapshot = await this.paneObserver.captureSnapshot();
|
|
5252
|
+
if (snapshot) {
|
|
5253
|
+
const parsed = parseApprovalSnapshot(snapshot);
|
|
5254
|
+
if (parsed && parsed.kind === "question" && Object.keys(parsed.submitMap || {}).length > 0) {
|
|
5255
|
+
isActionableWizard = true;
|
|
5256
|
+
}
|
|
5257
|
+
}
|
|
5258
|
+
} catch {
|
|
5259
|
+
}
|
|
5260
|
+
if (!isActionableWizard) {
|
|
5261
|
+
return {
|
|
5262
|
+
ok: false,
|
|
5263
|
+
reason: "display-only-question-active",
|
|
5264
|
+
details: "active question prompt cannot be dismissed via Escape from mobile"
|
|
5265
|
+
};
|
|
5266
|
+
}
|
|
5267
|
+
}
|
|
4558
5268
|
await this.sendKey(target, "Escape");
|
|
4559
5269
|
await delay(250);
|
|
4560
5270
|
const after = await this.paneObserver.probeApprovalUIActive();
|
|
@@ -4835,7 +5545,7 @@ function fireDaemonBeacon(name, params) {
|
|
|
4835
5545
|
|
|
4836
5546
|
// src/event-mapper.ts
|
|
4837
5547
|
var import_uuid2 = require("uuid");
|
|
4838
|
-
var
|
|
5548
|
+
var import_codevibe_core5 = require("@quantiya/codevibe-core");
|
|
4839
5549
|
var CONTENT_MAX = 150 * 1024;
|
|
4840
5550
|
var TOOL_OUTPUT_MAX = 50 * 1024;
|
|
4841
5551
|
var TOOL_DIFF_MAX = 100 * 1024;
|
|
@@ -4843,10 +5553,10 @@ function mapTranscriptEvent(emit, sessionId, notBeforeMs) {
|
|
|
4843
5553
|
const { event } = emit;
|
|
4844
5554
|
const base = {
|
|
4845
5555
|
sessionId,
|
|
4846
|
-
source:
|
|
5556
|
+
source: import_codevibe_core5.EventSource.DESKTOP
|
|
4847
5557
|
};
|
|
4848
5558
|
const orderingKey = `${sessionId}:${emit.conversationId}`;
|
|
4849
|
-
const nextTimestamp = () => (0,
|
|
5559
|
+
const nextTimestamp = () => (0, import_codevibe_core5.prepareEventTimestamp)({ orderingKey, agentClock: event.created_at, notBeforeMs });
|
|
4850
5560
|
const baseMetadata = {
|
|
4851
5561
|
step_index: event.step_index,
|
|
4852
5562
|
agy_source: event.source,
|
|
@@ -4886,7 +5596,7 @@ function mapTranscriptEvent(emit, sessionId, notBeforeMs) {
|
|
|
4886
5596
|
{
|
|
4887
5597
|
...base,
|
|
4888
5598
|
timestamp: nextTimestamp(),
|
|
4889
|
-
type:
|
|
5599
|
+
type: import_codevibe_core5.EventType.USER_PROMPT,
|
|
4890
5600
|
content: truncate(cleaned, CONTENT_MAX),
|
|
4891
5601
|
metadata: { ...baseMetadata }
|
|
4892
5602
|
}
|
|
@@ -4901,7 +5611,7 @@ function mapTranscriptEvent(emit, sessionId, notBeforeMs) {
|
|
|
4901
5611
|
events.push({
|
|
4902
5612
|
...base,
|
|
4903
5613
|
timestamp: nextTimestamp(),
|
|
4904
|
-
type:
|
|
5614
|
+
type: import_codevibe_core5.EventType.REASONING,
|
|
4905
5615
|
content: truncate(event.thinking, CONTENT_MAX),
|
|
4906
5616
|
metadata: { ...baseMetadata }
|
|
4907
5617
|
});
|
|
@@ -4910,7 +5620,7 @@ function mapTranscriptEvent(emit, sessionId, notBeforeMs) {
|
|
|
4910
5620
|
events.push({
|
|
4911
5621
|
...base,
|
|
4912
5622
|
timestamp: nextTimestamp(),
|
|
4913
|
-
type:
|
|
5623
|
+
type: import_codevibe_core5.EventType.ASSISTANT_RESPONSE,
|
|
4914
5624
|
content: truncate(event.content, CONTENT_MAX),
|
|
4915
5625
|
metadata: { ...baseMetadata }
|
|
4916
5626
|
});
|
|
@@ -4931,6 +5641,7 @@ function mapTranscriptEvent(emit, sessionId, notBeforeMs) {
|
|
|
4931
5641
|
conversationId: emit.conversationId,
|
|
4932
5642
|
command: extractCommandFromArgs(name, args),
|
|
4933
5643
|
filePath: extractFilePathFromArgs(name, args),
|
|
5644
|
+
questionText: extractQuestionFromArgs(name, args),
|
|
4934
5645
|
cwd: extractCwdFromArgs(args),
|
|
4935
5646
|
rawToolName: name,
|
|
4936
5647
|
startedAt: Date.now(),
|
|
@@ -4947,7 +5658,7 @@ function mapTranscriptEvent(emit, sessionId, notBeforeMs) {
|
|
|
4947
5658
|
{
|
|
4948
5659
|
...base,
|
|
4949
5660
|
timestamp: nextTimestamp(),
|
|
4950
|
-
type:
|
|
5661
|
+
type: import_codevibe_core5.EventType.NOTIFICATION,
|
|
4951
5662
|
content: truncate(content, CONTENT_MAX),
|
|
4952
5663
|
metadata: {
|
|
4953
5664
|
...baseMetadata,
|
|
@@ -4967,7 +5678,7 @@ function mapTranscriptEvent(emit, sessionId, notBeforeMs) {
|
|
|
4967
5678
|
{
|
|
4968
5679
|
...base,
|
|
4969
5680
|
timestamp: nextTimestamp(),
|
|
4970
|
-
type:
|
|
5681
|
+
type: import_codevibe_core5.EventType.TOOL_USE,
|
|
4971
5682
|
content: formatToolResultContent(event.type, toolName, truncatedContent, isErrorStatus),
|
|
4972
5683
|
metadata: {
|
|
4973
5684
|
...baseMetadata,
|
|
@@ -5032,8 +5743,9 @@ function hookToolNameToTranscriptType(hookName) {
|
|
|
5032
5743
|
case "MANAGE_TASK":
|
|
5033
5744
|
case "SCHEDULE":
|
|
5034
5745
|
case "ASK_PERMISSION":
|
|
5035
|
-
case "ASK_QUESTION":
|
|
5036
5746
|
return "GENERIC";
|
|
5747
|
+
case "ASK_QUESTION":
|
|
5748
|
+
return "ASK_QUESTION";
|
|
5037
5749
|
default:
|
|
5038
5750
|
return upper;
|
|
5039
5751
|
}
|
|
@@ -5069,6 +5781,32 @@ function extractFilePathFromArgs(toolName, args) {
|
|
|
5069
5781
|
}
|
|
5070
5782
|
return void 0;
|
|
5071
5783
|
}
|
|
5784
|
+
function extractQuestionFromArgs(toolName, args) {
|
|
5785
|
+
if (!args || typeof args !== "object") return void 0;
|
|
5786
|
+
const tn = (toolName || "").toLowerCase().trim();
|
|
5787
|
+
const isQuestionTool = tn === "ask_question" || tn === "question" || tn === "ask";
|
|
5788
|
+
if (!isQuestionTool) return void 0;
|
|
5789
|
+
const a = args;
|
|
5790
|
+
if (typeof a.question === "string" && a.question.trim().length > 0) {
|
|
5791
|
+
return stripWrappingQuotes2(a.question.trim());
|
|
5792
|
+
}
|
|
5793
|
+
if (typeof a.prompt === "string" && a.prompt.trim().length > 0) {
|
|
5794
|
+
return stripWrappingQuotes2(a.prompt.trim());
|
|
5795
|
+
}
|
|
5796
|
+
if (Array.isArray(a.questions) && a.questions.length > 0) {
|
|
5797
|
+
const first = a.questions[0];
|
|
5798
|
+
if (typeof first === "string" && first.trim().length > 0) {
|
|
5799
|
+
return stripWrappingQuotes2(first.trim());
|
|
5800
|
+
}
|
|
5801
|
+
if (first && typeof first === "object") {
|
|
5802
|
+
const q = first.question;
|
|
5803
|
+
if (typeof q === "string" && q.trim().length > 0) {
|
|
5804
|
+
return stripWrappingQuotes2(q.trim());
|
|
5805
|
+
}
|
|
5806
|
+
}
|
|
5807
|
+
}
|
|
5808
|
+
return void 0;
|
|
5809
|
+
}
|
|
5072
5810
|
function extractCwdFromArgs(args) {
|
|
5073
5811
|
if (!args || typeof args !== "object") return void 0;
|
|
5074
5812
|
const a = args;
|
|
@@ -5125,7 +5863,7 @@ function truncate(s, maxBytes) {
|
|
|
5125
5863
|
}
|
|
5126
5864
|
|
|
5127
5865
|
// src/tool-activity-integration.ts
|
|
5128
|
-
var
|
|
5866
|
+
var import_codevibe_core6 = require("@quantiya/codevibe-core");
|
|
5129
5867
|
var TOOL_ACTIVITY_CONTENT = "tool_activity";
|
|
5130
5868
|
var DEFAULT_WINDOW_MS = 1e4;
|
|
5131
5869
|
var DEFAULT_MAX_OCCURRENCES = 50;
|
|
@@ -5192,9 +5930,9 @@ function agyOccurrenceId(emit, matchedCall) {
|
|
|
5192
5930
|
createdAt: emit.event.created_at,
|
|
5193
5931
|
stepIndex: emit.event.step_index,
|
|
5194
5932
|
type: emit.event.type,
|
|
5195
|
-
contentDigest: (0,
|
|
5933
|
+
contentDigest: (0, import_codevibe_core6.digestOf)(emit.event.content ?? "")
|
|
5196
5934
|
};
|
|
5197
|
-
return `agy:${(0,
|
|
5935
|
+
return `agy:${(0, import_codevibe_core6.digestOf)(identity).slice("sha256:".length)}`;
|
|
5198
5936
|
}
|
|
5199
5937
|
var AgyToolActivityIntegration = class {
|
|
5200
5938
|
constructor(deps) {
|
|
@@ -5209,12 +5947,12 @@ var AgyToolActivityIntegration = class {
|
|
|
5209
5947
|
async start(sessionId) {
|
|
5210
5948
|
if (this.consolidator && this.backendSessionId === sessionId) return;
|
|
5211
5949
|
if (this.consolidator) await this.stop();
|
|
5212
|
-
const outbox = new
|
|
5950
|
+
const outbox = new import_codevibe_core6.ToolActivityOutbox({
|
|
5213
5951
|
agent: "agy",
|
|
5214
5952
|
logger: this.deps.logger,
|
|
5215
5953
|
root: this.deps.root
|
|
5216
5954
|
});
|
|
5217
|
-
const ledger = new
|
|
5955
|
+
const ledger = new import_codevibe_core6.ToolActivityLedger({
|
|
5218
5956
|
agent: "agy",
|
|
5219
5957
|
sessionId,
|
|
5220
5958
|
logger: this.deps.logger,
|
|
@@ -5224,7 +5962,7 @@ var AgyToolActivityIntegration = class {
|
|
|
5224
5962
|
const legacyTransport = this.buildTransport();
|
|
5225
5963
|
const transport = this.deps.selectTransport ? await this.deps.selectTransport(sessionId, legacyTransport) : legacyTransport;
|
|
5226
5964
|
if (transport.protocol !== "SYNC_V2") await this.rehomePendingWindows(outbox, sessionId);
|
|
5227
|
-
const consolidator = new
|
|
5965
|
+
const consolidator = new import_codevibe_core6.ToolActivityConsolidator({
|
|
5228
5966
|
agent: "agy",
|
|
5229
5967
|
sessionId,
|
|
5230
5968
|
logger: this.deps.logger,
|
|
@@ -5262,7 +6000,7 @@ var AgyToolActivityIntegration = class {
|
|
|
5262
6000
|
...target ? { normalizedTarget: target } : {},
|
|
5263
6001
|
ts: emit.event.created_at,
|
|
5264
6002
|
byteOffset: emit.byteOffset,
|
|
5265
|
-
...inputIdentity ? { digest: (0,
|
|
6003
|
+
...inputIdentity ? { digest: (0, import_codevibe_core6.digestOf)(inputIdentity) } : {}
|
|
5266
6004
|
});
|
|
5267
6005
|
}
|
|
5268
6006
|
async flush() {
|
|
@@ -5332,7 +6070,7 @@ var AgyToolActivityIntegration = class {
|
|
|
5332
6070
|
if (!session || session.sessionId !== sessionId) {
|
|
5333
6071
|
throw terminalSessionError(sessionId);
|
|
5334
6072
|
}
|
|
5335
|
-
const encrypted = (0,
|
|
6073
|
+
const encrypted = (0, import_codevibe_core6.encryptForEmit)(
|
|
5336
6074
|
override?.sessionKey ?? session.sessionKey ?? null,
|
|
5337
6075
|
session.sessionIsEncrypted ?? false,
|
|
5338
6076
|
TOOL_ACTIVITY_CONTENT,
|
|
@@ -5344,8 +6082,8 @@ var AgyToolActivityIntegration = class {
|
|
|
5344
6082
|
const sessionKeyGen = override?.sessionKeyGen ?? session.sessionKeyGen;
|
|
5345
6083
|
const input = {
|
|
5346
6084
|
sessionId,
|
|
5347
|
-
type:
|
|
5348
|
-
source:
|
|
6085
|
+
type: import_codevibe_core6.EventType.TOOL_ACTIVITY,
|
|
6086
|
+
source: import_codevibe_core6.EventSource.DESKTOP,
|
|
5349
6087
|
content: encrypted.content,
|
|
5350
6088
|
metadata: encrypted.metadata,
|
|
5351
6089
|
timestamp: envelope.windowStart,
|
|
@@ -5360,10 +6098,10 @@ var AgyToolActivityIntegration = class {
|
|
|
5360
6098
|
try {
|
|
5361
6099
|
await sendOnce(sessionId, envelope);
|
|
5362
6100
|
} catch (error) {
|
|
5363
|
-
if ((0,
|
|
6101
|
+
if ((0, import_codevibe_core6.isTerminalSessionError)(error)) {
|
|
5364
6102
|
throw new Error(`[tool-activity] Agy session ${sessionId} retired; awaiting replacement`);
|
|
5365
6103
|
}
|
|
5366
|
-
if ((0,
|
|
6104
|
+
if ((0, import_codevibe_core6.isSessionKeyStaleError)(error) && this.deps.refreshSessionKey) {
|
|
5367
6105
|
const refreshed = await this.deps.refreshSessionKey(sessionId);
|
|
5368
6106
|
if (refreshed) {
|
|
5369
6107
|
await sendOnce(sessionId, envelope, refreshed);
|
|
@@ -5472,7 +6210,7 @@ var McpServer = class _McpServer {
|
|
|
5472
6210
|
* under this single sessionId. /resume does NOT create a new
|
|
5473
6211
|
* session — same row. */
|
|
5474
6212
|
this.session = null;
|
|
5475
|
-
this.promptPublications = new
|
|
6213
|
+
this.promptPublications = new import_codevibe_core7.PromptPublicationTracker();
|
|
5476
6214
|
this.transcriptHandlers = /* @__PURE__ */ new Set();
|
|
5477
6215
|
this.nativeSourceSelection = null;
|
|
5478
6216
|
this.nativeSources = /* @__PURE__ */ new Map();
|
|
@@ -5636,7 +6374,7 @@ var McpServer = class _McpServer {
|
|
|
5636
6374
|
this.wrapperPid = options.wrapperPid ?? null;
|
|
5637
6375
|
this.cliLogPath = options.cliLogPath ?? null;
|
|
5638
6376
|
this.launchSettleTimeoutMs = options.launchSettleTimeoutMs ?? LAUNCH_SETTLE_TIMEOUT_MS;
|
|
5639
|
-
this.appSyncClient = options.appSyncClient ?? new
|
|
6377
|
+
this.appSyncClient = options.appSyncClient ?? new import_codevibe_core7.AppSyncClient();
|
|
5640
6378
|
this.toolActivity = options.toolActivityIntegration === void 0 ? new AgyToolActivityIntegration({
|
|
5641
6379
|
logger,
|
|
5642
6380
|
selectTransport: async (sessionId, legacyTransport) => {
|
|
@@ -5669,7 +6407,7 @@ var McpServer = class _McpServer {
|
|
|
5669
6407
|
this.approvalDetector = options.approvalDetector ?? new ApprovalDetector(options.detectorOptions);
|
|
5670
6408
|
this.paneObserver = options.paneObserver ?? new TmuxPaneObserver();
|
|
5671
6409
|
this.transcriptTailer = options.transcriptTailer ?? new TranscriptTailer({ admission: {
|
|
5672
|
-
restore: (source, initial) => this.restoreNativeSource(source, initial),
|
|
6410
|
+
restore: (source, initial, validateInitial) => this.restoreNativeSource(source, initial, validateInitial),
|
|
5673
6411
|
admit: (emit, source) => this.admitNativeSource(emit, source),
|
|
5674
6412
|
skip: (gap, source) => this.skipNativeSource(gap, source)
|
|
5675
6413
|
} });
|
|
@@ -5714,6 +6452,7 @@ var McpServer = class _McpServer {
|
|
|
5714
6452
|
// ─── Lifecycle ──────────────────────────────────────────────────────────
|
|
5715
6453
|
async start() {
|
|
5716
6454
|
if (this.started) throw new Error("McpServer.start() called twice");
|
|
6455
|
+
this.transcriptTailer.captureLaunchBaselines();
|
|
5717
6456
|
this.stopPromise = null;
|
|
5718
6457
|
this.stopTmuxLifecycleMonitor();
|
|
5719
6458
|
this.tmuxLifecycleObservedAlive = false;
|
|
@@ -5725,7 +6464,7 @@ var McpServer = class _McpServer {
|
|
|
5725
6464
|
const authed = await this.appSyncClient.authenticateWithStoredTokens();
|
|
5726
6465
|
if (!authed) {
|
|
5727
6466
|
const kind = this.appSyncClient.getLastAuthFailureKind();
|
|
5728
|
-
const failure = (0,
|
|
6467
|
+
const failure = (0, import_codevibe_core7.describeStoredAuthFailure)(
|
|
5729
6468
|
kind,
|
|
5730
6469
|
this.appSyncClient.getLastAuthFailureDetail()
|
|
5731
6470
|
);
|
|
@@ -5752,6 +6491,8 @@ var McpServer = class _McpServer {
|
|
|
5752
6491
|
() => reject(new Error("status read timed out")),
|
|
5753
6492
|
{ once: true }
|
|
5754
6493
|
));
|
|
6494
|
+
timeout.catch(() => {
|
|
6495
|
+
});
|
|
5755
6496
|
const subscription = await Promise.race([this.appSyncClient.getSubscriptionStatus(signal), timeout]);
|
|
5756
6497
|
if (this.appSyncClient.getCurrentUserId() !== statusUserId) throw new Error("account changed during read");
|
|
5757
6498
|
const sync = await Promise.race([this.appSyncClient.getCompanionSyncState(signal), timeout]);
|
|
@@ -5767,9 +6508,9 @@ var McpServer = class _McpServer {
|
|
|
5767
6508
|
logger.warn("Standalone Companion sync status unavailable; cloud admission remains authoritative");
|
|
5768
6509
|
}
|
|
5769
6510
|
if (!this.started) return { httpPort: 0 };
|
|
5770
|
-
await (0,
|
|
6511
|
+
await (0, import_codevibe_core7.registerDeviceEncryptionKey)(this.appSyncClient, logger);
|
|
5771
6512
|
if (!this.started) return { httpPort: 0 };
|
|
5772
|
-
(0,
|
|
6513
|
+
(0, import_codevibe_core7.startDeviceKeyWatcher)(this.appSyncClient, logger);
|
|
5773
6514
|
try {
|
|
5774
6515
|
const swept = await this.appSyncClient.sweepOrphanSessions({
|
|
5775
6516
|
agentType: "ANTIGRAVITY",
|
|
@@ -5842,7 +6583,10 @@ var McpServer = class _McpServer {
|
|
|
5842
6583
|
}
|
|
5843
6584
|
async stop() {
|
|
5844
6585
|
if (this.stopPromise) return this.stopPromise;
|
|
5845
|
-
if (!this.started)
|
|
6586
|
+
if (!this.started) {
|
|
6587
|
+
await this.transcriptTailer.stop();
|
|
6588
|
+
return;
|
|
6589
|
+
}
|
|
5846
6590
|
this.stopPromise = this.doStop();
|
|
5847
6591
|
try {
|
|
5848
6592
|
await this.stopPromise;
|
|
@@ -6015,7 +6759,10 @@ var McpServer = class _McpServer {
|
|
|
6015
6759
|
try {
|
|
6016
6760
|
const retired = await this.appSyncClient.updateSession({ sessionId, status: "INACTIVE" });
|
|
6017
6761
|
if (quiescentSession && retired?.sessionGenerationId) {
|
|
6018
|
-
await this.appSyncClient.completeDesktopSyncRetirement({
|
|
6762
|
+
await this.appSyncClient.completeDesktopSyncRetirement({
|
|
6763
|
+
sessionId,
|
|
6764
|
+
toolOutbox: new import_codevibe_core7.ToolActivityOutbox({ agent: "agy", logger })
|
|
6765
|
+
}, retired, async () => {
|
|
6019
6766
|
await Promise.allSettled([...this.transcriptHandlers]);
|
|
6020
6767
|
await this.nativeAdmissionChain;
|
|
6021
6768
|
await this.stopNativeSources(quiescentSession);
|
|
@@ -6064,7 +6811,7 @@ var McpServer = class _McpServer {
|
|
|
6064
6811
|
let sessionKey = null;
|
|
6065
6812
|
let sessionKeyGen = null;
|
|
6066
6813
|
try {
|
|
6067
|
-
const result = await (0,
|
|
6814
|
+
const result = await (0, import_codevibe_core7.resumeOrCreateSession)(
|
|
6068
6815
|
{
|
|
6069
6816
|
sessionId,
|
|
6070
6817
|
userId,
|
|
@@ -6152,7 +6899,7 @@ var McpServer = class _McpServer {
|
|
|
6152
6899
|
(evt) => {
|
|
6153
6900
|
return this.handleMobileEvent(evt).catch((err) => {
|
|
6154
6901
|
logger.error("handleMobileEvent failed", { error: String(err) });
|
|
6155
|
-
if ((0,
|
|
6902
|
+
if ((0, import_codevibe_core7.requiresSyncInputDeliveryReceipt)(evt)) throw err;
|
|
6156
6903
|
});
|
|
6157
6904
|
},
|
|
6158
6905
|
(err) => logger.warn("AppSync subscription error", { error: String(err) }),
|
|
@@ -6428,11 +7175,11 @@ var McpServer = class _McpServer {
|
|
|
6428
7175
|
this.approvalDetector.registerPendingCall(reg);
|
|
6429
7176
|
}
|
|
6430
7177
|
for (const [index, input] of mapped.events.entries()) {
|
|
6431
|
-
if (input.type ===
|
|
7178
|
+
if (input.type === import_codevibe_core7.EventType.USER_PROMPT || input.type === import_codevibe_core7.EventType.ASSISTANT_RESPONSE || input.type === import_codevibe_core7.EventType.NOTIFICATION) {
|
|
6432
7179
|
if (admission) await this.toolActivity.flush();
|
|
6433
7180
|
else await this.flushToolActivityAtBoundary(input.type);
|
|
6434
7181
|
}
|
|
6435
|
-
if (input.type ===
|
|
7182
|
+
if (input.type === import_codevibe_core7.EventType.TOOL_USE && typeof input.metadata === "object" && input.metadata) {
|
|
6436
7183
|
const meta = input.metadata;
|
|
6437
7184
|
const resultStep = typeof meta.step_index === "number" ? meta.step_index : void 0;
|
|
6438
7185
|
const agyType = typeof meta.agy_type === "string" ? meta.agy_type : void 0;
|
|
@@ -6459,7 +7206,7 @@ var McpServer = class _McpServer {
|
|
|
6459
7206
|
}
|
|
6460
7207
|
}
|
|
6461
7208
|
}
|
|
6462
|
-
if (input.type ===
|
|
7209
|
+
if (input.type === import_codevibe_core7.EventType.USER_PROMPT && input.source === import_codevibe_core7.EventSource.DESKTOP) {
|
|
6463
7210
|
const inputObservedAtMs = this.transcriptObservedAt.get(emit);
|
|
6464
7211
|
const clearedPromptIds = this.approvalDetector.onPanePromptCleared(inputObservedAtMs);
|
|
6465
7212
|
await this.resolveAcceptedInputPrompts(session, clearedPromptIds, inputObservedAtMs);
|
|
@@ -6549,10 +7296,10 @@ var McpServer = class _McpServer {
|
|
|
6549
7296
|
this.nativeSources.set(key, owner);
|
|
6550
7297
|
return owner;
|
|
6551
7298
|
}
|
|
6552
|
-
async restoreNativeSource(source, initial) {
|
|
7299
|
+
async restoreNativeSource(source, initial, validateInitial) {
|
|
6553
7300
|
const owner = await this.nativeOwner(source);
|
|
6554
7301
|
if (!owner) return null;
|
|
6555
|
-
const position = await owner.cursor.open(initial);
|
|
7302
|
+
const position = await owner.cursor.open(initial, validateInitial);
|
|
6556
7303
|
this.assertNativeOwner(owner);
|
|
6557
7304
|
await owner.selection.writer.mobileEchoes("agy").reconcile(owner.streamSha256, position.byteOffset);
|
|
6558
7305
|
this.drainNativeSource(owner);
|
|
@@ -6583,7 +7330,7 @@ var McpServer = class _McpServer {
|
|
|
6583
7330
|
const echoes = owner.selection.writer.mobileEchoes("agy");
|
|
6584
7331
|
for (let index = 0; index < admission.outputs.length; index++) {
|
|
6585
7332
|
const input = admission.outputs[index];
|
|
6586
|
-
if (input?.type ===
|
|
7333
|
+
if (input?.type === import_codevibe_core7.EventType.USER_PROMPT && await echoes.claim(echoFrame, input.content, emit.event.created_at)) admission.outputs[index] = null;
|
|
6587
7334
|
}
|
|
6588
7335
|
const factory = (index) => {
|
|
6589
7336
|
const input = admission.outputs[index];
|
|
@@ -6675,18 +7422,18 @@ var McpServer = class _McpServer {
|
|
|
6675
7422
|
const expectedSession = this.session;
|
|
6676
7423
|
try {
|
|
6677
7424
|
const session = await this.appSyncClient.getSession(sessionId);
|
|
6678
|
-
if (session?.status ===
|
|
7425
|
+
if (session?.status === import_codevibe_core7.SessionStatus.INACTIVE) {
|
|
6679
7426
|
const terminal = new Error(`Antigravity session is no longer active: ${sessionId}`);
|
|
6680
7427
|
terminal.errorType = "SessionTerminal";
|
|
6681
7428
|
throw terminal;
|
|
6682
7429
|
}
|
|
6683
|
-
if (!session || session.status !==
|
|
7430
|
+
if (!session || session.status !== import_codevibe_core7.SessionStatus.ACTIVE || !session.isEncrypted) {
|
|
6684
7431
|
return null;
|
|
6685
7432
|
}
|
|
6686
7433
|
const sessionKeyGen = session.sessionKeyGen ?? null;
|
|
6687
7434
|
const encryptedKeys = session.encryptedKeys ?? [];
|
|
6688
7435
|
if (encryptedKeys.length === 0) return null;
|
|
6689
|
-
const sessionKey = await
|
|
7436
|
+
const sessionKey = await import_codevibe_core7.keychainManager.getSessionKey(
|
|
6690
7437
|
sessionId,
|
|
6691
7438
|
encryptedKeys,
|
|
6692
7439
|
sessionKeyGen
|
|
@@ -6699,7 +7446,7 @@ var McpServer = class _McpServer {
|
|
|
6699
7446
|
liveSession.sessionIsEncrypted = true;
|
|
6700
7447
|
return { sessionKey, sessionKeyGen };
|
|
6701
7448
|
} catch (error) {
|
|
6702
|
-
if ((0,
|
|
7449
|
+
if ((0, import_codevibe_core7.isTerminalSessionError)(error)) throw error;
|
|
6703
7450
|
logger.warn("[tool-activity] Agy session-key refresh failed; window remains retryable", {
|
|
6704
7451
|
sessionId,
|
|
6705
7452
|
error: String(error)
|
|
@@ -6719,7 +7466,7 @@ var McpServer = class _McpServer {
|
|
|
6719
7466
|
* an encrypted keyless one (drop).
|
|
6720
7467
|
*/
|
|
6721
7468
|
encryptOutbound(session, input) {
|
|
6722
|
-
const enc = (0,
|
|
7469
|
+
const enc = (0, import_codevibe_core7.encryptForEmit)(
|
|
6723
7470
|
session.sessionKey ?? null,
|
|
6724
7471
|
session.sessionIsEncrypted ?? false,
|
|
6725
7472
|
input.content,
|
|
@@ -6749,7 +7496,7 @@ var McpServer = class _McpServer {
|
|
|
6749
7496
|
const promptId = input.producerKind === "PLUGIN_APPROVAL" ? input.promptId : void 0;
|
|
6750
7497
|
const promptEntry = promptId ? this.e1PromptRaises.get(promptId) : void 0;
|
|
6751
7498
|
const sendOnce = async () => {
|
|
6752
|
-
if (promptId && !(0,
|
|
7499
|
+
if (promptId && !(0, import_codevibe_core7.isOpenPromptRaise)(
|
|
6753
7500
|
promptEntry,
|
|
6754
7501
|
this.e1PromptRaises.get(promptId),
|
|
6755
7502
|
this.e1NeedsResolution.has(promptId)
|
|
@@ -6762,7 +7509,7 @@ var McpServer = class _McpServer {
|
|
|
6762
7509
|
try {
|
|
6763
7510
|
return await sendOnce();
|
|
6764
7511
|
} catch (error) {
|
|
6765
|
-
if (!(0,
|
|
7512
|
+
if (!(0, import_codevibe_core7.isSessionKeyStaleError)(error)) throw error;
|
|
6766
7513
|
const refreshed = await this.refreshToolActivitySessionKey(session.sessionId);
|
|
6767
7514
|
if (!refreshed) throw error;
|
|
6768
7515
|
return sendOnce();
|
|
@@ -6783,7 +7530,7 @@ var McpServer = class _McpServer {
|
|
|
6783
7530
|
* NOTIFICATION carrier, no fabricated options).
|
|
6784
7531
|
*/
|
|
6785
7532
|
newE1Raise(sessionId, mobileActionable, title) {
|
|
6786
|
-
const { record } = (0,
|
|
7533
|
+
const { record } = (0, import_codevibe_core7.newPromptRaise)({
|
|
6787
7534
|
sessionId,
|
|
6788
7535
|
producerKind: "PLUGIN_APPROVAL",
|
|
6789
7536
|
mobileActionable,
|
|
@@ -6883,26 +7630,26 @@ var McpServer = class _McpServer {
|
|
|
6883
7630
|
const bannerText = customBannerText || "\u26A0\uFE0F A prompt is waiting in your desktop terminal, but its options could not be shown here. Please answer it on your desktop.";
|
|
6884
7631
|
const input = {
|
|
6885
7632
|
sessionId: session.sessionId,
|
|
6886
|
-
type:
|
|
6887
|
-
source:
|
|
7633
|
+
type: import_codevibe_core7.EventType.NOTIFICATION,
|
|
7634
|
+
source: import_codevibe_core7.EventSource.DESKTOP,
|
|
6888
7635
|
content: bannerText,
|
|
6889
7636
|
metadata: {
|
|
6890
7637
|
e1SuppressedPrompt: true
|
|
6891
7638
|
},
|
|
6892
|
-
...(0,
|
|
7639
|
+
...(0, import_codevibe_core7.raiseFieldsFromRecord)(record),
|
|
6893
7640
|
notificationText: bannerText,
|
|
6894
|
-
timestamp: (0,
|
|
7641
|
+
timestamp: (0, import_codevibe_core7.prepareEventTimestamp)({ orderingKey: session.sessionId })
|
|
6895
7642
|
};
|
|
6896
|
-
await this.flushToolActivityAtBoundary(
|
|
7643
|
+
await this.flushToolActivityAtBoundary(import_codevibe_core7.EventType.NOTIFICATION);
|
|
6897
7644
|
return this.publishE1Prompt(session, input, reRaiseToken);
|
|
6898
7645
|
}
|
|
6899
7646
|
async publishE1Prompt(session, input, reRaiseToken) {
|
|
6900
7647
|
const promptId = input.promptId, entry = promptId ? this.e1PromptRaises.get(promptId) : void 0;
|
|
6901
|
-
if (!promptId || !(0,
|
|
7648
|
+
if (!promptId || !(0, import_codevibe_core7.isOpenPromptRaise)(entry, this.e1PromptRaises.get(promptId), this.e1NeedsResolution.has(promptId))) return "CLOSED";
|
|
6902
7649
|
if (!this.started || this.session !== session || input.sessionId !== session.sessionId) return "RETRY";
|
|
6903
7650
|
const { notificationText: _legacyHint, ...plain } = input;
|
|
6904
7651
|
const settle = (disposition) => {
|
|
6905
|
-
if ((0,
|
|
7652
|
+
if ((0, import_codevibe_core7.isOpenPromptRaise)(entry, this.e1PromptRaises.get(promptId), this.e1NeedsResolution.has(promptId))) {
|
|
6906
7653
|
if (disposition === "RETRY") this.e1NeedsReRaise.add(promptId);
|
|
6907
7654
|
else if (disposition === "RERAISED" || disposition === "OMITTED") this.e1NeedsReRaise.delete(promptId);
|
|
6908
7655
|
}
|
|
@@ -7077,7 +7824,7 @@ var McpServer = class _McpServer {
|
|
|
7077
7824
|
async reRaiseOneE1(session, entry, opts) {
|
|
7078
7825
|
const rec = entry.record;
|
|
7079
7826
|
if (this.e1NeedsResolution.has(rec.promptId)) return "CLOSED";
|
|
7080
|
-
if (!(0,
|
|
7827
|
+
if (!(0, import_codevibe_core7.canRecoverPromptRaise)(entry, this.promptPublications.isPublishing(rec.promptId))) {
|
|
7081
7828
|
return "RETRY";
|
|
7082
7829
|
}
|
|
7083
7830
|
const actionable = rec.mobileActionable && entry.contentPlain !== void 0;
|
|
@@ -7085,7 +7832,7 @@ var McpServer = class _McpServer {
|
|
|
7085
7832
|
const gen = opts?.gen;
|
|
7086
7833
|
const MAX_ATTEMPTS = 4;
|
|
7087
7834
|
for (let attempt = 1; ; attempt++) {
|
|
7088
|
-
if (!(0,
|
|
7835
|
+
if (!(0, import_codevibe_core7.isOpenPromptRaise)(
|
|
7089
7836
|
entry,
|
|
7090
7837
|
this.e1PromptRaises.get(rec.promptId),
|
|
7091
7838
|
this.e1NeedsResolution.has(rec.promptId)
|
|
@@ -7103,15 +7850,15 @@ var McpServer = class _McpServer {
|
|
|
7103
7850
|
if (actionable) {
|
|
7104
7851
|
const input = {
|
|
7105
7852
|
sessionId: session.sessionId,
|
|
7106
|
-
type:
|
|
7107
|
-
source:
|
|
7853
|
+
type: import_codevibe_core7.EventType.INTERACTIVE_PROMPT,
|
|
7854
|
+
source: import_codevibe_core7.EventSource.DESKTOP,
|
|
7108
7855
|
content: entry.contentPlain,
|
|
7109
7856
|
metadata: entry.metadataPlain ?? {},
|
|
7110
|
-
...(0,
|
|
7111
|
-
timestamp: (0,
|
|
7857
|
+
...(0, import_codevibe_core7.raiseFieldsFromRecord)(rec),
|
|
7858
|
+
timestamp: (0, import_codevibe_core7.prepareEventTimestamp)({ orderingKey: session.sessionId })
|
|
7112
7859
|
};
|
|
7113
|
-
await this.flushToolActivityAtBoundary(
|
|
7114
|
-
if (!(0,
|
|
7860
|
+
await this.flushToolActivityAtBoundary(import_codevibe_core7.EventType.INTERACTIVE_PROMPT);
|
|
7861
|
+
if (!(0, import_codevibe_core7.isOpenPromptRaise)(
|
|
7115
7862
|
entry,
|
|
7116
7863
|
this.e1PromptRaises.get(rec.promptId),
|
|
7117
7864
|
this.e1NeedsResolution.has(rec.promptId)
|
|
@@ -7191,7 +7938,7 @@ var McpServer = class _McpServer {
|
|
|
7191
7938
|
const session = this.session;
|
|
7192
7939
|
if (!session) return;
|
|
7193
7940
|
const sid = session.sessionId;
|
|
7194
|
-
const promptIds = [...this.e1PromptRaises.values()].filter((e) => e.record.sessionId === sid && !this.e1NeedsResolution.has(e.record.promptId) && (0,
|
|
7941
|
+
const promptIds = [...this.e1PromptRaises.values()].filter((e) => e.record.sessionId === sid && !this.e1NeedsResolution.has(e.record.promptId) && (0, import_codevibe_core7.canRecoverPromptRaise)(e, this.promptPublications.isPublishing(e.record.promptId))).map((e) => e.record.promptId);
|
|
7195
7942
|
if (promptIds.length === 0) return;
|
|
7196
7943
|
try {
|
|
7197
7944
|
await this.appSyncClient.refreshSessionOpenPromptTtl({ sessionId: sid, promptIds }, async (promptId, reRaiseToken) => {
|
|
@@ -7268,6 +8015,7 @@ var McpServer = class _McpServer {
|
|
|
7268
8015
|
error: String(error)
|
|
7269
8016
|
});
|
|
7270
8017
|
}
|
|
8018
|
+
this.transcriptTailer.refreshNativeOwnerBoundary();
|
|
7271
8019
|
this.session = null;
|
|
7272
8020
|
await this.ensureLaunchSession();
|
|
7273
8021
|
if (this.session) await this.drainE1RetirementPark();
|
|
@@ -7302,10 +8050,10 @@ var McpServer = class _McpServer {
|
|
|
7302
8050
|
}
|
|
7303
8051
|
const result = { ...evt };
|
|
7304
8052
|
try {
|
|
7305
|
-
result.content =
|
|
8053
|
+
result.content = import_codevibe_core7.cryptoService.decryptContent(evt.content, session.sessionKey);
|
|
7306
8054
|
const rawMeta = parseMaybeJson(evt.metadata);
|
|
7307
8055
|
if (rawMeta && typeof rawMeta === "object" && typeof rawMeta.encrypted === "string") {
|
|
7308
|
-
const decryptedMeta =
|
|
8056
|
+
const decryptedMeta = import_codevibe_core7.cryptoService.decryptMetadata(
|
|
7309
8057
|
rawMeta.encrypted,
|
|
7310
8058
|
session.sessionKey
|
|
7311
8059
|
);
|
|
@@ -7351,7 +8099,8 @@ var McpServer = class _McpServer {
|
|
|
7351
8099
|
this.raiseSuppressedBadge(session, cand.snapshotHash);
|
|
7352
8100
|
return;
|
|
7353
8101
|
}
|
|
7354
|
-
const
|
|
8102
|
+
const isActionable = Object.keys(parsed.submitMap || {}).length > 0;
|
|
8103
|
+
const e1rec = this.newE1Raise(session.sessionId, isActionable);
|
|
7355
8104
|
const emitted = this.approvalDetector.emitPaneOnlyPrompt(parsed, activeConvId, e1rec.promptId);
|
|
7356
8105
|
if (emitted) {
|
|
7357
8106
|
if (cand.snapshotHash) {
|
|
@@ -7400,10 +8149,11 @@ var McpServer = class _McpServer {
|
|
|
7400
8149
|
});
|
|
7401
8150
|
return;
|
|
7402
8151
|
}
|
|
8152
|
+
const isDisplayOnly = Object.keys(state.submitMap || {}).length === 0;
|
|
7403
8153
|
const e1existing = this.e1PromptRaises.get(state.promptId);
|
|
7404
|
-
const e1rec = e1existing ? e1existing.record : this.newE1Raise(session.sessionId,
|
|
8154
|
+
const e1rec = e1existing ? e1existing.record : this.newE1Raise(session.sessionId, !isDisplayOnly);
|
|
7405
8155
|
if (!e1existing) state.promptId = e1rec.promptId;
|
|
7406
|
-
const optionsArr = state.paneOptions ? state.paneOptions.map((o) => ({ number: o.number, text: o.text })) : Object.keys(state.submitMap).map((n) => ({ number: n }));
|
|
8156
|
+
const optionsArr = isDisplayOnly ? [] : state.paneOptions ? state.paneOptions.map((o) => ({ number: o.number, text: o.text })) : Object.keys(state.submitMap).map((n) => ({ number: n }));
|
|
7407
8157
|
const diffParsed = parseFencedDiffFromBody(
|
|
7408
8158
|
state.body,
|
|
7409
8159
|
state.paneDisplayHeader
|
|
@@ -7438,25 +8188,25 @@ var McpServer = class _McpServer {
|
|
|
7438
8188
|
const content = state.paneDisplayHeader || state.matchedPaneHeader || state.pendingCall?.command || state.pendingCall?.filePath || "approval requested";
|
|
7439
8189
|
const input = {
|
|
7440
8190
|
sessionId: session.sessionId,
|
|
7441
|
-
type:
|
|
7442
|
-
source:
|
|
8191
|
+
type: import_codevibe_core7.EventType.INTERACTIVE_PROMPT,
|
|
8192
|
+
source: import_codevibe_core7.EventSource.DESKTOP,
|
|
7443
8193
|
content,
|
|
7444
8194
|
metadata: optionsForMobile,
|
|
7445
8195
|
// E1 (§4) — raise identity top-level: promptId + ownerToken/producerKind/
|
|
7446
8196
|
// mobileActionable (the backend's classifyRaise upserts the open-prompt row).
|
|
7447
|
-
...(0,
|
|
8197
|
+
...(0, import_codevibe_core7.raiseFieldsFromRecord)(e1rec),
|
|
7448
8198
|
// Per-conversation monotonic timestamp (event-timestamp-ordering fix
|
|
7449
8199
|
// v5.4, 2026-05-24). orderingKey scopes the counter to this conv so
|
|
7450
8200
|
// INTERACTIVE_PROMPT can't be forced to lastMs+1 by a newer event
|
|
7451
8201
|
// from a sibling conversation under v9's multi-conv-per-session
|
|
7452
8202
|
// model.
|
|
7453
|
-
timestamp: (0,
|
|
8203
|
+
timestamp: (0, import_codevibe_core7.prepareEventTimestamp)({
|
|
7454
8204
|
orderingKey: `${session.sessionId}:${state.conversationId}`
|
|
7455
8205
|
})
|
|
7456
8206
|
};
|
|
7457
8207
|
try {
|
|
7458
8208
|
this.stashE1RaiseContent(e1rec.promptId, content, optionsForMobile);
|
|
7459
|
-
await this.flushToolActivityAtBoundary(
|
|
8209
|
+
await this.flushToolActivityAtBoundary(import_codevibe_core7.EventType.INTERACTIVE_PROMPT);
|
|
7460
8210
|
if (this.e1NeedsResolution.has(e1rec.promptId) || this.e1PromptRaises.get(e1rec.promptId)?.record !== e1rec) return;
|
|
7461
8211
|
await this.publishE1Prompt(session, input);
|
|
7462
8212
|
} catch (err) {
|
|
@@ -7504,7 +8254,7 @@ var McpServer = class _McpServer {
|
|
|
7504
8254
|
if (shouldDecrypt && sessionKey) {
|
|
7505
8255
|
try {
|
|
7506
8256
|
logger.info("Decrypting attachment", { id: attachment.id });
|
|
7507
|
-
buffer =
|
|
8257
|
+
buffer = import_codevibe_core7.cryptoService.decryptData(buffer, sessionKey);
|
|
7508
8258
|
logger.info("Attachment decrypted successfully", {
|
|
7509
8259
|
id: attachment.id,
|
|
7510
8260
|
decryptedSize: buffer.length
|
|
@@ -7560,7 +8310,7 @@ var McpServer = class _McpServer {
|
|
|
7560
8310
|
await this.ensureLaunchSession();
|
|
7561
8311
|
if (!this.session) return;
|
|
7562
8312
|
const session = this.session;
|
|
7563
|
-
if (evt.source !==
|
|
8313
|
+
if (evt.source !== import_codevibe_core7.EventSource.MOBILE) return;
|
|
7564
8314
|
if (evt.sessionId !== session.sessionId) {
|
|
7565
8315
|
logger.warn("Dropping mobile event with mismatched sessionId", {
|
|
7566
8316
|
expected: session.sessionId,
|
|
@@ -7572,19 +8322,19 @@ var McpServer = class _McpServer {
|
|
|
7572
8322
|
try {
|
|
7573
8323
|
decrypted = this.decryptInbound(session, evt);
|
|
7574
8324
|
} catch (error) {
|
|
7575
|
-
if ((0,
|
|
8325
|
+
if ((0, import_codevibe_core7.requiresSyncInputDeliveryReceipt)(evt)) throw error;
|
|
7576
8326
|
return;
|
|
7577
8327
|
}
|
|
7578
8328
|
evt = decrypted;
|
|
7579
8329
|
if (!await this.markDelivered(evt)) return;
|
|
7580
|
-
if (evt.type ===
|
|
8330
|
+
if (evt.type === import_codevibe_core7.EventType.USER_PROMPT || evt.type === import_codevibe_core7.EventType.PROMPT_RESPONSE) {
|
|
7581
8331
|
const mobileFloorMs = Date.parse(evt.timestamp);
|
|
7582
8332
|
this.mobilePromptFloorBySession.set(session.sessionId, {
|
|
7583
8333
|
floorMs: Number.isNaN(mobileFloorMs) ? Date.now() : mobileFloorMs,
|
|
7584
8334
|
setAtWallMs: Date.now()
|
|
7585
8335
|
});
|
|
7586
8336
|
}
|
|
7587
|
-
if (evt.type ===
|
|
8337
|
+
if (evt.type === import_codevibe_core7.EventType.USER_PROMPT || evt.type === import_codevibe_core7.EventType.PROMPT_RESPONSE) {
|
|
7588
8338
|
let promptContent = evt.content;
|
|
7589
8339
|
const attachments = evt.attachments ?? [];
|
|
7590
8340
|
if (attachments.length > 0) {
|
|
@@ -7625,16 +8375,16 @@ var McpServer = class _McpServer {
|
|
|
7625
8375
|
const rawMeta = parseMaybeJson(evt.metadata);
|
|
7626
8376
|
const meta = rawMeta && typeof rawMeta === "object" ? rawMeta : {};
|
|
7627
8377
|
const evtAny = evt;
|
|
7628
|
-
const responsePromptId = evt.type ===
|
|
8378
|
+
const responsePromptId = evt.type === import_codevibe_core7.EventType.PROMPT_RESPONSE ? typeof meta.promptId === "string" && meta.promptId || typeof meta.prompt_id === "string" && meta.prompt_id || typeof evtAny.promptId === "string" && evtAny.promptId || typeof evtAny.prompt_id === "string" && evtAny.prompt_id || null : null;
|
|
7629
8379
|
const advertisedOptionNumbers = Array.from(new Set(
|
|
7630
8380
|
pendingPrompts.flatMap((prompt) => Object.keys(prompt.submitMap))
|
|
7631
8381
|
));
|
|
7632
|
-
const intent = (0,
|
|
8382
|
+
const intent = (0, import_codevibe_core7.classifyMobilePromptInput)(
|
|
7633
8383
|
promptContent,
|
|
7634
8384
|
advertisedOptionNumbers
|
|
7635
8385
|
);
|
|
7636
8386
|
if (intent.kind === "option") {
|
|
7637
|
-
const activePrompt = evt.type ===
|
|
8387
|
+
const activePrompt = evt.type === import_codevibe_core7.EventType.PROMPT_RESPONSE ? pendingPrompts.find((prompt) => prompt.promptId === responsePromptId) ?? null : pendingPrompts.length === 1 ? pendingPrompts[0] : null;
|
|
7638
8388
|
if (!activePrompt || !Object.prototype.hasOwnProperty.call(activePrompt.submitMap, intent.option)) {
|
|
7639
8389
|
logger.warn("Numeric input does not identify one current advertised prompt; dropping", {
|
|
7640
8390
|
sessionId: session.sessionId,
|
|
@@ -7644,7 +8394,7 @@ var McpServer = class _McpServer {
|
|
|
7644
8394
|
});
|
|
7645
8395
|
return;
|
|
7646
8396
|
}
|
|
7647
|
-
if (evt.type ===
|
|
8397
|
+
if (evt.type === import_codevibe_core7.EventType.PROMPT_RESPONSE) {
|
|
7648
8398
|
if (responsePromptId !== activePrompt.promptId) {
|
|
7649
8399
|
logger.warn("PROMPT_RESPONSE does not identify the current advertised prompt; dropping", {
|
|
7650
8400
|
sessionId: session.sessionId,
|
|
@@ -7660,7 +8410,7 @@ var McpServer = class _McpServer {
|
|
|
7660
8410
|
intent.option
|
|
7661
8411
|
);
|
|
7662
8412
|
if (!optionResult.ok) {
|
|
7663
|
-
if (!(0,
|
|
8413
|
+
if (!(0, import_codevibe_core7.requiresSyncInputDeliveryReceipt)(evt)) this.mobileDeduper.forget(session.sessionId, intent.option);
|
|
7664
8414
|
return;
|
|
7665
8415
|
}
|
|
7666
8416
|
await this.resolveE1PluginApproval(session.sessionId, activePrompt.promptId);
|
|
@@ -7671,6 +8421,49 @@ var McpServer = class _McpServer {
|
|
|
7671
8421
|
promptContent = intent.followUpText;
|
|
7672
8422
|
await new Promise((resolve2) => setTimeout(resolve2, 250));
|
|
7673
8423
|
} else {
|
|
8424
|
+
const hasDisplayOnlyPrompt = pendingPrompts.some(
|
|
8425
|
+
(p) => Object.keys(p.submitMap || {}).length === 0
|
|
8426
|
+
);
|
|
8427
|
+
let livePaneDisplayOnly = false;
|
|
8428
|
+
let livePaneBlockedReason = null;
|
|
8429
|
+
if (!hasDisplayOnlyPrompt && this.paneObserver) {
|
|
8430
|
+
try {
|
|
8431
|
+
const probe = await this.paneObserver.probeApprovalUIActive();
|
|
8432
|
+
if (!probe.probeSucceeded) {
|
|
8433
|
+
livePaneBlockedReason = "Live terminal state could not be verified safely. Please interact directly in your terminal.";
|
|
8434
|
+
} else if (probe.ambiguous) {
|
|
8435
|
+
livePaneBlockedReason = "An ambiguous prompt is active in your terminal. Please interact directly in your terminal.";
|
|
8436
|
+
} else if (probe.active && probe.kind === "question") {
|
|
8437
|
+
let isActionableWizard = false;
|
|
8438
|
+
try {
|
|
8439
|
+
const liveSnapshot = await this.paneObserver.captureSnapshot();
|
|
8440
|
+
if (liveSnapshot) {
|
|
8441
|
+
const parsed = parseApprovalSnapshot(liveSnapshot);
|
|
8442
|
+
if (parsed && parsed.kind === "question" && Object.keys(parsed.submitMap || {}).length > 0) {
|
|
8443
|
+
isActionableWizard = true;
|
|
8444
|
+
}
|
|
8445
|
+
}
|
|
8446
|
+
} catch {
|
|
8447
|
+
}
|
|
8448
|
+
if (!isActionableWizard) {
|
|
8449
|
+
livePaneDisplayOnly = true;
|
|
8450
|
+
}
|
|
8451
|
+
}
|
|
8452
|
+
} catch {
|
|
8453
|
+
livePaneBlockedReason = "Live terminal state could not be verified safely. Please interact directly in your terminal.";
|
|
8454
|
+
}
|
|
8455
|
+
}
|
|
8456
|
+
if (livePaneBlockedReason) {
|
|
8457
|
+
await this.emitPromptSafetyNotification(session, livePaneBlockedReason);
|
|
8458
|
+
return;
|
|
8459
|
+
}
|
|
8460
|
+
if (hasDisplayOnlyPrompt || livePaneDisplayOnly) {
|
|
8461
|
+
await this.emitPromptSafetyNotification(
|
|
8462
|
+
session,
|
|
8463
|
+
"The active terminal question prompt is display-only on mobile. Please select an option directly in your terminal."
|
|
8464
|
+
);
|
|
8465
|
+
return;
|
|
8466
|
+
}
|
|
7674
8467
|
const dismissal = await this.promptResponder.dismissActivePrompt();
|
|
7675
8468
|
if (!dismissal.ok) {
|
|
7676
8469
|
await this.emitPromptSafetyNotification(
|
|
@@ -7687,7 +8480,7 @@ var McpServer = class _McpServer {
|
|
|
7687
8480
|
() => this.trackDurableMobileEcho(session, evt, promptContent, "free_form")
|
|
7688
8481
|
);
|
|
7689
8482
|
if (!result.ok) {
|
|
7690
|
-
if (!(0,
|
|
8483
|
+
if (!(0, import_codevibe_core7.requiresSyncInputDeliveryReceipt)(evt)) this.mobileDeduper.forget(session.sessionId, promptContent);
|
|
7691
8484
|
logger.warn("sendFreeForm failed", { reason: result.reason, details: result.details });
|
|
7692
8485
|
return;
|
|
7693
8486
|
}
|
|
@@ -7696,7 +8489,7 @@ var McpServer = class _McpServer {
|
|
|
7696
8489
|
}
|
|
7697
8490
|
}
|
|
7698
8491
|
async trackDurableMobileEcho(session, event, content, lane) {
|
|
7699
|
-
if (!(0,
|
|
8492
|
+
if (!(0, import_codevibe_core7.requiresSyncInputDeliveryReceipt)(event)) {
|
|
7700
8493
|
this.mobileDeduper.track(session.sessionId, content);
|
|
7701
8494
|
return;
|
|
7702
8495
|
}
|
|
@@ -7712,13 +8505,13 @@ var McpServer = class _McpServer {
|
|
|
7712
8505
|
async emitPromptSafetyNotification(session, content) {
|
|
7713
8506
|
const input = {
|
|
7714
8507
|
sessionId: session.sessionId,
|
|
7715
|
-
type:
|
|
7716
|
-
source:
|
|
8508
|
+
type: import_codevibe_core7.EventType.NOTIFICATION,
|
|
8509
|
+
source: import_codevibe_core7.EventSource.DESKTOP,
|
|
7717
8510
|
content,
|
|
7718
8511
|
metadata: { promptSafetyBlocked: true },
|
|
7719
|
-
timestamp: (0,
|
|
8512
|
+
timestamp: (0, import_codevibe_core7.prepareEventTimestamp)({ orderingKey: session.sessionId })
|
|
7720
8513
|
};
|
|
7721
|
-
await this.flushToolActivityAtBoundary(
|
|
8514
|
+
await this.flushToolActivityAtBoundary(import_codevibe_core7.EventType.NOTIFICATION);
|
|
7722
8515
|
try {
|
|
7723
8516
|
if (!this.started || this.sessionDisabled || this.session !== session) return;
|
|
7724
8517
|
const result = await this.appSyncClient.publishCompanionNotice(
|
|
@@ -7740,14 +8533,14 @@ var McpServer = class _McpServer {
|
|
|
7740
8533
|
*/
|
|
7741
8534
|
async markDelivered(evt) {
|
|
7742
8535
|
try {
|
|
7743
|
-
return await (0,
|
|
8536
|
+
return await (0, import_codevibe_core7.acknowledgeMobileInputDelivery)(this.appSyncClient.updateEventStatus.bind(this.appSyncClient), evt);
|
|
7744
8537
|
} catch (err) {
|
|
7745
8538
|
logger.warn("updateEventStatus(DELIVERED) failed", {
|
|
7746
8539
|
eventId: evt.eventId,
|
|
7747
8540
|
sessionId: evt.sessionId,
|
|
7748
8541
|
error: String(err)
|
|
7749
8542
|
});
|
|
7750
|
-
if ((0,
|
|
8543
|
+
if ((0, import_codevibe_core7.requiresSyncInputDeliveryReceipt)(evt)) throw err;
|
|
7751
8544
|
return true;
|
|
7752
8545
|
}
|
|
7753
8546
|
}
|
|
@@ -7761,7 +8554,7 @@ var McpServer = class _McpServer {
|
|
|
7761
8554
|
*/
|
|
7762
8555
|
async markExecuted(evt) {
|
|
7763
8556
|
try {
|
|
7764
|
-
await (0,
|
|
8557
|
+
await (0, import_codevibe_core7.acknowledgeMobileInputExecution)(this.appSyncClient.updateEventStatus.bind(this.appSyncClient), evt);
|
|
7765
8558
|
} catch (err) {
|
|
7766
8559
|
logger.warn("updateEventStatus(EXECUTED) failed", {
|
|
7767
8560
|
eventId: evt.eventId,
|
|
@@ -7792,7 +8585,7 @@ var McpServer = class _McpServer {
|
|
|
7792
8585
|
// orderingKey is session-scoped. Slightly coarser than the
|
|
7793
8586
|
// per-conv key used by transcript emits, but acceptable because
|
|
7794
8587
|
// hook-script pushes are off the hot transcript path.
|
|
7795
|
-
timestamp: (0,
|
|
8588
|
+
timestamp: (0, import_codevibe_core7.prepareEventTimestamp)({ orderingKey: payload.sessionId })
|
|
7796
8589
|
};
|
|
7797
8590
|
return this.appSyncClient.publishCompanionEvent(input, async () => {
|
|
7798
8591
|
assertOwner();
|
|
@@ -8166,7 +8959,7 @@ async function main() {
|
|
|
8166
8959
|
tmuxTarget: process.env.CODEVIBE_AGY_TMUX_TARGET ?? void 0,
|
|
8167
8960
|
wrapperPid: process.env.CODEVIBE_AGY_WRAPPER_PID ? parseInt(process.env.CODEVIBE_AGY_WRAPPER_PID, 10) : void 0
|
|
8168
8961
|
});
|
|
8169
|
-
(0,
|
|
8962
|
+
(0, import_codevibe_core7.installDaemonProcessGuards)(logger, {
|
|
8170
8963
|
onFatal: () => {
|
|
8171
8964
|
void server.stop().finally(() => process.exit(1));
|
|
8172
8965
|
}
|
|
@@ -8299,16 +9092,19 @@ var __testing = {
|
|
|
8299
9092
|
SessionNotFoundError,
|
|
8300
9093
|
StoredAuthFailureError,
|
|
8301
9094
|
TERMINAL_SHUTDOWN_SIGNALS,
|
|
9095
|
+
TmuxPaneObserver,
|
|
8302
9096
|
__testing,
|
|
8303
9097
|
classifyEntrypoint,
|
|
8304
9098
|
classifyTmuxHasSessionError,
|
|
8305
9099
|
commandDigest,
|
|
8306
9100
|
detectActivePromptKind,
|
|
9101
|
+
extractFileAccessPrompt,
|
|
8307
9102
|
extractSystemErrorPrompt,
|
|
8308
9103
|
generateLaunchSessionId,
|
|
8309
9104
|
getActiveConversationFromCliLog,
|
|
8310
9105
|
isAgyStatusFooterLine,
|
|
8311
9106
|
isComposedStatusFooter,
|
|
9107
|
+
isPaneInputSafetyBlocked,
|
|
8312
9108
|
isSystemErrorActiveFromIndex,
|
|
8313
9109
|
looksLikePromptSnapshot,
|
|
8314
9110
|
parseApprovalSnapshot,
|