@threadbase-sh/streamer 1.65.0 → 1.67.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/routes/misc.routes.d.ts.map +1 -1
- package/dist/api/types/api-deps.d.ts +2 -0
- package/dist/api/types/api-deps.d.ts.map +1 -1
- package/dist/cli.cjs +259 -14
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +259 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +247 -2
- package/dist/index.js.map +1 -1
- package/dist/server-wiring.d.ts +3 -0
- package/dist/server-wiring.d.ts.map +1 -1
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/services/host-pressure/hostPressure.d.ts +83 -0
- package/dist/services/host-pressure/hostPressure.d.ts.map +1 -0
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4926,7 +4926,7 @@ function redactPath(path) {
|
|
|
4926
4926
|
function worstStatus(checks) {
|
|
4927
4927
|
const rank = { ok: 0, unknown: 1, degraded: 2, failed: 3 };
|
|
4928
4928
|
return checks.reduce(
|
|
4929
|
-
(
|
|
4929
|
+
(worst2, c) => rank[c.status] > rank[worst2] ? c.status : worst2,
|
|
4930
4930
|
"ok"
|
|
4931
4931
|
);
|
|
4932
4932
|
}
|
|
@@ -5946,7 +5946,12 @@ var createMiscRoutes = (deps) => {
|
|
|
5946
5946
|
// Whether to encrypt to this server. Additive, same contract as `push`:
|
|
5947
5947
|
// absent means an older server, which a client must read as "unknown" and
|
|
5948
5948
|
// resolve as today's plaintext path — never as a reason to fail.
|
|
5949
|
-
e2ee: describeE2eeCapability(deps.featureFlagsConfig().values.e2ee)
|
|
5949
|
+
e2ee: describeE2eeCapability(deps.featureFlagsConfig().values.e2ee),
|
|
5950
|
+
// This build samples cheap host signals and pushes `host_pressure` when
|
|
5951
|
+
// the box is starved. Additive capability flag only — live readings stay
|
|
5952
|
+
// off this polled endpoint. Absent means an older server that never
|
|
5953
|
+
// samples. Informational: pressure never holds, kills, or refuses sessions.
|
|
5954
|
+
hostPressure: true
|
|
5950
5955
|
});
|
|
5951
5956
|
});
|
|
5952
5957
|
app.get("/api/profiles", (c) => c.json([]));
|
|
@@ -12851,6 +12856,7 @@ function createApiDeps(deps) {
|
|
|
12851
12856
|
wsHub: deps.wsHub,
|
|
12852
12857
|
cache: () => deps.cache(),
|
|
12853
12858
|
cacheMonitor: () => deps.cacheMonitor(),
|
|
12859
|
+
hostPressureMonitor: () => deps.hostPressureMonitor(),
|
|
12854
12860
|
pushRepo: () => deps.pushRepo(),
|
|
12855
12861
|
liveActivityPushEnabled: () => deps.liveActivityPushEnabled(),
|
|
12856
12862
|
devicesRepo: () => deps.devicesRepo(),
|
|
@@ -12901,6 +12907,8 @@ function createApiDeps(deps) {
|
|
|
12901
12907
|
}
|
|
12902
12908
|
const alertMsg = deps.cacheMonitor()?.wsMessage();
|
|
12903
12909
|
if (alertMsg) deps.wsHub.unicast(ws, alertMsg);
|
|
12910
|
+
const pressureMsg = deps.hostPressureMonitor()?.wsMessage();
|
|
12911
|
+
if (pressureMsg) deps.wsHub.unicast(ws, pressureMsg);
|
|
12904
12912
|
},
|
|
12905
12913
|
handleWsMessage: async (ws, raw, principal) => {
|
|
12906
12914
|
const deny = (type, required) => {
|
|
@@ -12971,6 +12979,13 @@ function createApiDeps(deps) {
|
|
|
12971
12979
|
);
|
|
12972
12980
|
}
|
|
12973
12981
|
}
|
|
12982
|
+
if (msg.type === "unsubscribe_session" && typeof msg.sessionId === "string") {
|
|
12983
|
+
if (!wsAllows(principal, "history:read")) {
|
|
12984
|
+
deny(msg.type, "history:read");
|
|
12985
|
+
return;
|
|
12986
|
+
}
|
|
12987
|
+
deps.removeSessionSubscriber(msg.sessionId, ws);
|
|
12988
|
+
}
|
|
12974
12989
|
if (msg.type === "hold_session" && typeof msg.sessionId === "string") {
|
|
12975
12990
|
if (!wsAllows(principal, "session:control")) {
|
|
12976
12991
|
deny(msg.type, "session:control");
|
|
@@ -13526,6 +13541,221 @@ function pruneAgentConversations(cache) {
|
|
|
13526
13541
|
return { scanned: rows.length, pruned, missing };
|
|
13527
13542
|
}
|
|
13528
13543
|
|
|
13544
|
+
// src/services/host-pressure/hostPressure.ts
|
|
13545
|
+
import { cpus, freemem, loadavg, totalmem } from "os";
|
|
13546
|
+
import { monitorEventLoopDelay } from "perf_hooks";
|
|
13547
|
+
var HOST_PRESSURE_SAMPLE_MS = 5e3;
|
|
13548
|
+
var HOST_PRESSURE_BARS = {
|
|
13549
|
+
memFreeRatio: {
|
|
13550
|
+
enterElevated: 0.15,
|
|
13551
|
+
leaveElevated: 0.17,
|
|
13552
|
+
enterCritical: 0.08,
|
|
13553
|
+
leaveCritical: 0.15
|
|
13554
|
+
},
|
|
13555
|
+
eventLoopP99Ms: {
|
|
13556
|
+
enterElevated: 100,
|
|
13557
|
+
leaveElevated: 80,
|
|
13558
|
+
enterCritical: 250,
|
|
13559
|
+
leaveCritical: 100
|
|
13560
|
+
},
|
|
13561
|
+
loadPerCpu: {
|
|
13562
|
+
enterElevated: 1.25,
|
|
13563
|
+
leaveElevated: 1.05,
|
|
13564
|
+
enterCritical: 2,
|
|
13565
|
+
leaveCritical: 1.25
|
|
13566
|
+
},
|
|
13567
|
+
// win32 has no loadavg. Busy ratio from os.cpus()[].times deltas is 0–1, so
|
|
13568
|
+
// it cannot reuse loadPerCpu's 2.0 critical bar. Reason on the wire stays `load`.
|
|
13569
|
+
cpuBusy: {
|
|
13570
|
+
enterElevated: 0.85,
|
|
13571
|
+
leaveElevated: 0.7,
|
|
13572
|
+
enterCritical: 0.97,
|
|
13573
|
+
leaveCritical: 0.85
|
|
13574
|
+
},
|
|
13575
|
+
liveAgentsPair: 4
|
|
13576
|
+
};
|
|
13577
|
+
var REASON_ORDER = ["memory", "event_loop", "load", "agents"];
|
|
13578
|
+
var RANK = { ok: 0, elevated: 1, critical: 2 };
|
|
13579
|
+
function worst(levels) {
|
|
13580
|
+
return levels.reduce(
|
|
13581
|
+
(acc, level) => RANK[level] > RANK[acc] ? level : acc,
|
|
13582
|
+
"ok"
|
|
13583
|
+
);
|
|
13584
|
+
}
|
|
13585
|
+
function schmittLowIsWorse(value, previous, enterElevated, leaveElevated, enterCritical, leaveCritical) {
|
|
13586
|
+
if (previous === "critical") {
|
|
13587
|
+
if (value < leaveCritical) return "critical";
|
|
13588
|
+
if (value < leaveElevated) return "elevated";
|
|
13589
|
+
return "ok";
|
|
13590
|
+
}
|
|
13591
|
+
if (previous === "elevated") {
|
|
13592
|
+
if (value < enterCritical) return "critical";
|
|
13593
|
+
if (value < leaveElevated) return "elevated";
|
|
13594
|
+
return "ok";
|
|
13595
|
+
}
|
|
13596
|
+
if (value < enterCritical) return "critical";
|
|
13597
|
+
if (value < enterElevated) return "elevated";
|
|
13598
|
+
return "ok";
|
|
13599
|
+
}
|
|
13600
|
+
function schmittHighIsWorse(value, previous, enterElevated, leaveElevated, enterCritical, leaveCritical) {
|
|
13601
|
+
if (previous === "critical") {
|
|
13602
|
+
if (value > leaveCritical) return "critical";
|
|
13603
|
+
if (value > leaveElevated) return "elevated";
|
|
13604
|
+
return "ok";
|
|
13605
|
+
}
|
|
13606
|
+
if (previous === "elevated") {
|
|
13607
|
+
if (value > enterCritical) return "critical";
|
|
13608
|
+
if (value > leaveElevated) return "elevated";
|
|
13609
|
+
return "ok";
|
|
13610
|
+
}
|
|
13611
|
+
if (value > enterCritical) return "critical";
|
|
13612
|
+
if (value > enterElevated) return "elevated";
|
|
13613
|
+
return "ok";
|
|
13614
|
+
}
|
|
13615
|
+
function timesTotal(times) {
|
|
13616
|
+
return times.user + times.nice + times.sys + times.idle + times.irq;
|
|
13617
|
+
}
|
|
13618
|
+
function hostPressureOs(platform3) {
|
|
13619
|
+
if (platform3 === "darwin" || platform3 === "linux" || platform3 === "win32") return platform3;
|
|
13620
|
+
return void 0;
|
|
13621
|
+
}
|
|
13622
|
+
function cpuBusyRatio(previous, next) {
|
|
13623
|
+
if (!previous || previous.length === 0 || next.length === 0 || previous.length !== next.length) {
|
|
13624
|
+
return 0;
|
|
13625
|
+
}
|
|
13626
|
+
let idle = 0;
|
|
13627
|
+
let total = 0;
|
|
13628
|
+
for (let i = 0; i < next.length; i++) {
|
|
13629
|
+
const dt = timesTotal(next[i]) - timesTotal(previous[i]);
|
|
13630
|
+
if (dt <= 0) continue;
|
|
13631
|
+
total += dt;
|
|
13632
|
+
idle += Math.max(0, next[i].idle - previous[i].idle);
|
|
13633
|
+
}
|
|
13634
|
+
if (total <= 0) return 0;
|
|
13635
|
+
return 1 - idle / total;
|
|
13636
|
+
}
|
|
13637
|
+
function classifyHostPressure(sample, previous, platform3) {
|
|
13638
|
+
const memRaw = schmittLowIsWorse(
|
|
13639
|
+
sample.memFreeRatio,
|
|
13640
|
+
previous,
|
|
13641
|
+
HOST_PRESSURE_BARS.memFreeRatio.enterElevated,
|
|
13642
|
+
HOST_PRESSURE_BARS.memFreeRatio.leaveElevated,
|
|
13643
|
+
HOST_PRESSURE_BARS.memFreeRatio.enterCritical,
|
|
13644
|
+
HOST_PRESSURE_BARS.memFreeRatio.leaveCritical
|
|
13645
|
+
);
|
|
13646
|
+
const mem = platform3 === "darwin" && memRaw === "critical" ? "elevated" : memRaw;
|
|
13647
|
+
const eventLoop = schmittHighIsWorse(
|
|
13648
|
+
sample.eventLoopP99Ms,
|
|
13649
|
+
previous,
|
|
13650
|
+
HOST_PRESSURE_BARS.eventLoopP99Ms.enterElevated,
|
|
13651
|
+
HOST_PRESSURE_BARS.eventLoopP99Ms.leaveElevated,
|
|
13652
|
+
HOST_PRESSURE_BARS.eventLoopP99Ms.enterCritical,
|
|
13653
|
+
HOST_PRESSURE_BARS.eventLoopP99Ms.leaveCritical
|
|
13654
|
+
);
|
|
13655
|
+
const ncpu = sample.ncpu > 0 ? sample.ncpu : 1;
|
|
13656
|
+
const windows = platform3 === "win32";
|
|
13657
|
+
const loadValue = windows ? sample.cpuBusyRatio ?? 0 : sample.load1 / ncpu;
|
|
13658
|
+
const loadBars = windows ? HOST_PRESSURE_BARS.cpuBusy : HOST_PRESSURE_BARS.loadPerCpu;
|
|
13659
|
+
const load = schmittHighIsWorse(
|
|
13660
|
+
loadValue,
|
|
13661
|
+
previous,
|
|
13662
|
+
loadBars.enterElevated,
|
|
13663
|
+
loadBars.leaveElevated,
|
|
13664
|
+
loadBars.enterCritical,
|
|
13665
|
+
loadBars.leaveCritical
|
|
13666
|
+
);
|
|
13667
|
+
const resourceLevel = worst([mem, eventLoop, load]);
|
|
13668
|
+
const agentsPair = sample.liveAgents >= HOST_PRESSURE_BARS.liveAgentsPair && resourceLevel !== "ok";
|
|
13669
|
+
const level = resourceLevel;
|
|
13670
|
+
const firing = [];
|
|
13671
|
+
if (mem !== "ok") firing.push("memory");
|
|
13672
|
+
if (eventLoop !== "ok") firing.push("event_loop");
|
|
13673
|
+
if (load !== "ok") firing.push("load");
|
|
13674
|
+
if (agentsPair) firing.push("agents");
|
|
13675
|
+
const reasons = REASON_ORDER.filter((reason) => firing.includes(reason));
|
|
13676
|
+
return { level, reasons };
|
|
13677
|
+
}
|
|
13678
|
+
var HostPressureMonitor = class {
|
|
13679
|
+
constructor(opts) {
|
|
13680
|
+
this.opts = opts;
|
|
13681
|
+
}
|
|
13682
|
+
opts;
|
|
13683
|
+
timer = null;
|
|
13684
|
+
level = "ok";
|
|
13685
|
+
lastWarning = null;
|
|
13686
|
+
start() {
|
|
13687
|
+
this.opts.histogram?.enable();
|
|
13688
|
+
if (this.timer) return;
|
|
13689
|
+
const intervalMs = this.opts.intervalMs ?? HOST_PRESSURE_SAMPLE_MS;
|
|
13690
|
+
this.timer = setInterval(() => this.tick(), intervalMs);
|
|
13691
|
+
this.timer.unref?.();
|
|
13692
|
+
}
|
|
13693
|
+
tick() {
|
|
13694
|
+
const sample = this.opts.readSample();
|
|
13695
|
+
const platform3 = this.opts.platform ?? process.platform;
|
|
13696
|
+
const classified = classifyHostPressure(sample, this.level, platform3);
|
|
13697
|
+
if (classified.level === this.level) return;
|
|
13698
|
+
this.level = classified.level;
|
|
13699
|
+
const updatedAt = (this.opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
|
|
13700
|
+
if (classified.level === "ok") {
|
|
13701
|
+
this.lastWarning = null;
|
|
13702
|
+
this.opts.wsHub.broadcast({ type: "host_pressure_cleared", updatedAt });
|
|
13703
|
+
return;
|
|
13704
|
+
}
|
|
13705
|
+
const os2 = hostPressureOs(platform3);
|
|
13706
|
+
const message = {
|
|
13707
|
+
type: "host_pressure",
|
|
13708
|
+
level: classified.level,
|
|
13709
|
+
reasons: classified.reasons,
|
|
13710
|
+
liveAgents: sample.liveAgents,
|
|
13711
|
+
updatedAt,
|
|
13712
|
+
...os2 ? { os: os2 } : {}
|
|
13713
|
+
};
|
|
13714
|
+
this.lastWarning = message;
|
|
13715
|
+
this.opts.wsHub.broadcast(message);
|
|
13716
|
+
}
|
|
13717
|
+
wsMessage() {
|
|
13718
|
+
return this.lastWarning;
|
|
13719
|
+
}
|
|
13720
|
+
dispose() {
|
|
13721
|
+
if (this.timer) {
|
|
13722
|
+
clearInterval(this.timer);
|
|
13723
|
+
this.timer = null;
|
|
13724
|
+
}
|
|
13725
|
+
this.opts.histogram?.disable();
|
|
13726
|
+
}
|
|
13727
|
+
};
|
|
13728
|
+
function createHostPressureMonitor(wsHub, liveAgents) {
|
|
13729
|
+
const histogram = monitorEventLoopDelay({ resolution: 20 });
|
|
13730
|
+
const windows = process.platform === "win32";
|
|
13731
|
+
let prevCpuTimes = null;
|
|
13732
|
+
const monitor = new HostPressureMonitor({
|
|
13733
|
+
wsHub,
|
|
13734
|
+
histogram,
|
|
13735
|
+
readSample: () => {
|
|
13736
|
+
const eventLoopP99Ms = histogram.percentile(99) / 1e6;
|
|
13737
|
+
histogram.reset();
|
|
13738
|
+
const total = totalmem();
|
|
13739
|
+
const cpuList = cpus();
|
|
13740
|
+
const sample = {
|
|
13741
|
+
liveAgents: liveAgents(),
|
|
13742
|
+
memFreeRatio: total > 0 ? freemem() / total : 1,
|
|
13743
|
+
eventLoopP99Ms,
|
|
13744
|
+
load1: loadavg()[0],
|
|
13745
|
+
ncpu: cpuList.length
|
|
13746
|
+
};
|
|
13747
|
+
if (windows) {
|
|
13748
|
+
const cpuTimes = cpuList.map((cpu) => cpu.times);
|
|
13749
|
+
sample.cpuBusyRatio = cpuBusyRatio(prevCpuTimes, cpuTimes);
|
|
13750
|
+
prevCpuTimes = cpuTimes;
|
|
13751
|
+
}
|
|
13752
|
+
return sample;
|
|
13753
|
+
}
|
|
13754
|
+
});
|
|
13755
|
+
monitor.start();
|
|
13756
|
+
return monitor;
|
|
13757
|
+
}
|
|
13758
|
+
|
|
13529
13759
|
// src/services/push/expoPushSender.ts
|
|
13530
13760
|
var log5 = getLogger("expo-push");
|
|
13531
13761
|
var EXPO_PUSH_ENDPOINT = "https://exp.host/--/api/v2/push/send";
|
|
@@ -15574,6 +15804,7 @@ var StreamerServer = class {
|
|
|
15574
15804
|
wsToClientId = /* @__PURE__ */ new Map();
|
|
15575
15805
|
cache = null;
|
|
15576
15806
|
cacheMonitor = null;
|
|
15807
|
+
hostPressureMonitor = null;
|
|
15577
15808
|
projectsRepo = null;
|
|
15578
15809
|
conversationsRepo = null;
|
|
15579
15810
|
sessionsRepo = null;
|
|
@@ -15924,6 +16155,7 @@ var StreamerServer = class {
|
|
|
15924
16155
|
// reset-and-rescan, and tests swap methods on the server instance.
|
|
15925
16156
|
cache: () => this.cache,
|
|
15926
16157
|
cacheMonitor: () => this.cacheMonitor,
|
|
16158
|
+
hostPressureMonitor: () => this.hostPressureMonitor,
|
|
15927
16159
|
pushRepo: () => this.pushRepo,
|
|
15928
16160
|
liveActivityPushEnabled: () => this.liveActivityNotifier !== null,
|
|
15929
16161
|
devicesRepo: () => this.devicesRepo,
|
|
@@ -15939,6 +16171,7 @@ var StreamerServer = class {
|
|
|
15939
16171
|
withReconciledLifecycle: (sessions) => this.withReconciledLifecycle(sessions),
|
|
15940
16172
|
currentWarmupState: () => this.currentWarmupState(),
|
|
15941
16173
|
addSessionSubscriber: (sessionId, ws) => this.addSessionSubscriber(sessionId, ws),
|
|
16174
|
+
removeSessionSubscriber: (sessionId, ws) => this.removeSessionSubscriber(sessionId, ws),
|
|
15942
16175
|
startGraceTimer: (sessionId, delayMs) => this.startGraceTimer(sessionId, delayMs),
|
|
15943
16176
|
armHoldWhenIdle: (sessionId) => this.armHoldWhenIdle(sessionId),
|
|
15944
16177
|
handleSessionsCount: (res) => this.handleSessionsCount(res),
|
|
@@ -16068,6 +16301,12 @@ var StreamerServer = class {
|
|
|
16068
16301
|
);
|
|
16069
16302
|
}
|
|
16070
16303
|
}
|
|
16304
|
+
removeSessionSubscriber(sessionId, ws) {
|
|
16305
|
+
const subs = this.sessionSubscribers.get(sessionId);
|
|
16306
|
+
if (!subs) return;
|
|
16307
|
+
subs.delete(ws);
|
|
16308
|
+
if (subs.size === 0) this.sessionSubscribers.delete(sessionId);
|
|
16309
|
+
}
|
|
16071
16310
|
/**
|
|
16072
16311
|
* Bring up Live Activity push, if credentials are present (Feature 12).
|
|
16073
16312
|
*
|
|
@@ -16398,6 +16637,10 @@ var StreamerServer = class {
|
|
|
16398
16637
|
this.idleReaperTimer = setInterval(() => this.reapIdleSessions(), IDLE_REAP_SWEEP_MS);
|
|
16399
16638
|
this.idleReaperTimer.unref?.();
|
|
16400
16639
|
}
|
|
16640
|
+
this.hostPressureMonitor = createHostPressureMonitor(
|
|
16641
|
+
this.wsHub,
|
|
16642
|
+
() => this.ptyAttachedIds().size
|
|
16643
|
+
);
|
|
16401
16644
|
const warmUp = new Promise((resolveWarm) => {
|
|
16402
16645
|
{
|
|
16403
16646
|
this.log.info(`Streamer server listening on port ${port}`, {
|
|
@@ -16710,6 +16953,8 @@ var StreamerServer = class {
|
|
|
16710
16953
|
clearInterval(this.idleReaperTimer);
|
|
16711
16954
|
this.idleReaperTimer = null;
|
|
16712
16955
|
}
|
|
16956
|
+
this.hostPressureMonitor?.dispose();
|
|
16957
|
+
this.hostPressureMonitor = null;
|
|
16713
16958
|
this.lastAgentChunkAt.clear();
|
|
16714
16959
|
this.terminalSeq.clear();
|
|
16715
16960
|
if (this.ptyManager.isRemote()) this.ptyManager.dispose();
|