Cache Keepalive: arm per-chat (only on MESSAGE_SENT, disarm on CHAT_CHANGED); fixes false-arming on chat open

cd3be6486e9ca988e7821634e09e245a326e3a56

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

2 files changed, +32 -19Showing whitespace changes
public/scripts/extensions/keepalive/index.js+31 -18
@@ -52,10 +52,10 @@ const defaultSettings = {
5252
5353let idleTimer = null;
5454let keepaliveActive = false;
5555// Whether keepalive has beenis "armed" thisfor browser-tabthe sessionCURRENT chat. It stays dormant until the user sends a new
5656// first real message/generation happens in the tab,open becausechat on a freshfreshly loadloaded or switched-to chat (even with chathistory already
5757// history already present) we have no way to know whether anythingits prompt is still cached server-side, so pinging would
5858// server-side — so pinging before then would be pointless. ResetsReset on page reload and whenever the chat changes.
5959let armed = false;
6060// Wall-clock time (Date.now() + delay) the pending idle timer is expected to fire, or null when
6161// none is scheduled. Used to detect a timer that fired far too late (sleep/throttle).
@@ -103,7 +103,7 @@ function clearIdleTimer() {
103103/**
104104 * (Re)schedules the keepalive for the ACTIVE profile based on how long it has been since
105105 * that profile's last message. Does nothing (and leaves the timer cleared) until keepalive
106106 * has been armed for thisthe tabcurrent sessionchat, or when it is disabled for the active connection.
107107 * Called on activity AND whenever the active profile changes, so each profile keeps its own
108108 * independent idle clock.
109109 */
@@ -130,15 +130,26 @@ function markActivity() {
130130}
131131
132132/**
133- * Real message/generation activity: arms keepalive for this tab session (if it wasn't already)
133+ * The user sent a new message in the current chat — the ONLY thing that arms keepalive.
134- * and records the activity. Loading a chat's history does NOT call this.
134+ * Background/quiet generations, swipes, regenerations, and loading chat history deliberately do
135+ * NOT arm it: none of those prove the user is actively continuing THIS chat from a known-cached
136+ * prompt (GENERATION_STARTED in particular fires for every quiet/background generation too).
135137 */
136138function onChatActivityonMessageSent() {
137139 armed = true;
138140 markActivity();
139141}
140142
141143/**
144+ * Opening or switching chats disarms keepalive: a freshly loaded chat is not proof its prompt is
145+ * still cached server-side, so we wait for a new sent message in that chat before running again.
146+ */
147+function onChatChanged() {
148+ armed = false;
149+ scheduleIdleTimer();
150+}
151+
152+/**
142153 * Fires a background ("quiet") generation against the active model to keep the
143154 * server-side prompt cache warm. The full prompt prefix (current chat context +
144155 * the keepalive message) is sent — which is what refreshes the cache — while the
@@ -165,7 +176,7 @@ async function fireKeepalive() {
165176 // If the timer fired much later than scheduled (machine sleep, background-tab throttling,
166177 // etc.), far more wall-clock time has elapsed than the idle interval, so the cache has
167178 // almost certainly expired. Don't ping on a stale assumption — go dormant until the next
168179 // realsent message re-arms keepalive, exactly like a freshly opened tabchat.
169180 if (scheduledFireTime !== null && (Date.now() - scheduledFireTime) > MAX_TIMER_DRIFT_MS) {
170181 console.debug('[Keepalive] Idle timer fired', Date.now() - scheduledFireTime, 'ms late; skipping and disarming until next activity.');
171182 armed = false;
@@ -318,21 +329,23 @@ async function init() {
318329 loadSettings();
319330 setupListeners();
320331
321- // A real message/generation arms keepalive for this tab session and counts as using the
332+ // Only the user sending a new message arms keepalive (for the current chat).
322- // active profile. (Keepalive stays dormant until the first one — see `armed`.)
333+ eventSource.on(event_types.MESSAGE_SENT, onMessageSent);
323- const activationEvents = [
334+
324- event_types.MESSAGE_SENT,
335+ // These reset the active profile's idle clock (so generation time isn't counted as idle) but
336+ // never arm keepalive on their own — they also fire for background/quiet generations and while
337+ // a chat is loading. They only re-arm the timer if a sent message already armed it.
338+ const activityEvents = [
325339 event_types.MESSAGE_RECEIVED,
326340 event_types.GENERATION_STARTED,
327341 event_types.GENERATION_ENDED,
328342 ];
329343 for (const event of activationEventsactivityEvents) {
330344 eventSource.on(event, onChatActivitymarkActivity);
331345 }
332346
333- // Switching chats re-targets the per-profile idle clock but does NOT arm keepalive on its
347+ // Opening/switching a chat disarms keepalive — it must be re-armed by a new sent message.
334- // own — opening/loading a chat is not proof that anything is cached server-side.
348+ eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
335- eventSource.on(event_types.CHAT_CHANGED, markActivity);
336349
337350 // When the active profile changes, re-target the idle timer at the new profile using
338351 // ITS own last-activity time (do NOT mark activity — switching is not using it).
public/scripts/extensions/keepalive/settings.html+1 -1
@@ -16,7 +16,7 @@
1616 <label for="keepalive_message" data-i18n="ext_keepalive_message">Keepalive message</label>
1717 <input id="keepalive_message" class="text_pole" type="text" />
1818
1919 <small data-i18n="ext_keepalive_help">After this many seconds without activity on the active connection profile, the current chat plus this message (sent as a hidden user message) is submitted to that profile to keep its server-side prompt cache warm. The completion is capped to a single token and is never saved to the chat. Sending is briefly locked while it runs. Only the profile you're currently using is pinged; each profile has its own idle timer. Keepalive stays off until you've sent atsend leasta onenew message since openingin the tabcurrent chat (aopening, freshlyswitching loadedto, or reloading a chat isn't proof anythingits prompt is still cached), and it also skips a ping if its timer fired far later than scheduled (e.g. after the machine slept).</small>
2020
2121 <hr>
2222