@threadbase-sh/streamer 1.42.0 → 1.43.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/cli.cjs +137 -8
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +137 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +137 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1815,6 +1815,7 @@ declare class StreamerServer {
|
|
|
1815
1815
|
private dbInstanceId;
|
|
1816
1816
|
private disableDb;
|
|
1817
1817
|
private skipStartupWarmup;
|
|
1818
|
+
private autoResumeOnBoot;
|
|
1818
1819
|
private browseRoot;
|
|
1819
1820
|
private publicUrl;
|
|
1820
1821
|
private browserCors;
|
|
@@ -1881,6 +1882,7 @@ declare class StreamerServer {
|
|
|
1881
1882
|
* a full broadcast if no match exists (old clients, or no WS registered yet).
|
|
1882
1883
|
*/
|
|
1883
1884
|
private broadcastOrUnicastSessionList;
|
|
1885
|
+
private sessionListPayload;
|
|
1884
1886
|
/**
|
|
1885
1887
|
* Overlay boot-reconciliation verdicts onto session responses.
|
|
1886
1888
|
*
|
|
@@ -1944,6 +1946,8 @@ declare class StreamerServer {
|
|
|
1944
1946
|
* by id rather than duplicating it.
|
|
1945
1947
|
*/
|
|
1946
1948
|
private rehydratePreviousSessions;
|
|
1949
|
+
/** Resume only the recent sessions the user explicitly allowed us to start at boot. */
|
|
1950
|
+
private autoResumePreviousSessions;
|
|
1947
1951
|
/**
|
|
1948
1952
|
* Pick a token guaranteed to appear in the spawned process's argv, for the
|
|
1949
1953
|
* reconciler's pid-reuse guard.
|
package/dist/index.d.ts
CHANGED
|
@@ -1815,6 +1815,7 @@ declare class StreamerServer {
|
|
|
1815
1815
|
private dbInstanceId;
|
|
1816
1816
|
private disableDb;
|
|
1817
1817
|
private skipStartupWarmup;
|
|
1818
|
+
private autoResumeOnBoot;
|
|
1818
1819
|
private browseRoot;
|
|
1819
1820
|
private publicUrl;
|
|
1820
1821
|
private browserCors;
|
|
@@ -1881,6 +1882,7 @@ declare class StreamerServer {
|
|
|
1881
1882
|
* a full broadcast if no match exists (old clients, or no WS registered yet).
|
|
1882
1883
|
*/
|
|
1883
1884
|
private broadcastOrUnicastSessionList;
|
|
1885
|
+
private sessionListPayload;
|
|
1884
1886
|
/**
|
|
1885
1887
|
* Overlay boot-reconciliation verdicts onto session responses.
|
|
1886
1888
|
*
|
|
@@ -1944,6 +1946,8 @@ declare class StreamerServer {
|
|
|
1944
1946
|
* by id rather than duplicating it.
|
|
1945
1947
|
*/
|
|
1946
1948
|
private rehydratePreviousSessions;
|
|
1949
|
+
/** Resume only the recent sessions the user explicitly allowed us to start at boot. */
|
|
1950
|
+
private autoResumePreviousSessions;
|
|
1947
1951
|
/**
|
|
1948
1952
|
* Pick a token guaranteed to appear in the spawned process's argv, for the
|
|
1949
1953
|
* reconciler's pid-reuse guard.
|
package/dist/index.js
CHANGED
|
@@ -8781,6 +8781,34 @@ function paginate(results, offset, limit) {
|
|
|
8781
8781
|
};
|
|
8782
8782
|
}
|
|
8783
8783
|
|
|
8784
|
+
// src/services/sessions/autoResumeOnBoot.ts
|
|
8785
|
+
var AUTO_RESUME_WINDOW_MS = 15 * 60 * 1e3;
|
|
8786
|
+
var AUTO_RESUME_MAX = 5;
|
|
8787
|
+
var AUTO_RESUME_CONCURRENCY = 2;
|
|
8788
|
+
var AUTO_RESUME_STAGGER_MS = 500;
|
|
8789
|
+
function autoResumeSkipReason(row, opts) {
|
|
8790
|
+
if (row.status_source !== "shutdown") return "not_shutdown";
|
|
8791
|
+
if (row.status !== "running" && row.status !== "waiting_input") return "not_interrupted";
|
|
8792
|
+
if (opts.now - row.status_updated_at > AUTO_RESUME_WINDOW_MS) return "too_old";
|
|
8793
|
+
if (!opts.projectExists(row.project_path)) return "project_missing";
|
|
8794
|
+
if (resumeIdForRow(row) == null) return "resume_identity_missing";
|
|
8795
|
+
return null;
|
|
8796
|
+
}
|
|
8797
|
+
function planAutoResume(rows, opts) {
|
|
8798
|
+
const eligible = [];
|
|
8799
|
+
const skipped = [];
|
|
8800
|
+
for (const row of rows) {
|
|
8801
|
+
const reason = autoResumeSkipReason(row, opts);
|
|
8802
|
+
if (reason) skipped.push({ row, reason });
|
|
8803
|
+
else eligible.push(row);
|
|
8804
|
+
}
|
|
8805
|
+
return {
|
|
8806
|
+
attempts: eligible.slice(0, AUTO_RESUME_MAX),
|
|
8807
|
+
skipped,
|
|
8808
|
+
overflow: eligible.slice(AUTO_RESUME_MAX)
|
|
8809
|
+
};
|
|
8810
|
+
}
|
|
8811
|
+
|
|
8784
8812
|
// src/services/sessions/conversationBusy.ts
|
|
8785
8813
|
import { statSync as statSync8 } from "fs";
|
|
8786
8814
|
var RESUME_BUSY_WINDOW_MS = 12e4;
|
|
@@ -9622,6 +9650,7 @@ var StreamerServer = class {
|
|
|
9622
9650
|
disableDb = false;
|
|
9623
9651
|
// Skip the startup warm-up scan (test hook; see ServerConfig.skipStartupWarmup).
|
|
9624
9652
|
skipStartupWarmup;
|
|
9653
|
+
autoResumeOnBoot;
|
|
9625
9654
|
browseRoot = null;
|
|
9626
9655
|
publicUrl = null;
|
|
9627
9656
|
browserCors;
|
|
@@ -9738,6 +9767,7 @@ var StreamerServer = class {
|
|
|
9738
9767
|
this.verbose = config.verbose ?? false;
|
|
9739
9768
|
this.disableDb = config.disableDb ?? false;
|
|
9740
9769
|
this.skipStartupWarmup = config.skipStartupWarmup ?? false;
|
|
9770
|
+
this.autoResumeOnBoot = config.autoResumeOnBoot ?? false;
|
|
9741
9771
|
this.scannerPersistenceDisabled = config.scannerPersistent === false;
|
|
9742
9772
|
this.scanProfiles = config.scanProfiles;
|
|
9743
9773
|
this.codexRoots = config.codexRoots ?? [join18(homedir9(), ".codex", "sessions")];
|
|
@@ -10218,16 +10248,19 @@ var StreamerServer = class {
|
|
|
10218
10248
|
broadcastOrUnicastSessionList(req) {
|
|
10219
10249
|
const clientId = req.headers["x-client-id"];
|
|
10220
10250
|
const ws = typeof clientId === "string" ? this.clientIdToWs.get(clientId) : void 0;
|
|
10221
|
-
const payload =
|
|
10222
|
-
type: "session_list",
|
|
10223
|
-
sessions: this.withReconciledLifecycle(this.sessionStore.list(this.ptyAttachedIds()))
|
|
10224
|
-
};
|
|
10251
|
+
const payload = this.sessionListPayload();
|
|
10225
10252
|
if (ws) {
|
|
10226
10253
|
this.wsHub.unicast(ws, payload);
|
|
10227
10254
|
} else {
|
|
10228
10255
|
this.wsHub.broadcast(payload);
|
|
10229
10256
|
}
|
|
10230
10257
|
}
|
|
10258
|
+
sessionListPayload() {
|
|
10259
|
+
return {
|
|
10260
|
+
type: "session_list",
|
|
10261
|
+
sessions: this.withReconciledLifecycle(this.sessionStore.list(this.ptyAttachedIds()))
|
|
10262
|
+
};
|
|
10263
|
+
}
|
|
10231
10264
|
/**
|
|
10232
10265
|
* Overlay boot-reconciliation verdicts onto session responses.
|
|
10233
10266
|
*
|
|
@@ -10401,7 +10434,7 @@ var StreamerServer = class {
|
|
|
10401
10434
|
* by id rather than duplicating it.
|
|
10402
10435
|
*/
|
|
10403
10436
|
rehydratePreviousSessions(verdicts) {
|
|
10404
|
-
if (!this.
|
|
10437
|
+
if (!this.managedSessionsRepo) return [];
|
|
10405
10438
|
try {
|
|
10406
10439
|
const now = Date.now();
|
|
10407
10440
|
const rows = this.managedSessionsRepo.listRecoverable({
|
|
@@ -10410,7 +10443,7 @@ var StreamerServer = class {
|
|
|
10410
10443
|
});
|
|
10411
10444
|
const truncated = rows.length > REHYDRATE_MAX;
|
|
10412
10445
|
const candidates = truncated ? rows.slice(0, REHYDRATE_MAX) : rows;
|
|
10413
|
-
if (candidates.length === 0) return;
|
|
10446
|
+
if (!this.featureFlags.sessionRehydration || candidates.length === 0) return candidates;
|
|
10414
10447
|
const verdictById = new Map(verdicts.map((v) => [v.sessionId, v]));
|
|
10415
10448
|
let rehydrated = 0;
|
|
10416
10449
|
const skippedBy = {};
|
|
@@ -10445,12 +10478,107 @@ var StreamerServer = class {
|
|
|
10445
10478
|
skippedBy,
|
|
10446
10479
|
truncated
|
|
10447
10480
|
});
|
|
10481
|
+
return candidates;
|
|
10448
10482
|
} catch (err) {
|
|
10449
10483
|
this.log.warn("[rehydrate] failed to rehydrate previous sessions", {
|
|
10450
10484
|
event: "sessions.rehydrate_failed",
|
|
10451
10485
|
err
|
|
10452
10486
|
});
|
|
10487
|
+
return [];
|
|
10488
|
+
}
|
|
10489
|
+
}
|
|
10490
|
+
/** Resume only the recent sessions the user explicitly allowed us to start at boot. */
|
|
10491
|
+
async autoResumePreviousSessions(rows) {
|
|
10492
|
+
if (!this.autoResumeOnBoot) return;
|
|
10493
|
+
const plan = planAutoResume(rows, { now: Date.now(), projectExists: existsSync11 });
|
|
10494
|
+
const skippedBy = {};
|
|
10495
|
+
for (const { row, reason } of plan.skipped) {
|
|
10496
|
+
skippedBy[reason] = (skippedBy[reason] ?? 0) + 1;
|
|
10497
|
+
this.log.debug(`[auto-resume] skipped ${row.session_id}: ${reason}`, {
|
|
10498
|
+
event: "sessions.auto_resume_skipped",
|
|
10499
|
+
sessionId: row.session_id,
|
|
10500
|
+
reason
|
|
10501
|
+
});
|
|
10453
10502
|
}
|
|
10503
|
+
if (plan.skipped.length > 0) {
|
|
10504
|
+
this.log.info(
|
|
10505
|
+
`[auto-resume] left ${plan.skipped.length} ineligible session(s) for manual resume`,
|
|
10506
|
+
{
|
|
10507
|
+
event: "sessions.auto_resume_skipped",
|
|
10508
|
+
skipped: plan.skipped.length,
|
|
10509
|
+
skippedBy
|
|
10510
|
+
}
|
|
10511
|
+
);
|
|
10512
|
+
}
|
|
10513
|
+
for (const row of plan.overflow) {
|
|
10514
|
+
this.log.info(`[auto-resume] left ${row.session_id} for manual resume: ceiling reached`, {
|
|
10515
|
+
event: "sessions.auto_resume_skipped",
|
|
10516
|
+
sessionId: row.session_id,
|
|
10517
|
+
reason: "ceiling_reached"
|
|
10518
|
+
});
|
|
10519
|
+
}
|
|
10520
|
+
let resumed = 0;
|
|
10521
|
+
let failed = 0;
|
|
10522
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
10523
|
+
let started = 0;
|
|
10524
|
+
const resume = async (row) => {
|
|
10525
|
+
try {
|
|
10526
|
+
const outcome = await this.resumeSession({
|
|
10527
|
+
sessionId: row.session_id,
|
|
10528
|
+
projectName: row.project_name,
|
|
10529
|
+
branch: row.branch
|
|
10530
|
+
});
|
|
10531
|
+
if (!outcome.ok) {
|
|
10532
|
+
failed++;
|
|
10533
|
+
this.log.info(`[auto-resume] skipped ${row.session_id}: ${outcome.reason}`, {
|
|
10534
|
+
event: "sessions.auto_resume_skipped",
|
|
10535
|
+
sessionId: row.session_id,
|
|
10536
|
+
reason: outcome.reason,
|
|
10537
|
+
...outcome.reason === "conversation_busy" && {
|
|
10538
|
+
detectedBy: outcome.detectedBy,
|
|
10539
|
+
lastActivityMs: outcome.lastActivityMs,
|
|
10540
|
+
likelyOwner: outcome.likelyOwner
|
|
10541
|
+
}
|
|
10542
|
+
});
|
|
10543
|
+
return;
|
|
10544
|
+
}
|
|
10545
|
+
resumed++;
|
|
10546
|
+
this.log.info(`[auto-resume] resumed ${row.session_id}`, {
|
|
10547
|
+
event: "sessions.auto_resume_succeeded",
|
|
10548
|
+
sessionId: row.session_id,
|
|
10549
|
+
alreadyRunning: outcome.alreadyRunning
|
|
10550
|
+
});
|
|
10551
|
+
} catch (err) {
|
|
10552
|
+
failed++;
|
|
10553
|
+
this.log.warn(`[auto-resume] failed to resume ${row.session_id}`, {
|
|
10554
|
+
event: "sessions.auto_resume_failed",
|
|
10555
|
+
sessionId: row.session_id,
|
|
10556
|
+
err
|
|
10557
|
+
});
|
|
10558
|
+
}
|
|
10559
|
+
};
|
|
10560
|
+
for (const row of plan.attempts) {
|
|
10561
|
+
while (inFlight.size >= AUTO_RESUME_CONCURRENCY) {
|
|
10562
|
+
await Promise.race(inFlight);
|
|
10563
|
+
}
|
|
10564
|
+
if (started > 0) {
|
|
10565
|
+
await new Promise((resolve2) => setTimeout(resolve2, AUTO_RESUME_STAGGER_MS));
|
|
10566
|
+
}
|
|
10567
|
+
const task = resume(row);
|
|
10568
|
+
inFlight.add(task);
|
|
10569
|
+
void task.then(() => inFlight.delete(task));
|
|
10570
|
+
started++;
|
|
10571
|
+
}
|
|
10572
|
+
await Promise.all(inFlight);
|
|
10573
|
+
if (resumed > 0) this.wsHub.broadcast(this.sessionListPayload());
|
|
10574
|
+
this.log.info(`[auto-resume] completed boot recovery: ${resumed} resumed`, {
|
|
10575
|
+
event: "sessions.auto_resume_completed",
|
|
10576
|
+
attempted: plan.attempts.length,
|
|
10577
|
+
resumed,
|
|
10578
|
+
failed,
|
|
10579
|
+
ineligible: plan.skipped.length,
|
|
10580
|
+
overflow: plan.overflow.length
|
|
10581
|
+
});
|
|
10454
10582
|
}
|
|
10455
10583
|
/**
|
|
10456
10584
|
* Pick a token guaranteed to appear in the spawned process's argv, for the
|
|
@@ -10772,8 +10900,9 @@ var StreamerServer = class {
|
|
|
10772
10900
|
);
|
|
10773
10901
|
this.scannerPersistenceDisabled = true;
|
|
10774
10902
|
}
|
|
10775
|
-
void this.reconcilePreviousSessions().then((v) => {
|
|
10776
|
-
this.rehydratePreviousSessions(v);
|
|
10903
|
+
void this.reconcilePreviousSessions().then(async (v) => {
|
|
10904
|
+
const recoverableRows = this.rehydratePreviousSessions(v);
|
|
10905
|
+
await this.autoResumePreviousSessions(recoverableRows);
|
|
10777
10906
|
this.pruneTerminalSessions();
|
|
10778
10907
|
});
|
|
10779
10908
|
if (this.skipStartupWarmup) {
|