@threadbase-sh/streamer 1.44.3 → 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/cli.cjs CHANGED
@@ -114168,14 +114168,19 @@ function scrapePermissionGate(lines) {
114168
114168
  const options = [];
114169
114169
  let cursor;
114170
114170
  let firstOptionLine = -1;
114171
- for (let i = 0; i < lines.length; i++) {
114172
- const m2 = OPTION_RE.exec(stripGutter(lines[i]));
114173
- if (!m2) continue;
114174
- const index = Number.parseInt(m2[2], 10);
114175
- if (!Number.isFinite(index)) continue;
114176
- if (firstOptionLine === -1) firstOptionLine = i;
114177
- if (m2[1]) cursor = index;
114178
- options.push({ index, label: m2[3] });
114171
+ for (let i = lines.length - 1; i >= 0; i--) {
114172
+ const stripped = stripGutter(lines[i]);
114173
+ const m2 = OPTION_RE.exec(stripped);
114174
+ if (m2) {
114175
+ const index = Number.parseInt(m2[2], 10);
114176
+ if (!Number.isFinite(index)) continue;
114177
+ firstOptionLine = i;
114178
+ if (m2[1]) cursor = index;
114179
+ options.unshift({ index, label: m2[3] });
114180
+ continue;
114181
+ }
114182
+ if (options.length === 0) continue;
114183
+ if (stripped.trim().length === 0 || BOX_ONLY_RE.test(lines[i].trim())) break;
114179
114184
  }
114180
114185
  if (options.length === 0) return null;
114181
114186
  let prompt;
@@ -114214,7 +114219,15 @@ function scrapeDetail(lines, promptLine) {
114214
114219
  }
114215
114220
  return collected.length > 0 ? collected.reverse().join("\n") : void 0;
114216
114221
  }
114217
- var OSC_777_PERMISSION_RE, OSC_777_WAITING_RE, OPTION_RE, FOOTER_RE, BOX_ONLY_RE, PROMPT_ARROW_RE, MAX_DETAIL_LINES;
114222
+ function detectGateScreen(lines) {
114223
+ if (lines.some((l) => ASK_MENU_FOOTER_RE.test(l))) return null;
114224
+ if (!lines.some((l) => GATE_FOOTER_RE.test(l))) return null;
114225
+ const gate = scrapePermissionGate(lines);
114226
+ if (!gate || gate.options.length < 2) return null;
114227
+ if (!gate.options.some((o) => YES_NO_LABEL_RE.test(o.label))) return null;
114228
+ return gate;
114229
+ }
114230
+ var OSC_777_PERMISSION_RE, OSC_777_WAITING_RE, OPTION_RE, FOOTER_RE, BOX_ONLY_RE, PROMPT_ARROW_RE, MAX_DETAIL_LINES, GATE_FOOTER_RE, ASK_MENU_FOOTER_RE, YES_NO_LABEL_RE;
114218
114231
  var init_detectPermissionGate = __esm({
114219
114232
  "src/services/questions/detectPermissionGate.ts"() {
114220
114233
  "use strict";
@@ -114225,6 +114238,9 @@ var init_detectPermissionGate = __esm({
114225
114238
  BOX_ONLY_RE = /^[\s│─┌┐└┘├┤┬┴┼╭╮╰╯╱╲=_-]+$/;
114226
114239
  PROMPT_ARROW_RE = /^[\s]*[❯›>]\s*$/;
114227
114240
  MAX_DETAIL_LINES = 6;
114241
+ GATE_FOOTER_RE = /esc to cancel/i;
114242
+ ASK_MENU_FOOTER_RE = /Enter to select/i;
114243
+ YES_NO_LABEL_RE = /^(yes|no)\b/i;
114228
114244
  }
114229
114245
  });
114230
114246
 
@@ -114448,7 +114464,7 @@ function toPublicSession2(s3) {
114448
114464
  function stripAnsi2(str) {
114449
114465
  return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1b\][^\x07]*\x07/g, "");
114450
114466
  }
114451
- var import_headless2, import_crypto9, import_fs22, import_path22, OUTPUT_BUFFER_MAX2, INPUT_HISTORY_MAX2, PTY_COLS2, PTY_ROWS2, SCREEN_SCROLLBACK2, CLAUDE_PROMPT_MARKERS, QUIET_DETECT_MS2, CLAUDE_READY_FALLBACK_MS, SUBMIT_BYTES2, SUBMIT_DELAY_MS, SUBMIT_MAX_WAIT_MS, pty2, PTYManager;
114467
+ var import_headless2, import_crypto9, import_fs22, import_path22, OUTPUT_BUFFER_MAX2, INPUT_HISTORY_MAX2, PTY_COLS2, PTY_ROWS2, SCREEN_SCROLLBACK2, CLAUDE_PROMPT_MARKERS, QUIET_DETECT_MS2, OSC_TAIL_CHARS, SCRAPE_THROTTLE_MS, CLAUDE_READY_FALLBACK_MS, SUBMIT_BYTES2, SUBMIT_DELAY_MS, SUBMIT_MAX_WAIT_MS, pty2, PTYManager;
114452
114468
  var init_pty_manager = __esm({
114453
114469
  "src/pty-manager.ts"() {
114454
114470
  "use strict";
@@ -114472,6 +114488,8 @@ var init_pty_manager = __esm({
114472
114488
  SCREEN_SCROLLBACK2 = 1e3;
114473
114489
  CLAUDE_PROMPT_MARKERS = ["\u256D", "\u276F"];
114474
114490
  QUIET_DETECT_MS2 = 500;
114491
+ OSC_TAIL_CHARS = 128;
114492
+ SCRAPE_THROTTLE_MS = 300;
114475
114493
  CLAUDE_READY_FALLBACK_MS = 8e3;
114476
114494
  SUBMIT_BYTES2 = "\r";
114477
114495
  SUBMIT_DELAY_MS = 16;
@@ -114490,6 +114508,12 @@ var init_pty_manager = __esm({
114490
114508
  // the next prompt-ready without a fresh 777 (gate closed). Prevents
114491
114509
  // re-broadcasting open/close on every chunk.
114492
114510
  permissionOpen = /* @__PURE__ */ new Set();
114511
+ // Last chunk's raw tail per session — prepended to the next chunk before
114512
+ // the OSC regex test so a split escape still matches. Consumed on match.
114513
+ oscTail = /* @__PURE__ */ new Map();
114514
+ // When the last full detection pass ran per session (any trigger) — the
114515
+ // clock the SCRAPE_THROTTLE_MS ceiling is measured against.
114516
+ lastDetectAt = /* @__PURE__ */ new Map();
114493
114517
  // Content key of the last AskUserQuestion broadcast from the rendered screen,
114494
114518
  // per session — de-dupes the same menu firing on consecutive repaints.
114495
114519
  lastScreenQuestionKey = /* @__PURE__ */ new Map();
@@ -114498,6 +114522,13 @@ var init_pty_manager = __esm({
114498
114522
  // on a prompt-ready/marker return and de-dupe consecutive repaints. Modelled
114499
114523
  // on permissionOpen but keyed by content (a shell prompt has no OSC trigger).
114500
114524
  shellPromptOpen = /* @__PURE__ */ new Map();
114525
+ // Content key (permissionContentKey, cursor excluded — see arm 3) of the
114526
+ // gate that was still painted on screen when arm 2 closed it. Suppresses
114527
+ // arm 3 re-claiming that same box from a later trigger-less/throttled pass
114528
+ // before Claude erases it — otherwise the paint-time claim reopens the
114529
+ // card it just closed. Cleared once detectGateScreen sees the box is gone,
114530
+ // so a genuinely new (even content-identical) gate is claimed normally.
114531
+ closedGateKey = /* @__PURE__ */ new Map();
114501
114532
  // Tracks sessions (both fresh and resume) whose PTY has spawned but Claude
114502
114533
  // hasn't yet reached an interactive prompt — i.e. onReady hasn't fired.
114503
114534
  pendingReady = /* @__PURE__ */ new Set();
@@ -114851,8 +114882,11 @@ var init_pty_manager = __esm({
114851
114882
  this.queuedInputs.delete(sessionId);
114852
114883
  this.firstChunkAt.delete(sessionId);
114853
114884
  this.permissionOpen.delete(sessionId);
114885
+ this.oscTail.delete(sessionId);
114886
+ this.lastDetectAt.delete(sessionId);
114854
114887
  this.lastScreenQuestionKey.delete(sessionId);
114855
114888
  this.shellPromptOpen.delete(sessionId);
114889
+ this.closedGateKey.delete(sessionId);
114856
114890
  this.quietCheckers.get(sessionId)?.cancel();
114857
114891
  this.quietCheckers.delete(sessionId);
114858
114892
  this.clearReadyFallback(sessionId);
@@ -114947,8 +114981,11 @@ var init_pty_manager = __esm({
114947
114981
  for (const timer of this.readyFallbackTimers.values()) clearTimeout(timer);
114948
114982
  this.readyFallbackTimers.clear();
114949
114983
  this.permissionOpen.clear();
114984
+ this.oscTail.clear();
114985
+ this.lastDetectAt.clear();
114950
114986
  this.lastScreenQuestionKey.clear();
114951
114987
  this.shellPromptOpen.clear();
114988
+ this.closedGateKey.clear();
114952
114989
  }
114953
114990
  handleOutput(sessionId, data) {
114954
114991
  const session = this.sessions.get(sessionId);
@@ -115012,16 +115049,24 @@ var init_pty_manager = __esm({
115012
115049
  async detectLivePrompts(sessionId, rawData, stripped) {
115013
115050
  const session = this.sessions.get(sessionId);
115014
115051
  if (!session) return;
115015
- const oscPermission = hasPermissionOsc(rawData);
115016
- const oscWaitingForInput = hasWaitingForInputOsc(rawData);
115052
+ const oscWindow = (this.oscTail.get(sessionId) ?? "") + rawData;
115053
+ const oscPermission = hasPermissionOsc(oscWindow);
115054
+ const oscWaitingForInput = hasWaitingForInputOsc(oscWindow);
115055
+ if (rawData !== "") {
115056
+ if (oscPermission || oscWaitingForInput) this.oscTail.delete(sessionId);
115057
+ else this.oscTail.set(sessionId, oscWindow.slice(-OSC_TAIL_CHARS));
115058
+ }
115017
115059
  const hasAskFooter = /Enter to select/i.test(stripped);
115018
115060
  const hasPromptMarker = CLAUDE_PROMPT_MARKERS.some((m2) => stripped.includes(m2));
115019
115061
  const hasShellPromptHint = /[[(]\s*y\s*\/\s*n\s*[\])]|press\s+(enter|return|any key)|\bcontinue\b\s*\?|^\s*(?:❯|>)?\s*\d+[.)]\s+\S/im.test(
115020
115062
  stripped
115021
115063
  );
115022
- if (!oscPermission && !oscWaitingForInput && !hasAskFooter && !hasShellPromptHint && !this.permissionOpen.has(sessionId) && !this.shellPromptOpen.has(sessionId) && !this.lastScreenQuestionKey.has(sessionId)) {
115064
+ const nowMs = Date.now();
115065
+ const scrapeDue = nowMs - (this.lastDetectAt.get(sessionId) ?? 0) >= SCRAPE_THROTTLE_MS;
115066
+ if (!oscPermission && !oscWaitingForInput && !hasAskFooter && !hasShellPromptHint && !scrapeDue && !this.permissionOpen.has(sessionId) && !this.shellPromptOpen.has(sessionId) && !this.lastScreenQuestionKey.has(sessionId)) {
115023
115067
  return;
115024
115068
  }
115069
+ this.lastDetectAt.set(sessionId, nowMs);
115025
115070
  const lines = await this.getOutputLines(sessionId, 60);
115026
115071
  const askFooterOnScreen = lines.some((l) => /Enter to select/i.test(l));
115027
115072
  if (oscPermission || hasAskFooter || askFooterOnScreen) {
@@ -115041,13 +115086,30 @@ var init_pty_manager = __esm({
115041
115086
  this.permissionOpen.add(sessionId);
115042
115087
  this.onPermissionChange?.(sessionId, gate ?? { options: [] });
115043
115088
  } else if (this.permissionOpen.has(sessionId) && !askFooterOnScreen) {
115044
- const gate = scrapePermissionGate(lines);
115045
- if (oscWaitingForInput || !gate && hasPromptMarker) {
115089
+ const gate = detectGateScreen(lines);
115090
+ const stillPainted = gate !== null || scrapePermissionGate(lines) !== null;
115091
+ if (oscWaitingForInput || !stillPainted && hasPromptMarker) {
115046
115092
  this.permissionOpen.delete(sessionId);
115047
115093
  this.onPermissionChange?.(sessionId, null);
115094
+ if (gate) {
115095
+ this.closedGateKey.set(sessionId, permissionContentKey({ ...gate, cursor: void 0 }));
115096
+ } else {
115097
+ this.closedGateKey.delete(sessionId);
115098
+ }
115048
115099
  } else if (gate) {
115049
115100
  this.onPermissionChange?.(sessionId, gate);
115050
115101
  }
115102
+ } else if (!askFooterOnScreen && !oscWaitingForInput) {
115103
+ const gate = detectGateScreen(lines);
115104
+ if (gate) {
115105
+ const key = permissionContentKey({ ...gate, cursor: void 0 });
115106
+ if (this.closedGateKey.get(sessionId) !== key) {
115107
+ this.permissionOpen.add(sessionId);
115108
+ this.onPermissionChange?.(sessionId, gate);
115109
+ }
115110
+ } else {
115111
+ this.closedGateKey.delete(sessionId);
115112
+ }
115051
115113
  }
115052
115114
  if (askFooterOnScreen) {
115053
115115
  const detected = detectQuestionFromScreen(lines);
@@ -115190,8 +115252,11 @@ var init_pty_manager = __esm({
115190
115252
  this.queuedInputs.delete(sessionId);
115191
115253
  this.firstChunkAt.delete(sessionId);
115192
115254
  this.permissionOpen.delete(sessionId);
115255
+ this.oscTail.delete(sessionId);
115256
+ this.lastDetectAt.delete(sessionId);
115193
115257
  this.lastScreenQuestionKey.delete(sessionId);
115194
115258
  this.shellPromptOpen.delete(sessionId);
115259
+ this.closedGateKey.delete(sessionId);
115195
115260
  this.quietCheckers.get(sessionId)?.cancel();
115196
115261
  this.quietCheckers.delete(sessionId);
115197
115262
  this.clearReadyFallback(sessionId);
@@ -147568,7 +147633,12 @@ function managedToResponse(s3, ptyAttached) {
147568
147633
  // the durable registry, so it is a previous run's session with no process
147569
147634
  // behind it — `resumable`, and `historical` rather than `managed`
147570
147635
  // (docs/plans/live-sessions-persistence-plan.md §4, Phase 1).
147571
- lifecycle: ptyAttached ? "attached" : s3.rehydrated ? "resumable" : s3.failureReason != null ? "failed" : "completed",
147636
+ // A session this run itself put on hold (grace timer, explicit hold_session,
147637
+ // idle reaper) is the other exception: the PTY is gone the same way an exit
147638
+ // leaves it gone, but the conversation is still resumable, not terminal —
147639
+ // `putOnHold` records that by leaving `statusSource: "shutdown"` (the only
147640
+ // place either runner sets it), so it is checked here alongside `rehydrated`.
147641
+ lifecycle: ptyAttached ? "attached" : s3.rehydrated || s3.statusSource === "shutdown" ? "resumable" : s3.failureReason != null ? "failed" : "completed",
147572
147642
  lifecycleSource: ptyAttached ? s3.reconciled ? "reconcile" : "spawn" : s3.rehydrated ? "reconcile" : "exit",
147573
147643
  // We own its PTY, so `status` is the authoritative signal — no inferred
147574
147644
  // `activity` is attached for managed sessions.
@@ -148422,7 +148492,14 @@ var StreamerServer = class {
148422
148492
  // even though the registry has one — the name only appeared after a
148423
148493
  // restart rebuilt the row as a stub. Guarded so a runner that has not
148424
148494
  // derived one yet cannot blank a name set by enrichResumedSessionAsync.
148425
- ...session.sessionName != null && { sessionName: session.sessionName }
148495
+ ...session.sessionName != null && { sessionName: session.sessionName },
148496
+ // Without mirroring this, SessionStore's copy is frozen at whatever
148497
+ // `addManaged` saw at spawn ("spawn") forever, because updateManaged
148498
+ // is a partial merge — so managedToResponse can never tell a
148499
+ // grace-timer/idle-reaper hold (statusSource "shutdown") apart from a
148500
+ // genuine process exit ("process-exit"), and reports both as
148501
+ // `lifecycle: "completed"`. See managedToResponse in session-store.ts.
148502
+ ...session.statusSource != null && { statusSource: session.statusSource }
148426
148503
  });
148427
148504
  this.managedSessionsRepo?.recordStatus(
148428
148505
  session.id,