@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.js
CHANGED
|
@@ -10620,6 +10620,19 @@ var SessionHandlers = class {
|
|
|
10620
10620
|
sessions: this.sessionStore.list(this.deps.ptyAttachedIds())
|
|
10621
10621
|
});
|
|
10622
10622
|
}
|
|
10623
|
+
/**
|
|
10624
|
+
* Same empty-unused check stop uses (promptCount === 0 and no cache row,
|
|
10625
|
+
* including boundConversationId / resumedFromConversationId). Safe after
|
|
10626
|
+
* putOnHold: the runner may already have dropped the live session.
|
|
10627
|
+
*/
|
|
10628
|
+
forgetIfEmptyUnused(sessionId) {
|
|
10629
|
+
const live = this.ptyManager.getSession(sessionId);
|
|
10630
|
+
const stored = this.sessionStore.getManaged(sessionId);
|
|
10631
|
+
const session = live ?? stored;
|
|
10632
|
+
if (!session) return;
|
|
10633
|
+
if (!this.shouldForgetEmptySession(session)) return;
|
|
10634
|
+
this.forgetEmptyStoppedSession(sessionId);
|
|
10635
|
+
}
|
|
10623
10636
|
async handleAdopt(sessionId, res) {
|
|
10624
10637
|
const discovered = await discoverClaudeProcesses();
|
|
10625
10638
|
this.sessionStore.setDiscovered(discovered);
|
|
@@ -12814,6 +12827,7 @@ function createLiveSessionOptions(deps) {
|
|
|
12814
12827
|
void deps.liveActivityNotifier()?.onStatusChange(session, previousStatus);
|
|
12815
12828
|
void deps.waitingInputNotifier()?.onStatusChange(session, previousStatus);
|
|
12816
12829
|
deps.sessionStatusBus.emit(`status:${session.id}`, session.status, session);
|
|
12830
|
+
deps.maybeFireHoldWhenIdle?.(session);
|
|
12817
12831
|
}
|
|
12818
12832
|
};
|
|
12819
12833
|
}
|
|
@@ -12962,7 +12976,20 @@ function createApiDeps(deps) {
|
|
|
12962
12976
|
deny(msg.type, "session:control");
|
|
12963
12977
|
return;
|
|
12964
12978
|
}
|
|
12965
|
-
|
|
12979
|
+
const when = msg.when;
|
|
12980
|
+
if (when === void 0 || when === "grace") {
|
|
12981
|
+
deps.startGraceTimer(msg.sessionId, deps.ptyGracePeriodMs);
|
|
12982
|
+
return;
|
|
12983
|
+
}
|
|
12984
|
+
if (when === "waiting_input") {
|
|
12985
|
+
deps.armHoldWhenIdle(msg.sessionId);
|
|
12986
|
+
return;
|
|
12987
|
+
}
|
|
12988
|
+
deps.log().warn(`[pty.hold_when_unknown] hold_session when=${String(when)}`, {
|
|
12989
|
+
event: "pty.hold_when_unknown",
|
|
12990
|
+
sessionId: msg.sessionId,
|
|
12991
|
+
when
|
|
12992
|
+
});
|
|
12966
12993
|
}
|
|
12967
12994
|
} catch {
|
|
12968
12995
|
}
|
|
@@ -15518,6 +15545,10 @@ var StreamerServer = class {
|
|
|
15518
15545
|
// Consecutive grace-timer defers for a still-`running` session (see
|
|
15519
15546
|
// GRACE_MAX_DEFERS). Reset when a subscriber reconnects or the PTY settles.
|
|
15520
15547
|
ptyGraceDeferCounts = /* @__PURE__ */ new Map();
|
|
15548
|
+
// Session ids that should be putOnHold at the next waiting_input/idle.
|
|
15549
|
+
// Same lifetime as ptyGraceTimers: in-memory, dropped on close/restart.
|
|
15550
|
+
// Last writer wins against the grace timer — never both armed.
|
|
15551
|
+
holdWhenIdle = /* @__PURE__ */ new Set();
|
|
15521
15552
|
// Map of sessionId → set of subscribed WS clients
|
|
15522
15553
|
sessionSubscribers = /* @__PURE__ */ new Map();
|
|
15523
15554
|
// sessionId → wall-clock ms of the last PTY chunk. Written from onOutput for
|
|
@@ -15745,7 +15776,8 @@ var StreamerServer = class {
|
|
|
15745
15776
|
waitingInputNotifier: () => this.waitingInputNotifier,
|
|
15746
15777
|
ptyAttachedIds: () => this.ptyAttachedIds(),
|
|
15747
15778
|
cancelPendingQuestion: (sessionId) => this.cancelPendingQuestion(sessionId),
|
|
15748
|
-
rememberSelfPtyEnded: (conversationId) => this.rememberSelfPtyEnded(conversationId)
|
|
15779
|
+
rememberSelfPtyEnded: (conversationId) => this.rememberSelfPtyEnded(conversationId),
|
|
15780
|
+
maybeFireHoldWhenIdle: (session) => this.maybeFireHoldWhenIdle(session)
|
|
15749
15781
|
})
|
|
15750
15782
|
);
|
|
15751
15783
|
this.sessionWatchers = new SessionWatchers({
|
|
@@ -15908,6 +15940,7 @@ var StreamerServer = class {
|
|
|
15908
15940
|
currentWarmupState: () => this.currentWarmupState(),
|
|
15909
15941
|
addSessionSubscriber: (sessionId, ws) => this.addSessionSubscriber(sessionId, ws),
|
|
15910
15942
|
startGraceTimer: (sessionId, delayMs) => this.startGraceTimer(sessionId, delayMs),
|
|
15943
|
+
armHoldWhenIdle: (sessionId) => this.armHoldWhenIdle(sessionId),
|
|
15911
15944
|
handleSessionsCount: (res) => this.handleSessionsCount(res),
|
|
15912
15945
|
applyLiveSessionSetting: (id, req, res, setting) => this.applyLiveSessionSetting(id, req, res, setting),
|
|
15913
15946
|
handlePairStart: (res) => this.handlePairStart(res),
|
|
@@ -16027,6 +16060,13 @@ var StreamerServer = class {
|
|
|
16027
16060
|
this.ptyGraceTimers.delete(sessionId);
|
|
16028
16061
|
}
|
|
16029
16062
|
this.ptyGraceDeferCounts.delete(sessionId);
|
|
16063
|
+
if (this.holdWhenIdle.delete(sessionId)) {
|
|
16064
|
+
this.log.info(
|
|
16065
|
+
`[hold-when-idle] cancelled ${sessionId} (subscribe)`,
|
|
16066
|
+
{ sessionId, event: "pty.hold_when_idle_cancel", reason: "subscribe" },
|
|
16067
|
+
"pino"
|
|
16068
|
+
);
|
|
16069
|
+
}
|
|
16030
16070
|
}
|
|
16031
16071
|
/**
|
|
16032
16072
|
* Bring up Live Activity push, if credentials are present (Feature 12).
|
|
@@ -16134,6 +16174,7 @@ var StreamerServer = class {
|
|
|
16134
16174
|
return reaped;
|
|
16135
16175
|
}
|
|
16136
16176
|
startGraceTimer(sessionId, delayMs) {
|
|
16177
|
+
this.holdWhenIdle.delete(sessionId);
|
|
16137
16178
|
const existing = this.ptyGraceTimers.get(sessionId);
|
|
16138
16179
|
if (existing) clearTimeout(existing);
|
|
16139
16180
|
const timer = setTimeout(() => {
|
|
@@ -16175,6 +16216,63 @@ var StreamerServer = class {
|
|
|
16175
16216
|
}, delayMs);
|
|
16176
16217
|
this.ptyGraceTimers.set(sessionId, timer);
|
|
16177
16218
|
}
|
|
16219
|
+
clearGrace(sessionId) {
|
|
16220
|
+
const existing = this.ptyGraceTimers.get(sessionId);
|
|
16221
|
+
if (existing) {
|
|
16222
|
+
clearTimeout(existing);
|
|
16223
|
+
this.ptyGraceTimers.delete(sessionId);
|
|
16224
|
+
}
|
|
16225
|
+
this.ptyGraceDeferCounts.delete(sessionId);
|
|
16226
|
+
}
|
|
16227
|
+
/**
|
|
16228
|
+
* Arm the in-app "Kill on idle" latch: hold now if already settled, otherwise
|
|
16229
|
+
* on the next running → waiting_input (or idle). No grace delay, no defer cap.
|
|
16230
|
+
* A subscribed leaving socket must not block an immediate hold.
|
|
16231
|
+
*/
|
|
16232
|
+
armHoldWhenIdle(sessionId) {
|
|
16233
|
+
if (!this.ptyManager.hasSession(sessionId)) return;
|
|
16234
|
+
this.clearGrace(sessionId);
|
|
16235
|
+
const status = this.ptyManager.getSession(sessionId)?.status ?? this.sessionStore.getManaged(sessionId)?.status;
|
|
16236
|
+
if (status === "waiting_input" || status === "idle") {
|
|
16237
|
+
this.holdWhenIdle.delete(sessionId);
|
|
16238
|
+
this.ptyManager.putOnHold(sessionId);
|
|
16239
|
+
this.forgetIfEmptyUnused(sessionId);
|
|
16240
|
+
return;
|
|
16241
|
+
}
|
|
16242
|
+
this.holdWhenIdle.add(sessionId);
|
|
16243
|
+
this.log.info(
|
|
16244
|
+
`[hold-when-idle] armed ${sessionId}`,
|
|
16245
|
+
{ sessionId, event: "pty.hold_when_idle_armed" },
|
|
16246
|
+
"pino"
|
|
16247
|
+
);
|
|
16248
|
+
}
|
|
16249
|
+
/**
|
|
16250
|
+
* Fire the Kill-on-idle latch from the shared onStatusChange funnel.
|
|
16251
|
+
* Delete first so the ensuing idle transition cannot re-enter.
|
|
16252
|
+
*/
|
|
16253
|
+
maybeFireHoldWhenIdle(session) {
|
|
16254
|
+
if (session.status !== "waiting_input" && session.status !== "idle") return;
|
|
16255
|
+
if (!this.holdWhenIdle.has(session.id)) return;
|
|
16256
|
+
this.holdWhenIdle.delete(session.id);
|
|
16257
|
+
if (this.hasSessionSubscriber(session.id)) {
|
|
16258
|
+
this.log.info(
|
|
16259
|
+
`[hold-when-idle] cancelled ${session.id} (subscriber)`,
|
|
16260
|
+
{ sessionId: session.id, event: "pty.hold_when_idle_cancel", reason: "subscriber" },
|
|
16261
|
+
"pino"
|
|
16262
|
+
);
|
|
16263
|
+
return;
|
|
16264
|
+
}
|
|
16265
|
+
this.log.info(
|
|
16266
|
+
`[hold-when-idle] holding ${session.id}`,
|
|
16267
|
+
{ sessionId: session.id, event: "pty.hold_when_idle_fire" },
|
|
16268
|
+
"pino"
|
|
16269
|
+
);
|
|
16270
|
+
this.ptyManager.putOnHold(session.id);
|
|
16271
|
+
this.forgetIfEmptyUnused(session.id);
|
|
16272
|
+
}
|
|
16273
|
+
forgetIfEmptyUnused(sessionId) {
|
|
16274
|
+
this.sessionHandlers.forgetIfEmptyUnused(sessionId);
|
|
16275
|
+
}
|
|
16178
16276
|
get port() {
|
|
16179
16277
|
const addr = this.httpServer.address();
|
|
16180
16278
|
return typeof addr === "object" && addr ? addr.port : 0;
|
|
@@ -16607,6 +16705,7 @@ var StreamerServer = class {
|
|
|
16607
16705
|
async close() {
|
|
16608
16706
|
for (const timer of this.ptyGraceTimers.values()) clearTimeout(timer);
|
|
16609
16707
|
this.ptyGraceTimers.clear();
|
|
16708
|
+
this.holdWhenIdle.clear();
|
|
16610
16709
|
if (this.idleReaperTimer) {
|
|
16611
16710
|
clearInterval(this.idleReaperTimer);
|
|
16612
16711
|
this.idleReaperTimer = null;
|