@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/cli.cjs
CHANGED
|
@@ -146439,6 +146439,34 @@ function paginate(results, offset, limit) {
|
|
|
146439
146439
|
};
|
|
146440
146440
|
}
|
|
146441
146441
|
|
|
146442
|
+
// src/services/sessions/autoResumeOnBoot.ts
|
|
146443
|
+
var AUTO_RESUME_WINDOW_MS = 15 * 60 * 1e3;
|
|
146444
|
+
var AUTO_RESUME_MAX = 5;
|
|
146445
|
+
var AUTO_RESUME_CONCURRENCY = 2;
|
|
146446
|
+
var AUTO_RESUME_STAGGER_MS = 500;
|
|
146447
|
+
function autoResumeSkipReason(row, opts) {
|
|
146448
|
+
if (row.status_source !== "shutdown") return "not_shutdown";
|
|
146449
|
+
if (row.status !== "running" && row.status !== "waiting_input") return "not_interrupted";
|
|
146450
|
+
if (opts.now - row.status_updated_at > AUTO_RESUME_WINDOW_MS) return "too_old";
|
|
146451
|
+
if (!opts.projectExists(row.project_path)) return "project_missing";
|
|
146452
|
+
if (resumeIdForRow(row) == null) return "resume_identity_missing";
|
|
146453
|
+
return null;
|
|
146454
|
+
}
|
|
146455
|
+
function planAutoResume(rows, opts) {
|
|
146456
|
+
const eligible = [];
|
|
146457
|
+
const skipped = [];
|
|
146458
|
+
for (const row of rows) {
|
|
146459
|
+
const reason = autoResumeSkipReason(row, opts);
|
|
146460
|
+
if (reason) skipped.push({ row, reason });
|
|
146461
|
+
else eligible.push(row);
|
|
146462
|
+
}
|
|
146463
|
+
return {
|
|
146464
|
+
attempts: eligible.slice(0, AUTO_RESUME_MAX),
|
|
146465
|
+
skipped,
|
|
146466
|
+
overflow: eligible.slice(AUTO_RESUME_MAX)
|
|
146467
|
+
};
|
|
146468
|
+
}
|
|
146469
|
+
|
|
146442
146470
|
// src/services/sessions/conversationBusy.ts
|
|
146443
146471
|
var import_fs29 = require("fs");
|
|
146444
146472
|
var RESUME_BUSY_WINDOW_MS = 12e4;
|
|
@@ -147312,6 +147340,7 @@ var StreamerServer = class {
|
|
|
147312
147340
|
disableDb = false;
|
|
147313
147341
|
// Skip the startup warm-up scan (test hook; see ServerConfig.skipStartupWarmup).
|
|
147314
147342
|
skipStartupWarmup;
|
|
147343
|
+
autoResumeOnBoot;
|
|
147315
147344
|
browseRoot = null;
|
|
147316
147345
|
publicUrl = null;
|
|
147317
147346
|
browserCors;
|
|
@@ -147428,6 +147457,7 @@ var StreamerServer = class {
|
|
|
147428
147457
|
this.verbose = config2.verbose ?? false;
|
|
147429
147458
|
this.disableDb = config2.disableDb ?? false;
|
|
147430
147459
|
this.skipStartupWarmup = config2.skipStartupWarmup ?? false;
|
|
147460
|
+
this.autoResumeOnBoot = config2.autoResumeOnBoot ?? false;
|
|
147431
147461
|
this.scannerPersistenceDisabled = config2.scannerPersistent === false;
|
|
147432
147462
|
this.scanProfiles = config2.scanProfiles;
|
|
147433
147463
|
this.codexRoots = config2.codexRoots ?? [(0, import_path29.join)((0, import_os13.homedir)(), ".codex", "sessions")];
|
|
@@ -147908,16 +147938,19 @@ var StreamerServer = class {
|
|
|
147908
147938
|
broadcastOrUnicastSessionList(req) {
|
|
147909
147939
|
const clientId = req.headers["x-client-id"];
|
|
147910
147940
|
const ws2 = typeof clientId === "string" ? this.clientIdToWs.get(clientId) : void 0;
|
|
147911
|
-
const payload =
|
|
147912
|
-
type: "session_list",
|
|
147913
|
-
sessions: this.withReconciledLifecycle(this.sessionStore.list(this.ptyAttachedIds()))
|
|
147914
|
-
};
|
|
147941
|
+
const payload = this.sessionListPayload();
|
|
147915
147942
|
if (ws2) {
|
|
147916
147943
|
this.wsHub.unicast(ws2, payload);
|
|
147917
147944
|
} else {
|
|
147918
147945
|
this.wsHub.broadcast(payload);
|
|
147919
147946
|
}
|
|
147920
147947
|
}
|
|
147948
|
+
sessionListPayload() {
|
|
147949
|
+
return {
|
|
147950
|
+
type: "session_list",
|
|
147951
|
+
sessions: this.withReconciledLifecycle(this.sessionStore.list(this.ptyAttachedIds()))
|
|
147952
|
+
};
|
|
147953
|
+
}
|
|
147921
147954
|
/**
|
|
147922
147955
|
* Overlay boot-reconciliation verdicts onto session responses.
|
|
147923
147956
|
*
|
|
@@ -148091,7 +148124,7 @@ var StreamerServer = class {
|
|
|
148091
148124
|
* by id rather than duplicating it.
|
|
148092
148125
|
*/
|
|
148093
148126
|
rehydratePreviousSessions(verdicts) {
|
|
148094
|
-
if (!this.
|
|
148127
|
+
if (!this.managedSessionsRepo) return [];
|
|
148095
148128
|
try {
|
|
148096
148129
|
const now = Date.now();
|
|
148097
148130
|
const rows = this.managedSessionsRepo.listRecoverable({
|
|
@@ -148100,7 +148133,7 @@ var StreamerServer = class {
|
|
|
148100
148133
|
});
|
|
148101
148134
|
const truncated = rows.length > REHYDRATE_MAX;
|
|
148102
148135
|
const candidates = truncated ? rows.slice(0, REHYDRATE_MAX) : rows;
|
|
148103
|
-
if (candidates.length === 0) return;
|
|
148136
|
+
if (!this.featureFlags.sessionRehydration || candidates.length === 0) return candidates;
|
|
148104
148137
|
const verdictById = new Map(verdicts.map((v2) => [v2.sessionId, v2]));
|
|
148105
148138
|
let rehydrated = 0;
|
|
148106
148139
|
const skippedBy = {};
|
|
@@ -148135,12 +148168,107 @@ var StreamerServer = class {
|
|
|
148135
148168
|
skippedBy,
|
|
148136
148169
|
truncated
|
|
148137
148170
|
});
|
|
148171
|
+
return candidates;
|
|
148138
148172
|
} catch (err) {
|
|
148139
148173
|
this.log.warn("[rehydrate] failed to rehydrate previous sessions", {
|
|
148140
148174
|
event: "sessions.rehydrate_failed",
|
|
148141
148175
|
err
|
|
148142
148176
|
});
|
|
148177
|
+
return [];
|
|
148178
|
+
}
|
|
148179
|
+
}
|
|
148180
|
+
/** Resume only the recent sessions the user explicitly allowed us to start at boot. */
|
|
148181
|
+
async autoResumePreviousSessions(rows) {
|
|
148182
|
+
if (!this.autoResumeOnBoot) return;
|
|
148183
|
+
const plan = planAutoResume(rows, { now: Date.now(), projectExists: import_fs30.existsSync });
|
|
148184
|
+
const skippedBy = {};
|
|
148185
|
+
for (const { row, reason } of plan.skipped) {
|
|
148186
|
+
skippedBy[reason] = (skippedBy[reason] ?? 0) + 1;
|
|
148187
|
+
this.log.debug(`[auto-resume] skipped ${row.session_id}: ${reason}`, {
|
|
148188
|
+
event: "sessions.auto_resume_skipped",
|
|
148189
|
+
sessionId: row.session_id,
|
|
148190
|
+
reason
|
|
148191
|
+
});
|
|
148143
148192
|
}
|
|
148193
|
+
if (plan.skipped.length > 0) {
|
|
148194
|
+
this.log.info(
|
|
148195
|
+
`[auto-resume] left ${plan.skipped.length} ineligible session(s) for manual resume`,
|
|
148196
|
+
{
|
|
148197
|
+
event: "sessions.auto_resume_skipped",
|
|
148198
|
+
skipped: plan.skipped.length,
|
|
148199
|
+
skippedBy
|
|
148200
|
+
}
|
|
148201
|
+
);
|
|
148202
|
+
}
|
|
148203
|
+
for (const row of plan.overflow) {
|
|
148204
|
+
this.log.info(`[auto-resume] left ${row.session_id} for manual resume: ceiling reached`, {
|
|
148205
|
+
event: "sessions.auto_resume_skipped",
|
|
148206
|
+
sessionId: row.session_id,
|
|
148207
|
+
reason: "ceiling_reached"
|
|
148208
|
+
});
|
|
148209
|
+
}
|
|
148210
|
+
let resumed = 0;
|
|
148211
|
+
let failed = 0;
|
|
148212
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
148213
|
+
let started = 0;
|
|
148214
|
+
const resume = async (row) => {
|
|
148215
|
+
try {
|
|
148216
|
+
const outcome = await this.resumeSession({
|
|
148217
|
+
sessionId: row.session_id,
|
|
148218
|
+
projectName: row.project_name,
|
|
148219
|
+
branch: row.branch
|
|
148220
|
+
});
|
|
148221
|
+
if (!outcome.ok) {
|
|
148222
|
+
failed++;
|
|
148223
|
+
this.log.info(`[auto-resume] skipped ${row.session_id}: ${outcome.reason}`, {
|
|
148224
|
+
event: "sessions.auto_resume_skipped",
|
|
148225
|
+
sessionId: row.session_id,
|
|
148226
|
+
reason: outcome.reason,
|
|
148227
|
+
...outcome.reason === "conversation_busy" && {
|
|
148228
|
+
detectedBy: outcome.detectedBy,
|
|
148229
|
+
lastActivityMs: outcome.lastActivityMs,
|
|
148230
|
+
likelyOwner: outcome.likelyOwner
|
|
148231
|
+
}
|
|
148232
|
+
});
|
|
148233
|
+
return;
|
|
148234
|
+
}
|
|
148235
|
+
resumed++;
|
|
148236
|
+
this.log.info(`[auto-resume] resumed ${row.session_id}`, {
|
|
148237
|
+
event: "sessions.auto_resume_succeeded",
|
|
148238
|
+
sessionId: row.session_id,
|
|
148239
|
+
alreadyRunning: outcome.alreadyRunning
|
|
148240
|
+
});
|
|
148241
|
+
} catch (err) {
|
|
148242
|
+
failed++;
|
|
148243
|
+
this.log.warn(`[auto-resume] failed to resume ${row.session_id}`, {
|
|
148244
|
+
event: "sessions.auto_resume_failed",
|
|
148245
|
+
sessionId: row.session_id,
|
|
148246
|
+
err
|
|
148247
|
+
});
|
|
148248
|
+
}
|
|
148249
|
+
};
|
|
148250
|
+
for (const row of plan.attempts) {
|
|
148251
|
+
while (inFlight.size >= AUTO_RESUME_CONCURRENCY) {
|
|
148252
|
+
await Promise.race(inFlight);
|
|
148253
|
+
}
|
|
148254
|
+
if (started > 0) {
|
|
148255
|
+
await new Promise((resolve4) => setTimeout(resolve4, AUTO_RESUME_STAGGER_MS));
|
|
148256
|
+
}
|
|
148257
|
+
const task = resume(row);
|
|
148258
|
+
inFlight.add(task);
|
|
148259
|
+
void task.then(() => inFlight.delete(task));
|
|
148260
|
+
started++;
|
|
148261
|
+
}
|
|
148262
|
+
await Promise.all(inFlight);
|
|
148263
|
+
if (resumed > 0) this.wsHub.broadcast(this.sessionListPayload());
|
|
148264
|
+
this.log.info(`[auto-resume] completed boot recovery: ${resumed} resumed`, {
|
|
148265
|
+
event: "sessions.auto_resume_completed",
|
|
148266
|
+
attempted: plan.attempts.length,
|
|
148267
|
+
resumed,
|
|
148268
|
+
failed,
|
|
148269
|
+
ineligible: plan.skipped.length,
|
|
148270
|
+
overflow: plan.overflow.length
|
|
148271
|
+
});
|
|
148144
148272
|
}
|
|
148145
148273
|
/**
|
|
148146
148274
|
* Pick a token guaranteed to appear in the spawned process's argv, for the
|
|
@@ -148462,8 +148590,9 @@ var StreamerServer = class {
|
|
|
148462
148590
|
);
|
|
148463
148591
|
this.scannerPersistenceDisabled = true;
|
|
148464
148592
|
}
|
|
148465
|
-
void this.reconcilePreviousSessions().then((v2) => {
|
|
148466
|
-
this.rehydratePreviousSessions(v2);
|
|
148593
|
+
void this.reconcilePreviousSessions().then(async (v2) => {
|
|
148594
|
+
const recoverableRows = this.rehydratePreviousSessions(v2);
|
|
148595
|
+
await this.autoResumePreviousSessions(recoverableRows);
|
|
148467
148596
|
this.pruneTerminalSessions();
|
|
148468
148597
|
});
|
|
148469
148598
|
if (this.skipStartupWarmup) {
|