@threadbase-sh/streamer 1.64.0 → 1.65.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/api/handlers/sessions.handlers.d.ts +6 -0
- package/dist/api/handlers/sessions.handlers.d.ts.map +1 -1
- package/dist/cli.cjs +101 -2
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +101 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +101 -2
- package/dist/index.js.map +1 -1
- package/dist/server-wiring.d.ts +5 -0
- package/dist/server-wiring.d.ts.map +1 -1
- package/dist/server.d.ts +14 -0
- package/dist/server.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -10660,6 +10660,19 @@ var SessionHandlers = class {
|
|
|
10660
10660
|
sessions: this.sessionStore.list(this.deps.ptyAttachedIds())
|
|
10661
10661
|
});
|
|
10662
10662
|
}
|
|
10663
|
+
/**
|
|
10664
|
+
* Same empty-unused check stop uses (promptCount === 0 and no cache row,
|
|
10665
|
+
* including boundConversationId / resumedFromConversationId). Safe after
|
|
10666
|
+
* putOnHold: the runner may already have dropped the live session.
|
|
10667
|
+
*/
|
|
10668
|
+
forgetIfEmptyUnused(sessionId) {
|
|
10669
|
+
const live = this.ptyManager.getSession(sessionId);
|
|
10670
|
+
const stored = this.sessionStore.getManaged(sessionId);
|
|
10671
|
+
const session = live ?? stored;
|
|
10672
|
+
if (!session) return;
|
|
10673
|
+
if (!this.shouldForgetEmptySession(session)) return;
|
|
10674
|
+
this.forgetEmptyStoppedSession(sessionId);
|
|
10675
|
+
}
|
|
10663
10676
|
async handleAdopt(sessionId, res) {
|
|
10664
10677
|
const discovered = await discoverClaudeProcesses();
|
|
10665
10678
|
this.sessionStore.setDiscovered(discovered);
|
|
@@ -12843,6 +12856,7 @@ function createLiveSessionOptions(deps) {
|
|
|
12843
12856
|
void deps.liveActivityNotifier()?.onStatusChange(session, previousStatus);
|
|
12844
12857
|
void deps.waitingInputNotifier()?.onStatusChange(session, previousStatus);
|
|
12845
12858
|
deps.sessionStatusBus.emit(`status:${session.id}`, session.status, session);
|
|
12859
|
+
deps.maybeFireHoldWhenIdle?.(session);
|
|
12846
12860
|
}
|
|
12847
12861
|
};
|
|
12848
12862
|
}
|
|
@@ -12991,7 +13005,20 @@ function createApiDeps(deps) {
|
|
|
12991
13005
|
deny(msg.type, "session:control");
|
|
12992
13006
|
return;
|
|
12993
13007
|
}
|
|
12994
|
-
|
|
13008
|
+
const when = msg.when;
|
|
13009
|
+
if (when === void 0 || when === "grace") {
|
|
13010
|
+
deps.startGraceTimer(msg.sessionId, deps.ptyGracePeriodMs);
|
|
13011
|
+
return;
|
|
13012
|
+
}
|
|
13013
|
+
if (when === "waiting_input") {
|
|
13014
|
+
deps.armHoldWhenIdle(msg.sessionId);
|
|
13015
|
+
return;
|
|
13016
|
+
}
|
|
13017
|
+
deps.log().warn(`[pty.hold_when_unknown] hold_session when=${String(when)}`, {
|
|
13018
|
+
event: "pty.hold_when_unknown",
|
|
13019
|
+
sessionId: msg.sessionId,
|
|
13020
|
+
when
|
|
13021
|
+
});
|
|
12995
13022
|
}
|
|
12996
13023
|
} catch {
|
|
12997
13024
|
}
|
|
@@ -15547,6 +15574,10 @@ var StreamerServer = class {
|
|
|
15547
15574
|
// Consecutive grace-timer defers for a still-`running` session (see
|
|
15548
15575
|
// GRACE_MAX_DEFERS). Reset when a subscriber reconnects or the PTY settles.
|
|
15549
15576
|
ptyGraceDeferCounts = /* @__PURE__ */ new Map();
|
|
15577
|
+
// Session ids that should be putOnHold at the next waiting_input/idle.
|
|
15578
|
+
// Same lifetime as ptyGraceTimers: in-memory, dropped on close/restart.
|
|
15579
|
+
// Last writer wins against the grace timer — never both armed.
|
|
15580
|
+
holdWhenIdle = /* @__PURE__ */ new Set();
|
|
15550
15581
|
// Map of sessionId → set of subscribed WS clients
|
|
15551
15582
|
sessionSubscribers = /* @__PURE__ */ new Map();
|
|
15552
15583
|
// sessionId → wall-clock ms of the last PTY chunk. Written from onOutput for
|
|
@@ -15774,7 +15805,8 @@ var StreamerServer = class {
|
|
|
15774
15805
|
waitingInputNotifier: () => this.waitingInputNotifier,
|
|
15775
15806
|
ptyAttachedIds: () => this.ptyAttachedIds(),
|
|
15776
15807
|
cancelPendingQuestion: (sessionId) => this.cancelPendingQuestion(sessionId),
|
|
15777
|
-
rememberSelfPtyEnded: (conversationId) => this.rememberSelfPtyEnded(conversationId)
|
|
15808
|
+
rememberSelfPtyEnded: (conversationId) => this.rememberSelfPtyEnded(conversationId),
|
|
15809
|
+
maybeFireHoldWhenIdle: (session) => this.maybeFireHoldWhenIdle(session)
|
|
15778
15810
|
})
|
|
15779
15811
|
);
|
|
15780
15812
|
this.sessionWatchers = new SessionWatchers({
|
|
@@ -15937,6 +15969,7 @@ var StreamerServer = class {
|
|
|
15937
15969
|
currentWarmupState: () => this.currentWarmupState(),
|
|
15938
15970
|
addSessionSubscriber: (sessionId, ws) => this.addSessionSubscriber(sessionId, ws),
|
|
15939
15971
|
startGraceTimer: (sessionId, delayMs) => this.startGraceTimer(sessionId, delayMs),
|
|
15972
|
+
armHoldWhenIdle: (sessionId) => this.armHoldWhenIdle(sessionId),
|
|
15940
15973
|
handleSessionsCount: (res) => this.handleSessionsCount(res),
|
|
15941
15974
|
applyLiveSessionSetting: (id, req, res, setting) => this.applyLiveSessionSetting(id, req, res, setting),
|
|
15942
15975
|
handlePairStart: (res) => this.handlePairStart(res),
|
|
@@ -16056,6 +16089,13 @@ var StreamerServer = class {
|
|
|
16056
16089
|
this.ptyGraceTimers.delete(sessionId);
|
|
16057
16090
|
}
|
|
16058
16091
|
this.ptyGraceDeferCounts.delete(sessionId);
|
|
16092
|
+
if (this.holdWhenIdle.delete(sessionId)) {
|
|
16093
|
+
this.log.info(
|
|
16094
|
+
`[hold-when-idle] cancelled ${sessionId} (subscribe)`,
|
|
16095
|
+
{ sessionId, event: "pty.hold_when_idle_cancel", reason: "subscribe" },
|
|
16096
|
+
"pino"
|
|
16097
|
+
);
|
|
16098
|
+
}
|
|
16059
16099
|
}
|
|
16060
16100
|
/**
|
|
16061
16101
|
* Bring up Live Activity push, if credentials are present (Feature 12).
|
|
@@ -16163,6 +16203,7 @@ var StreamerServer = class {
|
|
|
16163
16203
|
return reaped;
|
|
16164
16204
|
}
|
|
16165
16205
|
startGraceTimer(sessionId, delayMs) {
|
|
16206
|
+
this.holdWhenIdle.delete(sessionId);
|
|
16166
16207
|
const existing = this.ptyGraceTimers.get(sessionId);
|
|
16167
16208
|
if (existing) clearTimeout(existing);
|
|
16168
16209
|
const timer = setTimeout(() => {
|
|
@@ -16204,6 +16245,63 @@ var StreamerServer = class {
|
|
|
16204
16245
|
}, delayMs);
|
|
16205
16246
|
this.ptyGraceTimers.set(sessionId, timer);
|
|
16206
16247
|
}
|
|
16248
|
+
clearGrace(sessionId) {
|
|
16249
|
+
const existing = this.ptyGraceTimers.get(sessionId);
|
|
16250
|
+
if (existing) {
|
|
16251
|
+
clearTimeout(existing);
|
|
16252
|
+
this.ptyGraceTimers.delete(sessionId);
|
|
16253
|
+
}
|
|
16254
|
+
this.ptyGraceDeferCounts.delete(sessionId);
|
|
16255
|
+
}
|
|
16256
|
+
/**
|
|
16257
|
+
* Arm the in-app "Kill on idle" latch: hold now if already settled, otherwise
|
|
16258
|
+
* on the next running → waiting_input (or idle). No grace delay, no defer cap.
|
|
16259
|
+
* A subscribed leaving socket must not block an immediate hold.
|
|
16260
|
+
*/
|
|
16261
|
+
armHoldWhenIdle(sessionId) {
|
|
16262
|
+
if (!this.ptyManager.hasSession(sessionId)) return;
|
|
16263
|
+
this.clearGrace(sessionId);
|
|
16264
|
+
const status = this.ptyManager.getSession(sessionId)?.status ?? this.sessionStore.getManaged(sessionId)?.status;
|
|
16265
|
+
if (status === "waiting_input" || status === "idle") {
|
|
16266
|
+
this.holdWhenIdle.delete(sessionId);
|
|
16267
|
+
this.ptyManager.putOnHold(sessionId);
|
|
16268
|
+
this.forgetIfEmptyUnused(sessionId);
|
|
16269
|
+
return;
|
|
16270
|
+
}
|
|
16271
|
+
this.holdWhenIdle.add(sessionId);
|
|
16272
|
+
this.log.info(
|
|
16273
|
+
`[hold-when-idle] armed ${sessionId}`,
|
|
16274
|
+
{ sessionId, event: "pty.hold_when_idle_armed" },
|
|
16275
|
+
"pino"
|
|
16276
|
+
);
|
|
16277
|
+
}
|
|
16278
|
+
/**
|
|
16279
|
+
* Fire the Kill-on-idle latch from the shared onStatusChange funnel.
|
|
16280
|
+
* Delete first so the ensuing idle transition cannot re-enter.
|
|
16281
|
+
*/
|
|
16282
|
+
maybeFireHoldWhenIdle(session) {
|
|
16283
|
+
if (session.status !== "waiting_input" && session.status !== "idle") return;
|
|
16284
|
+
if (!this.holdWhenIdle.has(session.id)) return;
|
|
16285
|
+
this.holdWhenIdle.delete(session.id);
|
|
16286
|
+
if (this.hasSessionSubscriber(session.id)) {
|
|
16287
|
+
this.log.info(
|
|
16288
|
+
`[hold-when-idle] cancelled ${session.id} (subscriber)`,
|
|
16289
|
+
{ sessionId: session.id, event: "pty.hold_when_idle_cancel", reason: "subscriber" },
|
|
16290
|
+
"pino"
|
|
16291
|
+
);
|
|
16292
|
+
return;
|
|
16293
|
+
}
|
|
16294
|
+
this.log.info(
|
|
16295
|
+
`[hold-when-idle] holding ${session.id}`,
|
|
16296
|
+
{ sessionId: session.id, event: "pty.hold_when_idle_fire" },
|
|
16297
|
+
"pino"
|
|
16298
|
+
);
|
|
16299
|
+
this.ptyManager.putOnHold(session.id);
|
|
16300
|
+
this.forgetIfEmptyUnused(session.id);
|
|
16301
|
+
}
|
|
16302
|
+
forgetIfEmptyUnused(sessionId) {
|
|
16303
|
+
this.sessionHandlers.forgetIfEmptyUnused(sessionId);
|
|
16304
|
+
}
|
|
16207
16305
|
get port() {
|
|
16208
16306
|
const addr = this.httpServer.address();
|
|
16209
16307
|
return typeof addr === "object" && addr ? addr.port : 0;
|
|
@@ -16636,6 +16734,7 @@ var StreamerServer = class {
|
|
|
16636
16734
|
async close() {
|
|
16637
16735
|
for (const timer of this.ptyGraceTimers.values()) clearTimeout(timer);
|
|
16638
16736
|
this.ptyGraceTimers.clear();
|
|
16737
|
+
this.holdWhenIdle.clear();
|
|
16639
16738
|
if (this.idleReaperTimer) {
|
|
16640
16739
|
clearInterval(this.idleReaperTimer);
|
|
16641
16740
|
this.idleReaperTimer = null;
|