@threadbase-sh/streamer 1.44.4 → 1.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1775,8 +1775,11 @@ declare class PTYManager implements SessionRunner {
1775
1775
  private onLiveQuestionGone;
1776
1776
  private onUserMessage;
1777
1777
  private permissionOpen;
1778
+ private oscTail;
1779
+ private lastDetectAt;
1778
1780
  private lastScreenQuestionKey;
1779
1781
  private shellPromptOpen;
1782
+ private closedGateKey;
1780
1783
  private pendingReady;
1781
1784
  private queuedInputs;
1782
1785
  private log;
package/dist/index.d.ts CHANGED
@@ -1775,8 +1775,11 @@ declare class PTYManager implements SessionRunner {
1775
1775
  private onLiveQuestionGone;
1776
1776
  private onUserMessage;
1777
1777
  private permissionOpen;
1778
+ private oscTail;
1779
+ private lastDetectAt;
1778
1780
  private lastScreenQuestionKey;
1779
1781
  private shellPromptOpen;
1782
+ private closedGateKey;
1780
1783
  private pendingReady;
1781
1784
  private queuedInputs;
1782
1785
  private log;
package/dist/index.js CHANGED
@@ -2120,14 +2120,19 @@ function scrapePermissionGate(lines) {
2120
2120
  const options = [];
2121
2121
  let cursor;
2122
2122
  let firstOptionLine = -1;
2123
- for (let i = 0; i < lines.length; i++) {
2124
- const m = OPTION_RE.exec(stripGutter(lines[i]));
2125
- if (!m) continue;
2126
- const index = Number.parseInt(m[2], 10);
2127
- if (!Number.isFinite(index)) continue;
2128
- if (firstOptionLine === -1) firstOptionLine = i;
2129
- if (m[1]) cursor = index;
2130
- options.push({ index, label: m[3] });
2123
+ for (let i = lines.length - 1; i >= 0; i--) {
2124
+ const stripped = stripGutter(lines[i]);
2125
+ const m = OPTION_RE.exec(stripped);
2126
+ if (m) {
2127
+ const index = Number.parseInt(m[2], 10);
2128
+ if (!Number.isFinite(index)) continue;
2129
+ firstOptionLine = i;
2130
+ if (m[1]) cursor = index;
2131
+ options.unshift({ index, label: m[3] });
2132
+ continue;
2133
+ }
2134
+ if (options.length === 0) continue;
2135
+ if (stripped.trim().length === 0 || BOX_ONLY_RE.test(lines[i].trim())) break;
2131
2136
  }
2132
2137
  if (options.length === 0) return null;
2133
2138
  let prompt;
@@ -2167,6 +2172,17 @@ function scrapeDetail(lines, promptLine) {
2167
2172
  }
2168
2173
  return collected.length > 0 ? collected.reverse().join("\n") : void 0;
2169
2174
  }
2175
+ var GATE_FOOTER_RE = /esc to cancel/i;
2176
+ var ASK_MENU_FOOTER_RE = /Enter to select/i;
2177
+ var YES_NO_LABEL_RE = /^(yes|no)\b/i;
2178
+ function detectGateScreen(lines) {
2179
+ if (lines.some((l) => ASK_MENU_FOOTER_RE.test(l))) return null;
2180
+ if (!lines.some((l) => GATE_FOOTER_RE.test(l))) return null;
2181
+ const gate = scrapePermissionGate(lines);
2182
+ if (!gate || gate.options.length < 2) return null;
2183
+ if (!gate.options.some((o) => YES_NO_LABEL_RE.test(o.label))) return null;
2184
+ return gate;
2185
+ }
2170
2186
 
2171
2187
  // src/services/questions/detectQuestionFromScreen.ts
2172
2188
  var ASK_FOOTER_RE = /Enter to select/i;
@@ -2314,6 +2330,8 @@ var PTY_ROWS2 = 40;
2314
2330
  var SCREEN_SCROLLBACK2 = 1e3;
2315
2331
  var CLAUDE_PROMPT_MARKERS = ["\u256D", "\u276F"];
2316
2332
  var QUIET_DETECT_MS2 = 500;
2333
+ var OSC_TAIL_CHARS = 128;
2334
+ var SCRAPE_THROTTLE_MS = 300;
2317
2335
  var CLAUDE_READY_FALLBACK_MS = 8e3;
2318
2336
  function buildPasteBytes(input) {
2319
2337
  return `\x1B[200~${input}\x1B[201~`;
@@ -2372,6 +2390,12 @@ var PTYManager = class {
2372
2390
  // the next prompt-ready without a fresh 777 (gate closed). Prevents
2373
2391
  // re-broadcasting open/close on every chunk.
2374
2392
  permissionOpen = /* @__PURE__ */ new Set();
2393
+ // Last chunk's raw tail per session — prepended to the next chunk before
2394
+ // the OSC regex test so a split escape still matches. Consumed on match.
2395
+ oscTail = /* @__PURE__ */ new Map();
2396
+ // When the last full detection pass ran per session (any trigger) — the
2397
+ // clock the SCRAPE_THROTTLE_MS ceiling is measured against.
2398
+ lastDetectAt = /* @__PURE__ */ new Map();
2375
2399
  // Content key of the last AskUserQuestion broadcast from the rendered screen,
2376
2400
  // per session — de-dupes the same menu firing on consecutive repaints.
2377
2401
  lastScreenQuestionKey = /* @__PURE__ */ new Map();
@@ -2380,6 +2404,13 @@ var PTYManager = class {
2380
2404
  // on a prompt-ready/marker return and de-dupe consecutive repaints. Modelled
2381
2405
  // on permissionOpen but keyed by content (a shell prompt has no OSC trigger).
2382
2406
  shellPromptOpen = /* @__PURE__ */ new Map();
2407
+ // Content key (permissionContentKey, cursor excluded — see arm 3) of the
2408
+ // gate that was still painted on screen when arm 2 closed it. Suppresses
2409
+ // arm 3 re-claiming that same box from a later trigger-less/throttled pass
2410
+ // before Claude erases it — otherwise the paint-time claim reopens the
2411
+ // card it just closed. Cleared once detectGateScreen sees the box is gone,
2412
+ // so a genuinely new (even content-identical) gate is claimed normally.
2413
+ closedGateKey = /* @__PURE__ */ new Map();
2383
2414
  // Tracks sessions (both fresh and resume) whose PTY has spawned but Claude
2384
2415
  // hasn't yet reached an interactive prompt — i.e. onReady hasn't fired.
2385
2416
  pendingReady = /* @__PURE__ */ new Set();
@@ -2733,8 +2764,11 @@ var PTYManager = class {
2733
2764
  this.queuedInputs.delete(sessionId);
2734
2765
  this.firstChunkAt.delete(sessionId);
2735
2766
  this.permissionOpen.delete(sessionId);
2767
+ this.oscTail.delete(sessionId);
2768
+ this.lastDetectAt.delete(sessionId);
2736
2769
  this.lastScreenQuestionKey.delete(sessionId);
2737
2770
  this.shellPromptOpen.delete(sessionId);
2771
+ this.closedGateKey.delete(sessionId);
2738
2772
  this.quietCheckers.get(sessionId)?.cancel();
2739
2773
  this.quietCheckers.delete(sessionId);
2740
2774
  this.clearReadyFallback(sessionId);
@@ -2829,8 +2863,11 @@ var PTYManager = class {
2829
2863
  for (const timer of this.readyFallbackTimers.values()) clearTimeout(timer);
2830
2864
  this.readyFallbackTimers.clear();
2831
2865
  this.permissionOpen.clear();
2866
+ this.oscTail.clear();
2867
+ this.lastDetectAt.clear();
2832
2868
  this.lastScreenQuestionKey.clear();
2833
2869
  this.shellPromptOpen.clear();
2870
+ this.closedGateKey.clear();
2834
2871
  }
2835
2872
  handleOutput(sessionId, data) {
2836
2873
  const session = this.sessions.get(sessionId);
@@ -2894,16 +2931,24 @@ var PTYManager = class {
2894
2931
  async detectLivePrompts(sessionId, rawData, stripped) {
2895
2932
  const session = this.sessions.get(sessionId);
2896
2933
  if (!session) return;
2897
- const oscPermission = hasPermissionOsc(rawData);
2898
- const oscWaitingForInput = hasWaitingForInputOsc(rawData);
2934
+ const oscWindow = (this.oscTail.get(sessionId) ?? "") + rawData;
2935
+ const oscPermission = hasPermissionOsc(oscWindow);
2936
+ const oscWaitingForInput = hasWaitingForInputOsc(oscWindow);
2937
+ if (rawData !== "") {
2938
+ if (oscPermission || oscWaitingForInput) this.oscTail.delete(sessionId);
2939
+ else this.oscTail.set(sessionId, oscWindow.slice(-OSC_TAIL_CHARS));
2940
+ }
2899
2941
  const hasAskFooter = /Enter to select/i.test(stripped);
2900
2942
  const hasPromptMarker = CLAUDE_PROMPT_MARKERS.some((m) => stripped.includes(m));
2901
2943
  const hasShellPromptHint = /[[(]\s*y\s*\/\s*n\s*[\])]|press\s+(enter|return|any key)|\bcontinue\b\s*\?|^\s*(?:❯|>)?\s*\d+[.)]\s+\S/im.test(
2902
2944
  stripped
2903
2945
  );
2904
- if (!oscPermission && !oscWaitingForInput && !hasAskFooter && !hasShellPromptHint && !this.permissionOpen.has(sessionId) && !this.shellPromptOpen.has(sessionId) && !this.lastScreenQuestionKey.has(sessionId)) {
2946
+ const nowMs = Date.now();
2947
+ const scrapeDue = nowMs - (this.lastDetectAt.get(sessionId) ?? 0) >= SCRAPE_THROTTLE_MS;
2948
+ if (!oscPermission && !oscWaitingForInput && !hasAskFooter && !hasShellPromptHint && !scrapeDue && !this.permissionOpen.has(sessionId) && !this.shellPromptOpen.has(sessionId) && !this.lastScreenQuestionKey.has(sessionId)) {
2905
2949
  return;
2906
2950
  }
2951
+ this.lastDetectAt.set(sessionId, nowMs);
2907
2952
  const lines = await this.getOutputLines(sessionId, 60);
2908
2953
  const askFooterOnScreen = lines.some((l) => /Enter to select/i.test(l));
2909
2954
  if (oscPermission || hasAskFooter || askFooterOnScreen) {
@@ -2923,13 +2968,30 @@ var PTYManager = class {
2923
2968
  this.permissionOpen.add(sessionId);
2924
2969
  this.onPermissionChange?.(sessionId, gate ?? { options: [] });
2925
2970
  } else if (this.permissionOpen.has(sessionId) && !askFooterOnScreen) {
2926
- const gate = scrapePermissionGate(lines);
2927
- if (oscWaitingForInput || !gate && hasPromptMarker) {
2971
+ const gate = detectGateScreen(lines);
2972
+ const stillPainted = gate !== null || scrapePermissionGate(lines) !== null;
2973
+ if (oscWaitingForInput || !stillPainted && hasPromptMarker) {
2928
2974
  this.permissionOpen.delete(sessionId);
2929
2975
  this.onPermissionChange?.(sessionId, null);
2976
+ if (gate) {
2977
+ this.closedGateKey.set(sessionId, permissionContentKey({ ...gate, cursor: void 0 }));
2978
+ } else {
2979
+ this.closedGateKey.delete(sessionId);
2980
+ }
2930
2981
  } else if (gate) {
2931
2982
  this.onPermissionChange?.(sessionId, gate);
2932
2983
  }
2984
+ } else if (!askFooterOnScreen && !oscWaitingForInput) {
2985
+ const gate = detectGateScreen(lines);
2986
+ if (gate) {
2987
+ const key = permissionContentKey({ ...gate, cursor: void 0 });
2988
+ if (this.closedGateKey.get(sessionId) !== key) {
2989
+ this.permissionOpen.add(sessionId);
2990
+ this.onPermissionChange?.(sessionId, gate);
2991
+ }
2992
+ } else {
2993
+ this.closedGateKey.delete(sessionId);
2994
+ }
2933
2995
  }
2934
2996
  if (askFooterOnScreen) {
2935
2997
  const detected = detectQuestionFromScreen(lines);
@@ -3072,8 +3134,11 @@ var PTYManager = class {
3072
3134
  this.queuedInputs.delete(sessionId);
3073
3135
  this.firstChunkAt.delete(sessionId);
3074
3136
  this.permissionOpen.delete(sessionId);
3137
+ this.oscTail.delete(sessionId);
3138
+ this.lastDetectAt.delete(sessionId);
3075
3139
  this.lastScreenQuestionKey.delete(sessionId);
3076
3140
  this.shellPromptOpen.delete(sessionId);
3141
+ this.closedGateKey.delete(sessionId);
3077
3142
  this.quietCheckers.get(sessionId)?.cancel();
3078
3143
  this.quietCheckers.delete(sessionId);
3079
3144
  this.clearReadyFallback(sessionId);