@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/cli.cjs +58 -10
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +58 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +58 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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;
|
|
2036
|
-
//
|
|
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.
|
|
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") {
|