@threadbase-sh/streamer 1.41.0 → 1.41.2

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.cjs CHANGED
@@ -1955,8 +1955,8 @@ var PTY_COLS2 = 120;
1955
1955
  var PTY_ROWS2 = 40;
1956
1956
  var SCREEN_SCROLLBACK2 = 1e3;
1957
1957
  var CLAUDE_PROMPT_MARKERS = ["\u256D", "\u276F"];
1958
- var PROMPT_MARKER_FALLBACK_MS = 1e4;
1959
1958
  var QUIET_DETECT_MS2 = 500;
1959
+ var CLAUDE_READY_FALLBACK_MS = 8e3;
1960
1960
  function buildPasteBytes(input) {
1961
1961
  return `\x1B[200~${input}\x1B[201~`;
1962
1962
  }
@@ -2032,8 +2032,8 @@ var PTYManager = class {
2032
2032
  // silently lost — the "dot bug".
2033
2033
  queuedInputs = /* @__PURE__ */ new Map();
2034
2034
  log;
2035
- // Timestamp of first PTY chunk per session; drives the prompt-marker fallback
2036
- // when neither nor shows up within PROMPT_MARKER_FALLBACK_MS.
2035
+ // Timestamp of first PTY chunk per session; used for the [pty.ready] elapsed
2036
+ // measurement so a slow boot is visible in the logs.
2037
2037
  firstChunkAt = /* @__PURE__ */ new Map();
2038
2038
  // Per-session chunk counter and last-chunk timestamp. Diagnostic-only,
2039
2039
  // feeds the [pty.chunk] log lines so we can trace whether Claude responded
@@ -2044,6 +2044,8 @@ var PTYManager = class {
2044
2044
  // QUIET_DETECT_MS after the last chunk so ready/prompt detection doesn't
2045
2045
  // wait for another chunk that may never arrive (Claude blocked on input).
2046
2046
  quietCheckers = /* @__PURE__ */ new Map();
2047
+ // Per-session flat backstop from the first chunk (CLAUDE_READY_FALLBACK_MS).
2048
+ readyFallbackTimers = /* @__PURE__ */ new Map();
2047
2049
  // In-flight start()/startFresh() calls keyed by sessionId. A second
2048
2050
  // concurrent resume for the same session (double-tap, client retry) awaits
2049
2051
  // the first call's promise instead of spawning a duplicate PTY (CRITICAL #3).
@@ -2129,6 +2131,7 @@ var PTYManager = class {
2129
2131
  };
2130
2132
  this.sessions.set(sessionId, session);
2131
2133
  this.pendingReady.add(sessionId);
2134
+ this.armReadyFallback(sessionId);
2132
2135
  proc.onData((data) => {
2133
2136
  this.handleOutput(sessionId, data);
2134
2137
  });
@@ -2189,6 +2192,7 @@ var PTYManager = class {
2189
2192
  };
2190
2193
  this.sessions.set(sessionId, session);
2191
2194
  this.pendingReady.add(sessionId);
2195
+ this.armReadyFallback(sessionId);
2192
2196
  proc.onData((data) => {
2193
2197
  this.handleOutput(sessionId, data);
2194
2198
  });
@@ -2363,6 +2367,7 @@ var PTYManager = class {
2363
2367
  this.shellPromptOpen.delete(sessionId);
2364
2368
  this.quietCheckers.get(sessionId)?.cancel();
2365
2369
  this.quietCheckers.delete(sessionId);
2370
+ this.clearReadyFallback(sessionId);
2366
2371
  try {
2367
2372
  session.process.kill("SIGINT");
2368
2373
  } catch {
@@ -2451,6 +2456,8 @@ var PTYManager = class {
2451
2456
  this.lastChunkAt.clear();
2452
2457
  for (const quiet of this.quietCheckers.values()) quiet.cancel();
2453
2458
  this.quietCheckers.clear();
2459
+ for (const timer of this.readyFallbackTimers.values()) clearTimeout(timer);
2460
+ this.readyFallbackTimers.clear();
2454
2461
  this.permissionOpen.clear();
2455
2462
  this.lastScreenQuestionKey.clear();
2456
2463
  this.shellPromptOpen.clear();
@@ -2493,8 +2500,6 @@ var PTYManager = class {
2493
2500
  const matchedMarker = CLAUDE_PROMPT_MARKERS.find((m) => stripped.includes(m));
2494
2501
  if (session.status === "running" && matchedMarker) {
2495
2502
  this.markReady(sessionId, session, "prompt-marker", `marker:${matchedMarker}`);
2496
- } else if (session.status === "running" && this.pendingReady.has(sessionId) && now - (this.firstChunkAt.get(sessionId) ?? now) >= PROMPT_MARKER_FALLBACK_MS) {
2497
- this.markReady(sessionId, session, "timeout-fallback", "fallback:timeout");
2498
2503
  }
2499
2504
  this.onOutput?.(sessionId, data);
2500
2505
  this.detectLivePrompts(sessionId, data, stripped).catch((err) => {
@@ -2594,7 +2599,13 @@ var PTYManager = class {
2594
2599
  const session = this.sessions.get(sessionId);
2595
2600
  if (session?.status !== "running") return;
2596
2601
  if (this.pendingReady.has(sessionId)) {
2597
- this.markReady(sessionId, session, "quiet-fallback", "quiet:timeout");
2602
+ this.recheckReadyFromScreen(sessionId).catch((err) => {
2603
+ this.log.warn("[pty.ready] boot screen recheck failed", {
2604
+ event: "pty.ready_recheck_failed",
2605
+ sessionId,
2606
+ err
2607
+ });
2608
+ });
2598
2609
  } else {
2599
2610
  this.recheckReadyFromScreen(sessionId).catch((err) => {
2600
2611
  this.log.warn("[pty.ready] screen recheck failed", {
@@ -2626,9 +2637,32 @@ var PTYManager = class {
2626
2637
  this.markReady(sessionId, session, "screen-marker", `quiet:screen-marker:${matchedMarker}`);
2627
2638
  }
2628
2639
  }
2640
+ // Flat backstop from the first chunk: if neither a prompt marker nor the
2641
+ // screen recheck settles the session within CLAUDE_READY_FALLBACK_MS, mark it
2642
+ // ready anyway so start requests resolve and queued input is not held
2643
+ // forever. This is what makes the quiet-checker safe to be strict — a boot
2644
+ // variant whose marker we cannot see still recovers, just 8s later instead of
2645
+ // 500ms sooner and wrong. unref() so it never holds the process open.
2646
+ armReadyFallback(sessionId) {
2647
+ const timer = setTimeout(() => {
2648
+ this.readyFallbackTimers.delete(sessionId);
2649
+ const session = this.sessions.get(sessionId);
2650
+ if (session?.status === "running" && this.pendingReady.has(sessionId)) {
2651
+ this.markReady(sessionId, session, "timeout-fallback", "fallback:timeout");
2652
+ }
2653
+ }, CLAUDE_READY_FALLBACK_MS);
2654
+ timer.unref?.();
2655
+ this.readyFallbackTimers.set(sessionId, timer);
2656
+ }
2657
+ clearReadyFallback(sessionId) {
2658
+ const timer = this.readyFallbackTimers.get(sessionId);
2659
+ if (timer) clearTimeout(timer);
2660
+ this.readyFallbackTimers.delete(sessionId);
2661
+ }
2629
2662
  // Transition a session from "running" to "waiting_input", clear pendingReady,
2630
2663
  // and flush any queued input. Idempotent: callers can invoke at any chunk.
2631
2664
  markReady(sessionId, session, source, reason) {
2665
+ this.clearReadyFallback(sessionId);
2632
2666
  session.lastActivityAt = /* @__PURE__ */ new Date();
2633
2667
  session.status = "waiting_input";
2634
2668
  session.statusSource = source;
@@ -2672,6 +2706,7 @@ var PTYManager = class {
2672
2706
  this.shellPromptOpen.delete(sessionId);
2673
2707
  this.quietCheckers.get(sessionId)?.cancel();
2674
2708
  this.quietCheckers.delete(sessionId);
2709
+ this.clearReadyFallback(sessionId);
2675
2710
  }
2676
2711
  };
2677
2712
  function toPublicSession2(s) {
@@ -6662,7 +6697,8 @@ var ManagedSessionsRepository = class {
6662
6697
  completed_at = @completed_at,
6663
6698
  last_activity_at = @last_activity_at,
6664
6699
  prompt_count = @prompt_count,
6665
- failure_reason = COALESCE(@failure_reason, failure_reason)
6700
+ failure_reason = COALESCE(@failure_reason, failure_reason),
6701
+ session_name = COALESCE(@session_name, session_name)
6666
6702
  WHERE session_id = @session_id
6667
6703
  `);
6668
6704
  this.getStmt = db.prepare("SELECT * FROM managed_sessions WHERE session_id = ?");
@@ -6719,7 +6755,8 @@ var ManagedSessionsRepository = class {
6719
6755
  completed_at: fields.completedAt?.getTime() ?? null,
6720
6756
  last_activity_at: fields.lastActivityAt?.getTime() ?? null,
6721
6757
  prompt_count: fields.promptCount ?? 0,
6722
- failure_reason: fields.failureReason ?? null
6758
+ failure_reason: fields.failureReason ?? null,
6759
+ session_name: fields.sessionName ?? null
6723
6760
  });
6724
6761
  }
6725
6762
  get(sessionId) {
@@ -9776,7 +9813,14 @@ var StreamerServer = class {
9776
9813
  this.sessionStore.updateManaged(session.id, {
9777
9814
  status: session.status,
9778
9815
  completedAt: session.completedAt,
9779
- ...session.lastActivityAt != null && { lastActivityAt: session.lastActivityAt }
9816
+ ...session.lastActivityAt != null && { lastActivityAt: session.lastActivityAt },
9817
+ // The runner derives this from the first user message, on its own
9818
+ // copy of the session. Without mirroring it here SessionStore never
9819
+ // learns it, so a fresh live session is served with no sessionName
9820
+ // even though the registry has one — the name only appeared after a
9821
+ // restart rebuilt the row as a stub. Guarded so a runner that has not
9822
+ // derived one yet cannot blank a name set by enrichResumedSessionAsync.
9823
+ ...session.sessionName != null && { sessionName: session.sessionName }
9780
9824
  });
9781
9825
  this.managedSessionsRepo?.recordStatus(
9782
9826
  session.id,
@@ -9786,7 +9830,11 @@ var StreamerServer = class {
9786
9830
  completedAt: session.completedAt,
9787
9831
  lastActivityAt: session.lastActivityAt ?? null,
9788
9832
  promptCount: session.promptCount,
9789
- failureReason: session.failureReason ?? null
9833
+ failureReason: session.failureReason ?? null,
9834
+ // Derived from the first user message, so it does not exist yet at
9835
+ // recordSpawn. The input that produces it also flips
9836
+ // waiting_input→running, which lands here.
9837
+ sessionName: session.sessionName ?? null
9790
9838
  }
9791
9839
  );
9792
9840
  if (session.status === "waiting_input" || session.status === "idle") {