@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.cjs
CHANGED
|
@@ -8818,6 +8818,34 @@ function paginate(results, offset, limit) {
|
|
|
8818
8818
|
};
|
|
8819
8819
|
}
|
|
8820
8820
|
|
|
8821
|
+
// src/services/sessions/autoResumeOnBoot.ts
|
|
8822
|
+
var AUTO_RESUME_WINDOW_MS = 15 * 60 * 1e3;
|
|
8823
|
+
var AUTO_RESUME_MAX = 5;
|
|
8824
|
+
var AUTO_RESUME_CONCURRENCY = 2;
|
|
8825
|
+
var AUTO_RESUME_STAGGER_MS = 500;
|
|
8826
|
+
function autoResumeSkipReason(row, opts) {
|
|
8827
|
+
if (row.status_source !== "shutdown") return "not_shutdown";
|
|
8828
|
+
if (row.status !== "running" && row.status !== "waiting_input") return "not_interrupted";
|
|
8829
|
+
if (opts.now - row.status_updated_at > AUTO_RESUME_WINDOW_MS) return "too_old";
|
|
8830
|
+
if (!opts.projectExists(row.project_path)) return "project_missing";
|
|
8831
|
+
if (resumeIdForRow(row) == null) return "resume_identity_missing";
|
|
8832
|
+
return null;
|
|
8833
|
+
}
|
|
8834
|
+
function planAutoResume(rows, opts) {
|
|
8835
|
+
const eligible = [];
|
|
8836
|
+
const skipped = [];
|
|
8837
|
+
for (const row of rows) {
|
|
8838
|
+
const reason = autoResumeSkipReason(row, opts);
|
|
8839
|
+
if (reason) skipped.push({ row, reason });
|
|
8840
|
+
else eligible.push(row);
|
|
8841
|
+
}
|
|
8842
|
+
return {
|
|
8843
|
+
attempts: eligible.slice(0, AUTO_RESUME_MAX),
|
|
8844
|
+
skipped,
|
|
8845
|
+
overflow: eligible.slice(AUTO_RESUME_MAX)
|
|
8846
|
+
};
|
|
8847
|
+
}
|
|
8848
|
+
|
|
8821
8849
|
// src/services/sessions/conversationBusy.ts
|
|
8822
8850
|
var import_fs18 = require("fs");
|
|
8823
8851
|
var RESUME_BUSY_WINDOW_MS = 12e4;
|
|
@@ -9659,6 +9687,7 @@ var StreamerServer = class {
|
|
|
9659
9687
|
disableDb = false;
|
|
9660
9688
|
// Skip the startup warm-up scan (test hook; see ServerConfig.skipStartupWarmup).
|
|
9661
9689
|
skipStartupWarmup;
|
|
9690
|
+
autoResumeOnBoot;
|
|
9662
9691
|
browseRoot = null;
|
|
9663
9692
|
publicUrl = null;
|
|
9664
9693
|
browserCors;
|
|
@@ -9775,6 +9804,7 @@ var StreamerServer = class {
|
|
|
9775
9804
|
this.verbose = config.verbose ?? false;
|
|
9776
9805
|
this.disableDb = config.disableDb ?? false;
|
|
9777
9806
|
this.skipStartupWarmup = config.skipStartupWarmup ?? false;
|
|
9807
|
+
this.autoResumeOnBoot = config.autoResumeOnBoot ?? false;
|
|
9778
9808
|
this.scannerPersistenceDisabled = config.scannerPersistent === false;
|
|
9779
9809
|
this.scanProfiles = config.scanProfiles;
|
|
9780
9810
|
this.codexRoots = config.codexRoots ?? [(0, import_path18.join)((0, import_os10.homedir)(), ".codex", "sessions")];
|
|
@@ -10255,16 +10285,19 @@ var StreamerServer = class {
|
|
|
10255
10285
|
broadcastOrUnicastSessionList(req) {
|
|
10256
10286
|
const clientId = req.headers["x-client-id"];
|
|
10257
10287
|
const ws = typeof clientId === "string" ? this.clientIdToWs.get(clientId) : void 0;
|
|
10258
|
-
const payload =
|
|
10259
|
-
type: "session_list",
|
|
10260
|
-
sessions: this.withReconciledLifecycle(this.sessionStore.list(this.ptyAttachedIds()))
|
|
10261
|
-
};
|
|
10288
|
+
const payload = this.sessionListPayload();
|
|
10262
10289
|
if (ws) {
|
|
10263
10290
|
this.wsHub.unicast(ws, payload);
|
|
10264
10291
|
} else {
|
|
10265
10292
|
this.wsHub.broadcast(payload);
|
|
10266
10293
|
}
|
|
10267
10294
|
}
|
|
10295
|
+
sessionListPayload() {
|
|
10296
|
+
return {
|
|
10297
|
+
type: "session_list",
|
|
10298
|
+
sessions: this.withReconciledLifecycle(this.sessionStore.list(this.ptyAttachedIds()))
|
|
10299
|
+
};
|
|
10300
|
+
}
|
|
10268
10301
|
/**
|
|
10269
10302
|
* Overlay boot-reconciliation verdicts onto session responses.
|
|
10270
10303
|
*
|
|
@@ -10438,7 +10471,7 @@ var StreamerServer = class {
|
|
|
10438
10471
|
* by id rather than duplicating it.
|
|
10439
10472
|
*/
|
|
10440
10473
|
rehydratePreviousSessions(verdicts) {
|
|
10441
|
-
if (!this.
|
|
10474
|
+
if (!this.managedSessionsRepo) return [];
|
|
10442
10475
|
try {
|
|
10443
10476
|
const now = Date.now();
|
|
10444
10477
|
const rows = this.managedSessionsRepo.listRecoverable({
|
|
@@ -10447,7 +10480,7 @@ var StreamerServer = class {
|
|
|
10447
10480
|
});
|
|
10448
10481
|
const truncated = rows.length > REHYDRATE_MAX;
|
|
10449
10482
|
const candidates = truncated ? rows.slice(0, REHYDRATE_MAX) : rows;
|
|
10450
|
-
if (candidates.length === 0) return;
|
|
10483
|
+
if (!this.featureFlags.sessionRehydration || candidates.length === 0) return candidates;
|
|
10451
10484
|
const verdictById = new Map(verdicts.map((v) => [v.sessionId, v]));
|
|
10452
10485
|
let rehydrated = 0;
|
|
10453
10486
|
const skippedBy = {};
|
|
@@ -10482,12 +10515,107 @@ var StreamerServer = class {
|
|
|
10482
10515
|
skippedBy,
|
|
10483
10516
|
truncated
|
|
10484
10517
|
});
|
|
10518
|
+
return candidates;
|
|
10485
10519
|
} catch (err) {
|
|
10486
10520
|
this.log.warn("[rehydrate] failed to rehydrate previous sessions", {
|
|
10487
10521
|
event: "sessions.rehydrate_failed",
|
|
10488
10522
|
err
|
|
10489
10523
|
});
|
|
10524
|
+
return [];
|
|
10525
|
+
}
|
|
10526
|
+
}
|
|
10527
|
+
/** Resume only the recent sessions the user explicitly allowed us to start at boot. */
|
|
10528
|
+
async autoResumePreviousSessions(rows) {
|
|
10529
|
+
if (!this.autoResumeOnBoot) return;
|
|
10530
|
+
const plan = planAutoResume(rows, { now: Date.now(), projectExists: import_fs19.existsSync });
|
|
10531
|
+
const skippedBy = {};
|
|
10532
|
+
for (const { row, reason } of plan.skipped) {
|
|
10533
|
+
skippedBy[reason] = (skippedBy[reason] ?? 0) + 1;
|
|
10534
|
+
this.log.debug(`[auto-resume] skipped ${row.session_id}: ${reason}`, {
|
|
10535
|
+
event: "sessions.auto_resume_skipped",
|
|
10536
|
+
sessionId: row.session_id,
|
|
10537
|
+
reason
|
|
10538
|
+
});
|
|
10490
10539
|
}
|
|
10540
|
+
if (plan.skipped.length > 0) {
|
|
10541
|
+
this.log.info(
|
|
10542
|
+
`[auto-resume] left ${plan.skipped.length} ineligible session(s) for manual resume`,
|
|
10543
|
+
{
|
|
10544
|
+
event: "sessions.auto_resume_skipped",
|
|
10545
|
+
skipped: plan.skipped.length,
|
|
10546
|
+
skippedBy
|
|
10547
|
+
}
|
|
10548
|
+
);
|
|
10549
|
+
}
|
|
10550
|
+
for (const row of plan.overflow) {
|
|
10551
|
+
this.log.info(`[auto-resume] left ${row.session_id} for manual resume: ceiling reached`, {
|
|
10552
|
+
event: "sessions.auto_resume_skipped",
|
|
10553
|
+
sessionId: row.session_id,
|
|
10554
|
+
reason: "ceiling_reached"
|
|
10555
|
+
});
|
|
10556
|
+
}
|
|
10557
|
+
let resumed = 0;
|
|
10558
|
+
let failed = 0;
|
|
10559
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
10560
|
+
let started = 0;
|
|
10561
|
+
const resume = async (row) => {
|
|
10562
|
+
try {
|
|
10563
|
+
const outcome = await this.resumeSession({
|
|
10564
|
+
sessionId: row.session_id,
|
|
10565
|
+
projectName: row.project_name,
|
|
10566
|
+
branch: row.branch
|
|
10567
|
+
});
|
|
10568
|
+
if (!outcome.ok) {
|
|
10569
|
+
failed++;
|
|
10570
|
+
this.log.info(`[auto-resume] skipped ${row.session_id}: ${outcome.reason}`, {
|
|
10571
|
+
event: "sessions.auto_resume_skipped",
|
|
10572
|
+
sessionId: row.session_id,
|
|
10573
|
+
reason: outcome.reason,
|
|
10574
|
+
...outcome.reason === "conversation_busy" && {
|
|
10575
|
+
detectedBy: outcome.detectedBy,
|
|
10576
|
+
lastActivityMs: outcome.lastActivityMs,
|
|
10577
|
+
likelyOwner: outcome.likelyOwner
|
|
10578
|
+
}
|
|
10579
|
+
});
|
|
10580
|
+
return;
|
|
10581
|
+
}
|
|
10582
|
+
resumed++;
|
|
10583
|
+
this.log.info(`[auto-resume] resumed ${row.session_id}`, {
|
|
10584
|
+
event: "sessions.auto_resume_succeeded",
|
|
10585
|
+
sessionId: row.session_id,
|
|
10586
|
+
alreadyRunning: outcome.alreadyRunning
|
|
10587
|
+
});
|
|
10588
|
+
} catch (err) {
|
|
10589
|
+
failed++;
|
|
10590
|
+
this.log.warn(`[auto-resume] failed to resume ${row.session_id}`, {
|
|
10591
|
+
event: "sessions.auto_resume_failed",
|
|
10592
|
+
sessionId: row.session_id,
|
|
10593
|
+
err
|
|
10594
|
+
});
|
|
10595
|
+
}
|
|
10596
|
+
};
|
|
10597
|
+
for (const row of plan.attempts) {
|
|
10598
|
+
while (inFlight.size >= AUTO_RESUME_CONCURRENCY) {
|
|
10599
|
+
await Promise.race(inFlight);
|
|
10600
|
+
}
|
|
10601
|
+
if (started > 0) {
|
|
10602
|
+
await new Promise((resolve2) => setTimeout(resolve2, AUTO_RESUME_STAGGER_MS));
|
|
10603
|
+
}
|
|
10604
|
+
const task = resume(row);
|
|
10605
|
+
inFlight.add(task);
|
|
10606
|
+
void task.then(() => inFlight.delete(task));
|
|
10607
|
+
started++;
|
|
10608
|
+
}
|
|
10609
|
+
await Promise.all(inFlight);
|
|
10610
|
+
if (resumed > 0) this.wsHub.broadcast(this.sessionListPayload());
|
|
10611
|
+
this.log.info(`[auto-resume] completed boot recovery: ${resumed} resumed`, {
|
|
10612
|
+
event: "sessions.auto_resume_completed",
|
|
10613
|
+
attempted: plan.attempts.length,
|
|
10614
|
+
resumed,
|
|
10615
|
+
failed,
|
|
10616
|
+
ineligible: plan.skipped.length,
|
|
10617
|
+
overflow: plan.overflow.length
|
|
10618
|
+
});
|
|
10491
10619
|
}
|
|
10492
10620
|
/**
|
|
10493
10621
|
* Pick a token guaranteed to appear in the spawned process's argv, for the
|
|
@@ -10809,8 +10937,9 @@ var StreamerServer = class {
|
|
|
10809
10937
|
);
|
|
10810
10938
|
this.scannerPersistenceDisabled = true;
|
|
10811
10939
|
}
|
|
10812
|
-
void this.reconcilePreviousSessions().then((v) => {
|
|
10813
|
-
this.rehydratePreviousSessions(v);
|
|
10940
|
+
void this.reconcilePreviousSessions().then(async (v) => {
|
|
10941
|
+
const recoverableRows = this.rehydratePreviousSessions(v);
|
|
10942
|
+
await this.autoResumePreviousSessions(recoverableRows);
|
|
10814
10943
|
this.pruneTerminalSessions();
|
|
10815
10944
|
});
|
|
10816
10945
|
if (this.skipStartupWarmup) {
|