@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.
Files changed (2) hide show
  1. package/dist/server.js +959 -163
  2. 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 import_codevibe_core6 = require("@quantiya/codevibe-core");
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.startTime = Date.now();
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: 0,
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
- for (const link of fileLinks(state.paneDisplayHeader)) promptPaths.add(link);
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
- const isChooser = hasPrompt && kind !== "system_error";
2557
- return { active: isChooser, headerText, probeSucceeded: true, kind };
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 \d+\/\d+:/i.test(chunk) || /\benter Select\b/i.test(chunk) || /^File access\s*$/im.test(chunk) || /Allow access to this file\?/i.test(chunk) || /No, deny access/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)
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 (/^\s*(?:\?\s+for shortcuts|esc to cancel)\b/i.test(trimmed)) {
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 hasQuestionHeader = /Question \d+\/\d+:/i.test(recent);
3052
- const hasQuestionOptions = /^[>\s]*[1-9]\. \S/im.test(recent);
3053
- const hasQuestionFooter = /\benter Select\b/i.test(recent);
3054
- return hasQuestionHeader && hasQuestionOptions && hasQuestionFooter;
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 hasFileAccessQuestion = /Allow access to this file\?/i.test(recent);
3059
- const hasApprovalOptions = /^[>\s]*[1-9]\. (?:Yes|No)\b/im.test(recent);
3060
- const hasApprovalFooter = /\besc to cancel\b/i.test(recent);
3061
- return hasFileAccessHeader && hasFileAccessQuestion && hasApprovalOptions && hasApprovalFooter;
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 headingIndex = -1;
3449
+ let questionIndex = -1;
3073
3450
  for (let index = lines.length - 1; index >= 0; index -= 1) {
3074
- if (/^\s*File access\s*$/i.test(lines[index])) {
3075
- headingIndex = index;
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 readLine = block.slice(0, questionIndex + 1).find((line) => /^\s*Read:\s*\S/i.test(line));
3084
- const identity = readLine?.replace(/^\s*Read:\s*/i, "").trim() || "non-workspace file access";
3085
- const fullLine = block[questionIndex].trim();
3086
- const body = block.slice(1, questionIndex).join("\n").trim();
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 (lastApprovalLineIdx < 0 && approvalRe.test(line)) {
3116
- lastApprovalLineIdx = i;
3697
+ if (approvalRe.test(line)) {
3698
+ if (!lineInFence[i] && lastApprovalLineIdx < 0) {
3699
+ lastApprovalLineIdx = i;
3700
+ }
3117
3701
  }
3118
- if (lastQuestionLineIdx < 0 && questionRe.test(line)) {
3119
- lastQuestionLineIdx = i;
3702
+ if (isQuestionHeaderLine(line)) {
3703
+ if (!lineInFence[i] && lastQuestionLineIdx < 0) {
3704
+ lastQuestionLineIdx = i;
3705
+ }
3120
3706
  }
3121
- if (lastFileAccessLineIdx < 0 && fileAccessRe.test(line)) {
3122
- lastFileAccessLineIdx = i;
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
- lastApprovalLineIdx = i;
3134
- break;
3723
+ if (!lineInFence[i]) {
3724
+ lastApprovalLineIdx = i;
3725
+ break;
3726
+ }
3135
3727
  }
3136
3728
  }
3137
3729
  }
3138
- const latestChooserIdx = Math.max(
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
- const hasQuestionOptions = /^[>\s]*[1-9]\. \S/im.test(remainingLines);
3148
- const hasQuestionFooter = /\benter Select\b/i.test(remainingLines);
3149
- if (hasQuestionOptions && hasQuestionFooter) {
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 = /Allow access to this file\?/i.test(remainingLines);
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 = /\besc to cancel\b/i.test(remainingLines);
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 matches = Array.from(stripped.matchAll(/Question \d+\/\d+:\s*([^\n]+)/gi));
3530
- if (matches.length === 0) return null;
3531
- const last = matches[matches.length - 1];
3532
- return { fullLine: last[0].trim(), body: last[1].trim() };
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
- const stripped = snapshot.replace(ANSI_ESCAPE_REGEX, "");
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 (/Allow access to this file\?/i.test(lines[index])) {
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 (/Question \d+\/\d+:/i.test(lines[i])) {
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 submitMap = buildQuestionSubmitMap(options);
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])\. (.+?)\s*$/;
3701
- const fenceRegex = /^```/;
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 (fenceRegex.test(lines[i])) {
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 recent = snapshot.split("\n").slice(-30);
3720
- const optionRegex = /^[>\s]*([1-9])\. (.+?)\s*$/;
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 import_codevibe_core4 = require("@quantiya/codevibe-core");
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: import_codevibe_core4.EventSource.DESKTOP
5556
+ source: import_codevibe_core5.EventSource.DESKTOP
4847
5557
  };
4848
5558
  const orderingKey = `${sessionId}:${emit.conversationId}`;
4849
- const nextTimestamp = () => (0, import_codevibe_core4.prepareEventTimestamp)({ orderingKey, agentClock: event.created_at, notBeforeMs });
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: import_codevibe_core4.EventType.USER_PROMPT,
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: import_codevibe_core4.EventType.REASONING,
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: import_codevibe_core4.EventType.ASSISTANT_RESPONSE,
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: import_codevibe_core4.EventType.NOTIFICATION,
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: import_codevibe_core4.EventType.TOOL_USE,
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 import_codevibe_core5 = require("@quantiya/codevibe-core");
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, import_codevibe_core5.digestOf)(emit.event.content ?? "")
5933
+ contentDigest: (0, import_codevibe_core6.digestOf)(emit.event.content ?? "")
5196
5934
  };
5197
- return `agy:${(0, import_codevibe_core5.digestOf)(identity).slice("sha256:".length)}`;
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 import_codevibe_core5.ToolActivityOutbox({
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 import_codevibe_core5.ToolActivityLedger({
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 import_codevibe_core5.ToolActivityConsolidator({
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, import_codevibe_core5.digestOf)(inputIdentity) } : {}
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, import_codevibe_core5.encryptForEmit)(
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: import_codevibe_core5.EventType.TOOL_ACTIVITY,
5348
- source: import_codevibe_core5.EventSource.DESKTOP,
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, import_codevibe_core5.isTerminalSessionError)(error)) {
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, import_codevibe_core5.isSessionKeyStaleError)(error) && this.deps.refreshSessionKey) {
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 import_codevibe_core6.PromptPublicationTracker();
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 import_codevibe_core6.AppSyncClient();
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, import_codevibe_core6.describeStoredAuthFailure)(
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, import_codevibe_core6.registerDeviceEncryptionKey)(this.appSyncClient, logger);
6511
+ await (0, import_codevibe_core7.registerDeviceEncryptionKey)(this.appSyncClient, logger);
5771
6512
  if (!this.started) return { httpPort: 0 };
5772
- (0, import_codevibe_core6.startDeviceKeyWatcher)(this.appSyncClient, logger);
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) return;
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({ sessionId }, retired, async () => {
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, import_codevibe_core6.resumeOrCreateSession)(
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, import_codevibe_core6.requiresSyncInputDeliveryReceipt)(evt)) throw err;
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 === import_codevibe_core6.EventType.USER_PROMPT || input.type === import_codevibe_core6.EventType.ASSISTANT_RESPONSE || input.type === import_codevibe_core6.EventType.NOTIFICATION) {
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 === import_codevibe_core6.EventType.TOOL_USE && typeof input.metadata === "object" && input.metadata) {
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 === import_codevibe_core6.EventType.USER_PROMPT && input.source === import_codevibe_core6.EventSource.DESKTOP) {
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 === import_codevibe_core6.EventType.USER_PROMPT && await echoes.claim(echoFrame, input.content, emit.event.created_at)) admission.outputs[index] = null;
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 === import_codevibe_core6.SessionStatus.INACTIVE) {
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 !== import_codevibe_core6.SessionStatus.ACTIVE || !session.isEncrypted) {
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 import_codevibe_core6.keychainManager.getSessionKey(
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, import_codevibe_core6.isTerminalSessionError)(error)) throw error;
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, import_codevibe_core6.encryptForEmit)(
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, import_codevibe_core6.isOpenPromptRaise)(
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, import_codevibe_core6.isSessionKeyStaleError)(error)) throw error;
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, import_codevibe_core6.newPromptRaise)({
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: import_codevibe_core6.EventType.NOTIFICATION,
6887
- source: import_codevibe_core6.EventSource.DESKTOP,
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, import_codevibe_core6.raiseFieldsFromRecord)(record),
7639
+ ...(0, import_codevibe_core7.raiseFieldsFromRecord)(record),
6893
7640
  notificationText: bannerText,
6894
- timestamp: (0, import_codevibe_core6.prepareEventTimestamp)({ orderingKey: session.sessionId })
7641
+ timestamp: (0, import_codevibe_core7.prepareEventTimestamp)({ orderingKey: session.sessionId })
6895
7642
  };
6896
- await this.flushToolActivityAtBoundary(import_codevibe_core6.EventType.NOTIFICATION);
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, import_codevibe_core6.isOpenPromptRaise)(entry, this.e1PromptRaises.get(promptId), this.e1NeedsResolution.has(promptId))) return "CLOSED";
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, import_codevibe_core6.isOpenPromptRaise)(entry, this.e1PromptRaises.get(promptId), this.e1NeedsResolution.has(promptId))) {
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, import_codevibe_core6.canRecoverPromptRaise)(entry, this.promptPublications.isPublishing(rec.promptId))) {
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, import_codevibe_core6.isOpenPromptRaise)(
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: import_codevibe_core6.EventType.INTERACTIVE_PROMPT,
7107
- source: import_codevibe_core6.EventSource.DESKTOP,
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, import_codevibe_core6.raiseFieldsFromRecord)(rec),
7111
- timestamp: (0, import_codevibe_core6.prepareEventTimestamp)({ orderingKey: session.sessionId })
7857
+ ...(0, import_codevibe_core7.raiseFieldsFromRecord)(rec),
7858
+ timestamp: (0, import_codevibe_core7.prepareEventTimestamp)({ orderingKey: session.sessionId })
7112
7859
  };
7113
- await this.flushToolActivityAtBoundary(import_codevibe_core6.EventType.INTERACTIVE_PROMPT);
7114
- if (!(0, import_codevibe_core6.isOpenPromptRaise)(
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, import_codevibe_core6.canRecoverPromptRaise)(e, this.promptPublications.isPublishing(e.record.promptId))).map((e) => e.record.promptId);
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 = import_codevibe_core6.cryptoService.decryptContent(evt.content, session.sessionKey);
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 = import_codevibe_core6.cryptoService.decryptMetadata(
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 e1rec = this.newE1Raise(session.sessionId, true);
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, true);
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: import_codevibe_core6.EventType.INTERACTIVE_PROMPT,
7442
- source: import_codevibe_core6.EventSource.DESKTOP,
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, import_codevibe_core6.raiseFieldsFromRecord)(e1rec),
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, import_codevibe_core6.prepareEventTimestamp)({
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(import_codevibe_core6.EventType.INTERACTIVE_PROMPT);
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 = import_codevibe_core6.cryptoService.decryptData(buffer, sessionKey);
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 !== import_codevibe_core6.EventSource.MOBILE) return;
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, import_codevibe_core6.requiresSyncInputDeliveryReceipt)(evt)) throw error;
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 === import_codevibe_core6.EventType.USER_PROMPT || evt.type === import_codevibe_core6.EventType.PROMPT_RESPONSE) {
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 === import_codevibe_core6.EventType.USER_PROMPT || evt.type === import_codevibe_core6.EventType.PROMPT_RESPONSE) {
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 === import_codevibe_core6.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;
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, import_codevibe_core6.classifyMobilePromptInput)(
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 === import_codevibe_core6.EventType.PROMPT_RESPONSE ? pendingPrompts.find((prompt) => prompt.promptId === responsePromptId) ?? null : pendingPrompts.length === 1 ? pendingPrompts[0] : null;
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 === import_codevibe_core6.EventType.PROMPT_RESPONSE) {
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, import_codevibe_core6.requiresSyncInputDeliveryReceipt)(evt)) this.mobileDeduper.forget(session.sessionId, intent.option);
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, import_codevibe_core6.requiresSyncInputDeliveryReceipt)(evt)) this.mobileDeduper.forget(session.sessionId, promptContent);
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, import_codevibe_core6.requiresSyncInputDeliveryReceipt)(event)) {
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: import_codevibe_core6.EventType.NOTIFICATION,
7716
- source: import_codevibe_core6.EventSource.DESKTOP,
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, import_codevibe_core6.prepareEventTimestamp)({ orderingKey: session.sessionId })
8512
+ timestamp: (0, import_codevibe_core7.prepareEventTimestamp)({ orderingKey: session.sessionId })
7720
8513
  };
7721
- await this.flushToolActivityAtBoundary(import_codevibe_core6.EventType.NOTIFICATION);
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, import_codevibe_core6.acknowledgeMobileInputDelivery)(this.appSyncClient.updateEventStatus.bind(this.appSyncClient), evt);
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, import_codevibe_core6.requiresSyncInputDeliveryReceipt)(evt)) throw err;
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, import_codevibe_core6.acknowledgeMobileInputExecution)(this.appSyncClient.updateEventStatus.bind(this.appSyncClient), evt);
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, import_codevibe_core6.prepareEventTimestamp)({ orderingKey: payload.sessionId })
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, import_codevibe_core6.installDaemonProcessGuards)(logger, {
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,