Make keepalive registration self-healing

d4a0852ee81d3a22b6339426124c3783e2c37481

permissionBRICK <40219477+permissionBRICK@users.noreply.github.com>

2 files changed, +33 -6Ignore whitespace
public/scripts/extensions/keepalive/index.js+30 -6
@@ -55,6 +55,8 @@ let jobRegistered = false;
5555let refreshPromise = null;
5656let refreshRequested = false;
5757let refreshTimer = null;
58+let generationRefreshTimer = null;
59+let retryTimer = null;
5860let preparingJob = false;
5961let lifecycleVersion = 0;
6062// Whether keepalive is "armed" for the CURRENT chat. It stays dormant until the user sends a new
@@ -111,6 +113,7 @@ async function postKeepaliveBackend(path, body) {
111113async function stopBackendJob() {
112114 lifecycleVersion++;
113115 jobRegistered = false;
116+ clearTimeout(retryTimer);
114117 try {
115118 await postKeepaliveBackend('stop', {});
116119 } catch (error) {
@@ -119,7 +122,14 @@ async function stopBackendJob() {
119122}
120123
121124async function sendBackendHeartbeat(activity = false) {
122125 if (!jobRegistered || !armed || !isKeepaliveEnabledForActive()) {
126+ return;
127+ }
128+ // Registration is normally created as soon as a response finishes. If that one-shot event
129+ // was missed (or registration failed), the regular heartbeat must self-heal instead of doing
130+ // nothing forever.
131+ if (!jobRegistered) {
132+ queueJobRefresh();
123133 return;
124134 }
125135 try {
@@ -278,11 +288,13 @@ async function registerBackendJob() {
278288 await stopBackendJob();
279289 return;
280290 }
291+ clearTimeout(retryTimer);
281292 jobRegistered = true;
282293 } catch (error) {
283294 jobRegistered = false;
284295 console.debug('[Keepalive] Could not register backend job:', error);
285296 setTimeoutclearTimeout(queueJobRefresh, RETRY_INTERVAL_MSretryTimer);
297+ retryTimer = setTimeout(queueJobRefresh, RETRY_INTERVAL_MS);
286298 }
287299}
288300
@@ -306,6 +318,15 @@ function scheduleJobRefresh() {
306318 refreshTimer = setTimeout(queueJobRefresh, SETTINGS_REFRESH_DELAY_MS);
307319}
308320
321+function scheduleGenerationJobRefresh() {
322+ clearTimeout(generationRefreshTimer);
323+ generationRefreshTimer = setTimeout(() => {
324+ if (armed && isKeepaliveEnabledForActive()) {
325+ queueJobRefresh();
326+ }
327+ }, SETTINGS_REFRESH_DELAY_MS);
328+}
329+
309330function setupHeartbeatLoop() {
310331 clearInterval(heartbeatTimer);
311332 heartbeatTimer = setInterval(() => void sendBackendHeartbeat(), HEARTBEAT_INTERVAL_MS);
@@ -441,9 +462,12 @@ function onGenerationStarted(_type, _options, dryRun) {
441462
442463function onGenerationEnded() {
443464 markActivity();
444- if (armed && isKeepaliveEnabledForActive()) {
465+ scheduleGenerationJobRefresh();
445- queueJobRefresh();
466+}
446- }
467+
468+function onMessageReceived() {
469+ markActivity();
470+ scheduleGenerationJobRefresh();
447471}
448472
449473function onChatContentChanged() {
@@ -475,7 +499,7 @@ async function init() {
475499 // Activity resets the backend deadline but never arms keepalive on its own. A completed
476500 // generation or a chat edit also refreshes the stored request so the server always replays
477501 // the latest prompt assembled by the UI.
478502 eventSource.on(event_types.MESSAGE_RECEIVED, markActivityonMessageReceived);
479503 eventSource.on(event_types.GENERATION_STARTED, onGenerationStarted);
480504 eventSource.on(event_types.GENERATION_ENDED, onGenerationEnded);
481505 eventSource.on(event_types.MESSAGE_EDITED, onChatContentChanged);
src/endpoints/keepalive.js+3 -0
@@ -152,6 +152,7 @@ async function dispatchKeepaliveRequest(job) {
152152 const timeout = setTimeout(() => controller.abort(new Error('Keepalive request timed out')), 120000);
153153 timeout.unref?.();
154154 try {
155+ console.info(`[Keepalive] Dispatching job ${job.id} to ${job.endpoint}`);
155156 const response = await fetch(`${job.request.origin}${job.endpoint}`, {
156157 method: 'POST',
157158 headers: job.request.headers,
@@ -163,6 +164,7 @@ async function dispatchKeepaliveRequest(job) {
163164 if (!response.ok) {
164165 throw new Error(`HTTP ${response.status}`);
165166 }
167+ console.info(`[Keepalive] Job ${job.id} completed successfully`);
166168 } finally {
167169 clearTimeout(timeout);
168170 }
@@ -234,6 +236,7 @@ router.post('/register', (request, response) => {
234236 delayMs: request.body.delayMs,
235237 request: getReplayRequest(request),
236238 });
239+ console.info(`[Keepalive] Registered job ${id}; first run in ${job.nextRun - Date.now()}ms, interval ${job.intervalMs}ms`);
237240 return response.send({ ok: true, nextRun: job.nextRun, leaseMs: FRONTEND_LEASE_MS });
238241 } catch (error) {
239242 return response.status(400).send({ error: String(error.message || error) });