@threadbase-sh/streamer 1.64.0 → 1.66.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/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 +346 -16
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +346 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +334 -4
- package/dist/index.js.map +1 -1
- package/dist/server-wiring.d.ts +7 -0
- package/dist/server-wiring.d.ts.map +1 -1
- package/dist/server.d.ts +15 -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.cjs
CHANGED
|
@@ -4136,7 +4136,7 @@ var import_events = require("events");
|
|
|
4136
4136
|
var import_fs28 = require("fs");
|
|
4137
4137
|
var import_promises7 = require("fs/promises");
|
|
4138
4138
|
var import_http = require("http");
|
|
4139
|
-
var
|
|
4139
|
+
var import_os15 = require("os");
|
|
4140
4140
|
var import_path24 = require("path");
|
|
4141
4141
|
|
|
4142
4142
|
// src/api/app.ts
|
|
@@ -4979,7 +4979,7 @@ function redactPath(path) {
|
|
|
4979
4979
|
function worstStatus(checks) {
|
|
4980
4980
|
const rank = { ok: 0, unknown: 1, degraded: 2, failed: 3 };
|
|
4981
4981
|
return checks.reduce(
|
|
4982
|
-
(
|
|
4982
|
+
(worst2, c) => rank[c.status] > rank[worst2] ? c.status : worst2,
|
|
4983
4983
|
"ok"
|
|
4984
4984
|
);
|
|
4985
4985
|
}
|
|
@@ -5994,7 +5994,12 @@ var createMiscRoutes = (deps) => {
|
|
|
5994
5994
|
// Whether to encrypt to this server. Additive, same contract as `push`:
|
|
5995
5995
|
// absent means an older server, which a client must read as "unknown" and
|
|
5996
5996
|
// resolve as today's plaintext path — never as a reason to fail.
|
|
5997
|
-
e2ee: describeE2eeCapability(deps.featureFlagsConfig().values.e2ee)
|
|
5997
|
+
e2ee: describeE2eeCapability(deps.featureFlagsConfig().values.e2ee),
|
|
5998
|
+
// This build samples cheap host signals and pushes `host_pressure` when
|
|
5999
|
+
// the box is starved. Additive capability flag only — live readings stay
|
|
6000
|
+
// off this polled endpoint. Absent means an older server that never
|
|
6001
|
+
// samples. Informational: pressure never holds, kills, or refuses sessions.
|
|
6002
|
+
hostPressure: true
|
|
5998
6003
|
});
|
|
5999
6004
|
});
|
|
6000
6005
|
app.get("/api/profiles", (c) => c.json([]));
|
|
@@ -10660,6 +10665,19 @@ var SessionHandlers = class {
|
|
|
10660
10665
|
sessions: this.sessionStore.list(this.deps.ptyAttachedIds())
|
|
10661
10666
|
});
|
|
10662
10667
|
}
|
|
10668
|
+
/**
|
|
10669
|
+
* Same empty-unused check stop uses (promptCount === 0 and no cache row,
|
|
10670
|
+
* including boundConversationId / resumedFromConversationId). Safe after
|
|
10671
|
+
* putOnHold: the runner may already have dropped the live session.
|
|
10672
|
+
*/
|
|
10673
|
+
forgetIfEmptyUnused(sessionId) {
|
|
10674
|
+
const live = this.ptyManager.getSession(sessionId);
|
|
10675
|
+
const stored = this.sessionStore.getManaged(sessionId);
|
|
10676
|
+
const session = live ?? stored;
|
|
10677
|
+
if (!session) return;
|
|
10678
|
+
if (!this.shouldForgetEmptySession(session)) return;
|
|
10679
|
+
this.forgetEmptyStoppedSession(sessionId);
|
|
10680
|
+
}
|
|
10663
10681
|
async handleAdopt(sessionId, res) {
|
|
10664
10682
|
const discovered = await discoverClaudeProcesses();
|
|
10665
10683
|
this.sessionStore.setDiscovered(discovered);
|
|
@@ -12843,6 +12861,7 @@ function createLiveSessionOptions(deps) {
|
|
|
12843
12861
|
void deps.liveActivityNotifier()?.onStatusChange(session, previousStatus);
|
|
12844
12862
|
void deps.waitingInputNotifier()?.onStatusChange(session, previousStatus);
|
|
12845
12863
|
deps.sessionStatusBus.emit(`status:${session.id}`, session.status, session);
|
|
12864
|
+
deps.maybeFireHoldWhenIdle?.(session);
|
|
12846
12865
|
}
|
|
12847
12866
|
};
|
|
12848
12867
|
}
|
|
@@ -12866,6 +12885,7 @@ function createApiDeps(deps) {
|
|
|
12866
12885
|
wsHub: deps.wsHub,
|
|
12867
12886
|
cache: () => deps.cache(),
|
|
12868
12887
|
cacheMonitor: () => deps.cacheMonitor(),
|
|
12888
|
+
hostPressureMonitor: () => deps.hostPressureMonitor(),
|
|
12869
12889
|
pushRepo: () => deps.pushRepo(),
|
|
12870
12890
|
liveActivityPushEnabled: () => deps.liveActivityPushEnabled(),
|
|
12871
12891
|
devicesRepo: () => deps.devicesRepo(),
|
|
@@ -12916,6 +12936,8 @@ function createApiDeps(deps) {
|
|
|
12916
12936
|
}
|
|
12917
12937
|
const alertMsg = deps.cacheMonitor()?.wsMessage();
|
|
12918
12938
|
if (alertMsg) deps.wsHub.unicast(ws, alertMsg);
|
|
12939
|
+
const pressureMsg = deps.hostPressureMonitor()?.wsMessage();
|
|
12940
|
+
if (pressureMsg) deps.wsHub.unicast(ws, pressureMsg);
|
|
12919
12941
|
},
|
|
12920
12942
|
handleWsMessage: async (ws, raw, principal) => {
|
|
12921
12943
|
const deny = (type, required) => {
|
|
@@ -12991,7 +13013,20 @@ function createApiDeps(deps) {
|
|
|
12991
13013
|
deny(msg.type, "session:control");
|
|
12992
13014
|
return;
|
|
12993
13015
|
}
|
|
12994
|
-
|
|
13016
|
+
const when = msg.when;
|
|
13017
|
+
if (when === void 0 || when === "grace") {
|
|
13018
|
+
deps.startGraceTimer(msg.sessionId, deps.ptyGracePeriodMs);
|
|
13019
|
+
return;
|
|
13020
|
+
}
|
|
13021
|
+
if (when === "waiting_input") {
|
|
13022
|
+
deps.armHoldWhenIdle(msg.sessionId);
|
|
13023
|
+
return;
|
|
13024
|
+
}
|
|
13025
|
+
deps.log().warn(`[pty.hold_when_unknown] hold_session when=${String(when)}`, {
|
|
13026
|
+
event: "pty.hold_when_unknown",
|
|
13027
|
+
sessionId: msg.sessionId,
|
|
13028
|
+
when
|
|
13029
|
+
});
|
|
12995
13030
|
}
|
|
12996
13031
|
} catch {
|
|
12997
13032
|
}
|
|
@@ -13528,6 +13563,221 @@ function pruneAgentConversations(cache) {
|
|
|
13528
13563
|
return { scanned: rows.length, pruned, missing };
|
|
13529
13564
|
}
|
|
13530
13565
|
|
|
13566
|
+
// src/services/host-pressure/hostPressure.ts
|
|
13567
|
+
var import_os13 = require("os");
|
|
13568
|
+
var import_perf_hooks = require("perf_hooks");
|
|
13569
|
+
var HOST_PRESSURE_SAMPLE_MS = 5e3;
|
|
13570
|
+
var HOST_PRESSURE_BARS = {
|
|
13571
|
+
memFreeRatio: {
|
|
13572
|
+
enterElevated: 0.15,
|
|
13573
|
+
leaveElevated: 0.17,
|
|
13574
|
+
enterCritical: 0.08,
|
|
13575
|
+
leaveCritical: 0.15
|
|
13576
|
+
},
|
|
13577
|
+
eventLoopP99Ms: {
|
|
13578
|
+
enterElevated: 100,
|
|
13579
|
+
leaveElevated: 80,
|
|
13580
|
+
enterCritical: 250,
|
|
13581
|
+
leaveCritical: 100
|
|
13582
|
+
},
|
|
13583
|
+
loadPerCpu: {
|
|
13584
|
+
enterElevated: 1.25,
|
|
13585
|
+
leaveElevated: 1.05,
|
|
13586
|
+
enterCritical: 2,
|
|
13587
|
+
leaveCritical: 1.25
|
|
13588
|
+
},
|
|
13589
|
+
// win32 has no loadavg. Busy ratio from os.cpus()[].times deltas is 0–1, so
|
|
13590
|
+
// it cannot reuse loadPerCpu's 2.0 critical bar. Reason on the wire stays `load`.
|
|
13591
|
+
cpuBusy: {
|
|
13592
|
+
enterElevated: 0.85,
|
|
13593
|
+
leaveElevated: 0.7,
|
|
13594
|
+
enterCritical: 0.97,
|
|
13595
|
+
leaveCritical: 0.85
|
|
13596
|
+
},
|
|
13597
|
+
liveAgentsPair: 4
|
|
13598
|
+
};
|
|
13599
|
+
var REASON_ORDER = ["memory", "event_loop", "load", "agents"];
|
|
13600
|
+
var RANK = { ok: 0, elevated: 1, critical: 2 };
|
|
13601
|
+
function worst(levels) {
|
|
13602
|
+
return levels.reduce(
|
|
13603
|
+
(acc, level) => RANK[level] > RANK[acc] ? level : acc,
|
|
13604
|
+
"ok"
|
|
13605
|
+
);
|
|
13606
|
+
}
|
|
13607
|
+
function schmittLowIsWorse(value, previous, enterElevated, leaveElevated, enterCritical, leaveCritical) {
|
|
13608
|
+
if (previous === "critical") {
|
|
13609
|
+
if (value < leaveCritical) return "critical";
|
|
13610
|
+
if (value < leaveElevated) return "elevated";
|
|
13611
|
+
return "ok";
|
|
13612
|
+
}
|
|
13613
|
+
if (previous === "elevated") {
|
|
13614
|
+
if (value < enterCritical) return "critical";
|
|
13615
|
+
if (value < leaveElevated) return "elevated";
|
|
13616
|
+
return "ok";
|
|
13617
|
+
}
|
|
13618
|
+
if (value < enterCritical) return "critical";
|
|
13619
|
+
if (value < enterElevated) return "elevated";
|
|
13620
|
+
return "ok";
|
|
13621
|
+
}
|
|
13622
|
+
function schmittHighIsWorse(value, previous, enterElevated, leaveElevated, enterCritical, leaveCritical) {
|
|
13623
|
+
if (previous === "critical") {
|
|
13624
|
+
if (value > leaveCritical) return "critical";
|
|
13625
|
+
if (value > leaveElevated) return "elevated";
|
|
13626
|
+
return "ok";
|
|
13627
|
+
}
|
|
13628
|
+
if (previous === "elevated") {
|
|
13629
|
+
if (value > enterCritical) return "critical";
|
|
13630
|
+
if (value > leaveElevated) return "elevated";
|
|
13631
|
+
return "ok";
|
|
13632
|
+
}
|
|
13633
|
+
if (value > enterCritical) return "critical";
|
|
13634
|
+
if (value > enterElevated) return "elevated";
|
|
13635
|
+
return "ok";
|
|
13636
|
+
}
|
|
13637
|
+
function timesTotal(times) {
|
|
13638
|
+
return times.user + times.nice + times.sys + times.idle + times.irq;
|
|
13639
|
+
}
|
|
13640
|
+
function hostPressureOs(platform3) {
|
|
13641
|
+
if (platform3 === "darwin" || platform3 === "linux" || platform3 === "win32") return platform3;
|
|
13642
|
+
return void 0;
|
|
13643
|
+
}
|
|
13644
|
+
function cpuBusyRatio(previous, next) {
|
|
13645
|
+
if (!previous || previous.length === 0 || next.length === 0 || previous.length !== next.length) {
|
|
13646
|
+
return 0;
|
|
13647
|
+
}
|
|
13648
|
+
let idle = 0;
|
|
13649
|
+
let total = 0;
|
|
13650
|
+
for (let i = 0; i < next.length; i++) {
|
|
13651
|
+
const dt = timesTotal(next[i]) - timesTotal(previous[i]);
|
|
13652
|
+
if (dt <= 0) continue;
|
|
13653
|
+
total += dt;
|
|
13654
|
+
idle += Math.max(0, next[i].idle - previous[i].idle);
|
|
13655
|
+
}
|
|
13656
|
+
if (total <= 0) return 0;
|
|
13657
|
+
return 1 - idle / total;
|
|
13658
|
+
}
|
|
13659
|
+
function classifyHostPressure(sample, previous, platform3) {
|
|
13660
|
+
const memRaw = schmittLowIsWorse(
|
|
13661
|
+
sample.memFreeRatio,
|
|
13662
|
+
previous,
|
|
13663
|
+
HOST_PRESSURE_BARS.memFreeRatio.enterElevated,
|
|
13664
|
+
HOST_PRESSURE_BARS.memFreeRatio.leaveElevated,
|
|
13665
|
+
HOST_PRESSURE_BARS.memFreeRatio.enterCritical,
|
|
13666
|
+
HOST_PRESSURE_BARS.memFreeRatio.leaveCritical
|
|
13667
|
+
);
|
|
13668
|
+
const mem = platform3 === "darwin" && memRaw === "critical" ? "elevated" : memRaw;
|
|
13669
|
+
const eventLoop = schmittHighIsWorse(
|
|
13670
|
+
sample.eventLoopP99Ms,
|
|
13671
|
+
previous,
|
|
13672
|
+
HOST_PRESSURE_BARS.eventLoopP99Ms.enterElevated,
|
|
13673
|
+
HOST_PRESSURE_BARS.eventLoopP99Ms.leaveElevated,
|
|
13674
|
+
HOST_PRESSURE_BARS.eventLoopP99Ms.enterCritical,
|
|
13675
|
+
HOST_PRESSURE_BARS.eventLoopP99Ms.leaveCritical
|
|
13676
|
+
);
|
|
13677
|
+
const ncpu = sample.ncpu > 0 ? sample.ncpu : 1;
|
|
13678
|
+
const windows = platform3 === "win32";
|
|
13679
|
+
const loadValue = windows ? sample.cpuBusyRatio ?? 0 : sample.load1 / ncpu;
|
|
13680
|
+
const loadBars = windows ? HOST_PRESSURE_BARS.cpuBusy : HOST_PRESSURE_BARS.loadPerCpu;
|
|
13681
|
+
const load = schmittHighIsWorse(
|
|
13682
|
+
loadValue,
|
|
13683
|
+
previous,
|
|
13684
|
+
loadBars.enterElevated,
|
|
13685
|
+
loadBars.leaveElevated,
|
|
13686
|
+
loadBars.enterCritical,
|
|
13687
|
+
loadBars.leaveCritical
|
|
13688
|
+
);
|
|
13689
|
+
const resourceLevel = worst([mem, eventLoop, load]);
|
|
13690
|
+
const agentsPair = sample.liveAgents >= HOST_PRESSURE_BARS.liveAgentsPair && resourceLevel !== "ok";
|
|
13691
|
+
const level = resourceLevel;
|
|
13692
|
+
const firing = [];
|
|
13693
|
+
if (mem !== "ok") firing.push("memory");
|
|
13694
|
+
if (eventLoop !== "ok") firing.push("event_loop");
|
|
13695
|
+
if (load !== "ok") firing.push("load");
|
|
13696
|
+
if (agentsPair) firing.push("agents");
|
|
13697
|
+
const reasons = REASON_ORDER.filter((reason) => firing.includes(reason));
|
|
13698
|
+
return { level, reasons };
|
|
13699
|
+
}
|
|
13700
|
+
var HostPressureMonitor = class {
|
|
13701
|
+
constructor(opts) {
|
|
13702
|
+
this.opts = opts;
|
|
13703
|
+
}
|
|
13704
|
+
opts;
|
|
13705
|
+
timer = null;
|
|
13706
|
+
level = "ok";
|
|
13707
|
+
lastWarning = null;
|
|
13708
|
+
start() {
|
|
13709
|
+
this.opts.histogram?.enable();
|
|
13710
|
+
if (this.timer) return;
|
|
13711
|
+
const intervalMs = this.opts.intervalMs ?? HOST_PRESSURE_SAMPLE_MS;
|
|
13712
|
+
this.timer = setInterval(() => this.tick(), intervalMs);
|
|
13713
|
+
this.timer.unref?.();
|
|
13714
|
+
}
|
|
13715
|
+
tick() {
|
|
13716
|
+
const sample = this.opts.readSample();
|
|
13717
|
+
const platform3 = this.opts.platform ?? process.platform;
|
|
13718
|
+
const classified = classifyHostPressure(sample, this.level, platform3);
|
|
13719
|
+
if (classified.level === this.level) return;
|
|
13720
|
+
this.level = classified.level;
|
|
13721
|
+
const updatedAt = (this.opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
|
|
13722
|
+
if (classified.level === "ok") {
|
|
13723
|
+
this.lastWarning = null;
|
|
13724
|
+
this.opts.wsHub.broadcast({ type: "host_pressure_cleared", updatedAt });
|
|
13725
|
+
return;
|
|
13726
|
+
}
|
|
13727
|
+
const os2 = hostPressureOs(platform3);
|
|
13728
|
+
const message = {
|
|
13729
|
+
type: "host_pressure",
|
|
13730
|
+
level: classified.level,
|
|
13731
|
+
reasons: classified.reasons,
|
|
13732
|
+
liveAgents: sample.liveAgents,
|
|
13733
|
+
updatedAt,
|
|
13734
|
+
...os2 ? { os: os2 } : {}
|
|
13735
|
+
};
|
|
13736
|
+
this.lastWarning = message;
|
|
13737
|
+
this.opts.wsHub.broadcast(message);
|
|
13738
|
+
}
|
|
13739
|
+
wsMessage() {
|
|
13740
|
+
return this.lastWarning;
|
|
13741
|
+
}
|
|
13742
|
+
dispose() {
|
|
13743
|
+
if (this.timer) {
|
|
13744
|
+
clearInterval(this.timer);
|
|
13745
|
+
this.timer = null;
|
|
13746
|
+
}
|
|
13747
|
+
this.opts.histogram?.disable();
|
|
13748
|
+
}
|
|
13749
|
+
};
|
|
13750
|
+
function createHostPressureMonitor(wsHub, liveAgents) {
|
|
13751
|
+
const histogram = (0, import_perf_hooks.monitorEventLoopDelay)({ resolution: 20 });
|
|
13752
|
+
const windows = process.platform === "win32";
|
|
13753
|
+
let prevCpuTimes = null;
|
|
13754
|
+
const monitor = new HostPressureMonitor({
|
|
13755
|
+
wsHub,
|
|
13756
|
+
histogram,
|
|
13757
|
+
readSample: () => {
|
|
13758
|
+
const eventLoopP99Ms = histogram.percentile(99) / 1e6;
|
|
13759
|
+
histogram.reset();
|
|
13760
|
+
const total = (0, import_os13.totalmem)();
|
|
13761
|
+
const cpuList = (0, import_os13.cpus)();
|
|
13762
|
+
const sample = {
|
|
13763
|
+
liveAgents: liveAgents(),
|
|
13764
|
+
memFreeRatio: total > 0 ? (0, import_os13.freemem)() / total : 1,
|
|
13765
|
+
eventLoopP99Ms,
|
|
13766
|
+
load1: (0, import_os13.loadavg)()[0],
|
|
13767
|
+
ncpu: cpuList.length
|
|
13768
|
+
};
|
|
13769
|
+
if (windows) {
|
|
13770
|
+
const cpuTimes = cpuList.map((cpu) => cpu.times);
|
|
13771
|
+
sample.cpuBusyRatio = cpuBusyRatio(prevCpuTimes, cpuTimes);
|
|
13772
|
+
prevCpuTimes = cpuTimes;
|
|
13773
|
+
}
|
|
13774
|
+
return sample;
|
|
13775
|
+
}
|
|
13776
|
+
});
|
|
13777
|
+
monitor.start();
|
|
13778
|
+
return monitor;
|
|
13779
|
+
}
|
|
13780
|
+
|
|
13531
13781
|
// src/services/push/expoPushSender.ts
|
|
13532
13782
|
var log5 = getLogger("expo-push");
|
|
13533
13783
|
var EXPO_PUSH_ENDPOINT = "https://exp.host/--/api/v2/push/send";
|
|
@@ -15027,7 +15277,7 @@ function discoveredToResponse(d, conversationId) {
|
|
|
15027
15277
|
|
|
15028
15278
|
// src/session-watchers.ts
|
|
15029
15279
|
var import_fs27 = require("fs");
|
|
15030
|
-
var
|
|
15280
|
+
var import_os14 = require("os");
|
|
15031
15281
|
var import_path23 = require("path");
|
|
15032
15282
|
var SessionWatchers = class {
|
|
15033
15283
|
constructor(deps) {
|
|
@@ -15109,7 +15359,7 @@ var SessionWatchers = class {
|
|
|
15109
15359
|
// was passed to Claude via --session-id so the filename matches from the start.
|
|
15110
15360
|
watchForJsonl(sessionId, projectPath) {
|
|
15111
15361
|
const encoded = projectPath.replace(/[/\\:.]/g, "-");
|
|
15112
|
-
const projectsDir = (0, import_path23.join)((0,
|
|
15362
|
+
const projectsDir = (0, import_path23.join)((0, import_os14.homedir)(), ".claude", "projects", encoded);
|
|
15113
15363
|
const expectedFile = `${sessionId}.jsonl`;
|
|
15114
15364
|
const filePath = (0, import_path23.join)(projectsDir, expectedFile);
|
|
15115
15365
|
const deadline = Date.now() + 12e4;
|
|
@@ -15547,6 +15797,10 @@ var StreamerServer = class {
|
|
|
15547
15797
|
// Consecutive grace-timer defers for a still-`running` session (see
|
|
15548
15798
|
// GRACE_MAX_DEFERS). Reset when a subscriber reconnects or the PTY settles.
|
|
15549
15799
|
ptyGraceDeferCounts = /* @__PURE__ */ new Map();
|
|
15800
|
+
// Session ids that should be putOnHold at the next waiting_input/idle.
|
|
15801
|
+
// Same lifetime as ptyGraceTimers: in-memory, dropped on close/restart.
|
|
15802
|
+
// Last writer wins against the grace timer — never both armed.
|
|
15803
|
+
holdWhenIdle = /* @__PURE__ */ new Set();
|
|
15550
15804
|
// Map of sessionId → set of subscribed WS clients
|
|
15551
15805
|
sessionSubscribers = /* @__PURE__ */ new Map();
|
|
15552
15806
|
// sessionId → wall-clock ms of the last PTY chunk. Written from onOutput for
|
|
@@ -15572,6 +15826,7 @@ var StreamerServer = class {
|
|
|
15572
15826
|
wsToClientId = /* @__PURE__ */ new Map();
|
|
15573
15827
|
cache = null;
|
|
15574
15828
|
cacheMonitor = null;
|
|
15829
|
+
hostPressureMonitor = null;
|
|
15575
15830
|
projectsRepo = null;
|
|
15576
15831
|
conversationsRepo = null;
|
|
15577
15832
|
sessionsRepo = null;
|
|
@@ -15636,7 +15891,7 @@ var StreamerServer = class {
|
|
|
15636
15891
|
this.skipStartupWarmup = config.skipStartupWarmup ?? false;
|
|
15637
15892
|
this.autoResumeOnBoot = config.autoResumeOnBoot ?? false;
|
|
15638
15893
|
this.scanProfiles = config.scanProfiles;
|
|
15639
|
-
this.codexRoots = config.codexRoots ?? [(0, import_path24.join)((0,
|
|
15894
|
+
this.codexRoots = config.codexRoots ?? [(0, import_path24.join)((0, import_os15.homedir)(), ".codex", "sessions")];
|
|
15640
15895
|
this.ptyGracePeriodMs = config.ptyGracePeriodMs ?? DEFAULT_PTY_GRACE_PERIOD_MS;
|
|
15641
15896
|
this.defaultSystemPrompt = config.defaultSystemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
15642
15897
|
const flagResolution = resolveFeatureFlags({
|
|
@@ -15653,7 +15908,7 @@ var StreamerServer = class {
|
|
|
15653
15908
|
this.claudeFlagsPersistable = config.claudeFlags === void 0;
|
|
15654
15909
|
this.claudeFlags = config.claudeFlags ?? loadClaudeFlags();
|
|
15655
15910
|
this.claudeExtraArgs = config.claudeExtraArgs ?? loadClaudeExtraArgs();
|
|
15656
|
-
this.cacheDir = config.cacheDir ?? loadCacheDir() ?? (0, import_path24.join)((0,
|
|
15911
|
+
this.cacheDir = config.cacheDir ?? loadCacheDir() ?? (0, import_path24.join)((0, import_os15.homedir)(), ".threadbase", "cache");
|
|
15657
15912
|
this.runtimeDbPath = resolveRuntimeDbPath(config.runtimeDbPath);
|
|
15658
15913
|
this.tailSize = config.tailSize ?? loadTailSize() ?? 10;
|
|
15659
15914
|
this.directoryDebounceMs = parseDirScanDebounceEnv(process.env.THREADBASE_DIR_SCAN_DEBOUNCE_MS) ?? config.directoryScanDebounceMs ?? 1e3;
|
|
@@ -15774,7 +16029,8 @@ var StreamerServer = class {
|
|
|
15774
16029
|
waitingInputNotifier: () => this.waitingInputNotifier,
|
|
15775
16030
|
ptyAttachedIds: () => this.ptyAttachedIds(),
|
|
15776
16031
|
cancelPendingQuestion: (sessionId) => this.cancelPendingQuestion(sessionId),
|
|
15777
|
-
rememberSelfPtyEnded: (conversationId) => this.rememberSelfPtyEnded(conversationId)
|
|
16032
|
+
rememberSelfPtyEnded: (conversationId) => this.rememberSelfPtyEnded(conversationId),
|
|
16033
|
+
maybeFireHoldWhenIdle: (session) => this.maybeFireHoldWhenIdle(session)
|
|
15778
16034
|
})
|
|
15779
16035
|
);
|
|
15780
16036
|
this.sessionWatchers = new SessionWatchers({
|
|
@@ -15921,6 +16177,7 @@ var StreamerServer = class {
|
|
|
15921
16177
|
// reset-and-rescan, and tests swap methods on the server instance.
|
|
15922
16178
|
cache: () => this.cache,
|
|
15923
16179
|
cacheMonitor: () => this.cacheMonitor,
|
|
16180
|
+
hostPressureMonitor: () => this.hostPressureMonitor,
|
|
15924
16181
|
pushRepo: () => this.pushRepo,
|
|
15925
16182
|
liveActivityPushEnabled: () => this.liveActivityNotifier !== null,
|
|
15926
16183
|
devicesRepo: () => this.devicesRepo,
|
|
@@ -15937,6 +16194,7 @@ var StreamerServer = class {
|
|
|
15937
16194
|
currentWarmupState: () => this.currentWarmupState(),
|
|
15938
16195
|
addSessionSubscriber: (sessionId, ws) => this.addSessionSubscriber(sessionId, ws),
|
|
15939
16196
|
startGraceTimer: (sessionId, delayMs) => this.startGraceTimer(sessionId, delayMs),
|
|
16197
|
+
armHoldWhenIdle: (sessionId) => this.armHoldWhenIdle(sessionId),
|
|
15940
16198
|
handleSessionsCount: (res) => this.handleSessionsCount(res),
|
|
15941
16199
|
applyLiveSessionSetting: (id, req, res, setting) => this.applyLiveSessionSetting(id, req, res, setting),
|
|
15942
16200
|
handlePairStart: (res) => this.handlePairStart(res),
|
|
@@ -16056,6 +16314,13 @@ var StreamerServer = class {
|
|
|
16056
16314
|
this.ptyGraceTimers.delete(sessionId);
|
|
16057
16315
|
}
|
|
16058
16316
|
this.ptyGraceDeferCounts.delete(sessionId);
|
|
16317
|
+
if (this.holdWhenIdle.delete(sessionId)) {
|
|
16318
|
+
this.log.info(
|
|
16319
|
+
`[hold-when-idle] cancelled ${sessionId} (subscribe)`,
|
|
16320
|
+
{ sessionId, event: "pty.hold_when_idle_cancel", reason: "subscribe" },
|
|
16321
|
+
"pino"
|
|
16322
|
+
);
|
|
16323
|
+
}
|
|
16059
16324
|
}
|
|
16060
16325
|
/**
|
|
16061
16326
|
* Bring up Live Activity push, if credentials are present (Feature 12).
|
|
@@ -16083,14 +16348,14 @@ var StreamerServer = class {
|
|
|
16083
16348
|
}
|
|
16084
16349
|
this.apnsClient = new ApnsClient(creds);
|
|
16085
16350
|
const sender = new LiveActivitySender(this.apnsClient, pushRepo);
|
|
16086
|
-
const serverId = process.env.THREADBASE_INSTANCE_ID ?? (0,
|
|
16087
|
-
this.liveActivityNotifier = new LiveActivityNotifier(sender, serverId, (0,
|
|
16351
|
+
const serverId = process.env.THREADBASE_INSTANCE_ID ?? (0, import_os15.hostname)();
|
|
16352
|
+
this.liveActivityNotifier = new LiveActivityNotifier(sender, serverId, (0, import_os15.hostname)());
|
|
16088
16353
|
this.liveActivityRenewal = new LiveActivityRenewalScheduler({
|
|
16089
16354
|
repo: pushRepo,
|
|
16090
16355
|
sender,
|
|
16091
16356
|
sessionStore: this.sessionStore,
|
|
16092
16357
|
serverId,
|
|
16093
|
-
serverLabel: (0,
|
|
16358
|
+
serverLabel: (0, import_os15.hostname)()
|
|
16094
16359
|
});
|
|
16095
16360
|
this.liveActivityRenewal.start();
|
|
16096
16361
|
this.log.info("Live Activity push enabled", {
|
|
@@ -16110,7 +16375,7 @@ var StreamerServer = class {
|
|
|
16110
16375
|
*/
|
|
16111
16376
|
initWaitingInputPush(pushRepo) {
|
|
16112
16377
|
const sender = new ExpoPushSender(pushRepo, process.env.THREADBASE_EXPO_ACCESS_TOKEN);
|
|
16113
|
-
const serverId = process.env.THREADBASE_INSTANCE_ID ?? (0,
|
|
16378
|
+
const serverId = process.env.THREADBASE_INSTANCE_ID ?? (0, import_os15.hostname)();
|
|
16114
16379
|
this.waitingInputNotifier = new WaitingInputNotifier(
|
|
16115
16380
|
sender,
|
|
16116
16381
|
serverId,
|
|
@@ -16163,6 +16428,7 @@ var StreamerServer = class {
|
|
|
16163
16428
|
return reaped;
|
|
16164
16429
|
}
|
|
16165
16430
|
startGraceTimer(sessionId, delayMs) {
|
|
16431
|
+
this.holdWhenIdle.delete(sessionId);
|
|
16166
16432
|
const existing = this.ptyGraceTimers.get(sessionId);
|
|
16167
16433
|
if (existing) clearTimeout(existing);
|
|
16168
16434
|
const timer = setTimeout(() => {
|
|
@@ -16204,6 +16470,63 @@ var StreamerServer = class {
|
|
|
16204
16470
|
}, delayMs);
|
|
16205
16471
|
this.ptyGraceTimers.set(sessionId, timer);
|
|
16206
16472
|
}
|
|
16473
|
+
clearGrace(sessionId) {
|
|
16474
|
+
const existing = this.ptyGraceTimers.get(sessionId);
|
|
16475
|
+
if (existing) {
|
|
16476
|
+
clearTimeout(existing);
|
|
16477
|
+
this.ptyGraceTimers.delete(sessionId);
|
|
16478
|
+
}
|
|
16479
|
+
this.ptyGraceDeferCounts.delete(sessionId);
|
|
16480
|
+
}
|
|
16481
|
+
/**
|
|
16482
|
+
* Arm the in-app "Kill on idle" latch: hold now if already settled, otherwise
|
|
16483
|
+
* on the next running → waiting_input (or idle). No grace delay, no defer cap.
|
|
16484
|
+
* A subscribed leaving socket must not block an immediate hold.
|
|
16485
|
+
*/
|
|
16486
|
+
armHoldWhenIdle(sessionId) {
|
|
16487
|
+
if (!this.ptyManager.hasSession(sessionId)) return;
|
|
16488
|
+
this.clearGrace(sessionId);
|
|
16489
|
+
const status = this.ptyManager.getSession(sessionId)?.status ?? this.sessionStore.getManaged(sessionId)?.status;
|
|
16490
|
+
if (status === "waiting_input" || status === "idle") {
|
|
16491
|
+
this.holdWhenIdle.delete(sessionId);
|
|
16492
|
+
this.ptyManager.putOnHold(sessionId);
|
|
16493
|
+
this.forgetIfEmptyUnused(sessionId);
|
|
16494
|
+
return;
|
|
16495
|
+
}
|
|
16496
|
+
this.holdWhenIdle.add(sessionId);
|
|
16497
|
+
this.log.info(
|
|
16498
|
+
`[hold-when-idle] armed ${sessionId}`,
|
|
16499
|
+
{ sessionId, event: "pty.hold_when_idle_armed" },
|
|
16500
|
+
"pino"
|
|
16501
|
+
);
|
|
16502
|
+
}
|
|
16503
|
+
/**
|
|
16504
|
+
* Fire the Kill-on-idle latch from the shared onStatusChange funnel.
|
|
16505
|
+
* Delete first so the ensuing idle transition cannot re-enter.
|
|
16506
|
+
*/
|
|
16507
|
+
maybeFireHoldWhenIdle(session) {
|
|
16508
|
+
if (session.status !== "waiting_input" && session.status !== "idle") return;
|
|
16509
|
+
if (!this.holdWhenIdle.has(session.id)) return;
|
|
16510
|
+
this.holdWhenIdle.delete(session.id);
|
|
16511
|
+
if (this.hasSessionSubscriber(session.id)) {
|
|
16512
|
+
this.log.info(
|
|
16513
|
+
`[hold-when-idle] cancelled ${session.id} (subscriber)`,
|
|
16514
|
+
{ sessionId: session.id, event: "pty.hold_when_idle_cancel", reason: "subscriber" },
|
|
16515
|
+
"pino"
|
|
16516
|
+
);
|
|
16517
|
+
return;
|
|
16518
|
+
}
|
|
16519
|
+
this.log.info(
|
|
16520
|
+
`[hold-when-idle] holding ${session.id}`,
|
|
16521
|
+
{ sessionId: session.id, event: "pty.hold_when_idle_fire" },
|
|
16522
|
+
"pino"
|
|
16523
|
+
);
|
|
16524
|
+
this.ptyManager.putOnHold(session.id);
|
|
16525
|
+
this.forgetIfEmptyUnused(session.id);
|
|
16526
|
+
}
|
|
16527
|
+
forgetIfEmptyUnused(sessionId) {
|
|
16528
|
+
this.sessionHandlers.forgetIfEmptyUnused(sessionId);
|
|
16529
|
+
}
|
|
16207
16530
|
get port() {
|
|
16208
16531
|
const addr = this.httpServer.address();
|
|
16209
16532
|
return typeof addr === "object" && addr ? addr.port : 0;
|
|
@@ -16282,7 +16605,7 @@ var StreamerServer = class {
|
|
|
16282
16605
|
let sessions = null;
|
|
16283
16606
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
16284
16607
|
const transport = await connectOrSpawnHost({
|
|
16285
|
-
instanceId: process.env.THREADBASE_INSTANCE_ID ?? (0,
|
|
16608
|
+
instanceId: process.env.THREADBASE_INSTANCE_ID ?? (0, import_os15.hostname)()
|
|
16286
16609
|
});
|
|
16287
16610
|
try {
|
|
16288
16611
|
sessions = await this.ptyManager.useRemoteRunner(transport);
|
|
@@ -16329,6 +16652,10 @@ var StreamerServer = class {
|
|
|
16329
16652
|
this.idleReaperTimer = setInterval(() => this.reapIdleSessions(), IDLE_REAP_SWEEP_MS);
|
|
16330
16653
|
this.idleReaperTimer.unref?.();
|
|
16331
16654
|
}
|
|
16655
|
+
this.hostPressureMonitor = createHostPressureMonitor(
|
|
16656
|
+
this.wsHub,
|
|
16657
|
+
() => this.ptyAttachedIds().size
|
|
16658
|
+
);
|
|
16332
16659
|
const warmUp = new Promise((resolveWarm) => {
|
|
16333
16660
|
{
|
|
16334
16661
|
this.log.info(`Streamer server listening on port ${port}`, {
|
|
@@ -16636,10 +16963,13 @@ var StreamerServer = class {
|
|
|
16636
16963
|
async close() {
|
|
16637
16964
|
for (const timer of this.ptyGraceTimers.values()) clearTimeout(timer);
|
|
16638
16965
|
this.ptyGraceTimers.clear();
|
|
16966
|
+
this.holdWhenIdle.clear();
|
|
16639
16967
|
if (this.idleReaperTimer) {
|
|
16640
16968
|
clearInterval(this.idleReaperTimer);
|
|
16641
16969
|
this.idleReaperTimer = null;
|
|
16642
16970
|
}
|
|
16971
|
+
this.hostPressureMonitor?.dispose();
|
|
16972
|
+
this.hostPressureMonitor = null;
|
|
16643
16973
|
this.lastAgentChunkAt.clear();
|
|
16644
16974
|
this.terminalSeq.clear();
|
|
16645
16975
|
if (this.ptyManager.isRemote()) this.ptyManager.dispose();
|
|
@@ -16833,7 +17163,7 @@ var StreamerServer = class {
|
|
|
16833
17163
|
deviceToken: device.deviceToken,
|
|
16834
17164
|
capabilities: device.capabilities,
|
|
16835
17165
|
publicUrl: this.publicUrl,
|
|
16836
|
-
machineName: (0,
|
|
17166
|
+
machineName: (0, import_os15.hostname)(),
|
|
16837
17167
|
serverVersion: getVersion()
|
|
16838
17168
|
})
|
|
16839
17169
|
);
|
|
@@ -16867,7 +17197,7 @@ var StreamerServer = class {
|
|
|
16867
17197
|
// Released builds that predate that fix DO still adopt it, so changing
|
|
16868
17198
|
// this value moves where old devices talk. It is not a free field.
|
|
16869
17199
|
publicUrl: this.publicUrl,
|
|
16870
|
-
machineName: (0,
|
|
17200
|
+
machineName: (0, import_os15.hostname)(),
|
|
16871
17201
|
...device && {
|
|
16872
17202
|
deviceId: device.deviceId,
|
|
16873
17203
|
deviceToken: device.deviceToken,
|