Repository navigation
feat(core,kde): opt-in session keepalive to open the 5-hour window early - #11
Open
danielbanariba wants to merge 2 commits into
Open
danielbanariba wants to merge 2 commits into
danielbanariba wants to merge 2 commits into
Conversation
… scheduler hook Add shared/core/keepalive.js: buildShellCommand() wraps a user command with the PATH prefix needed under plasmashell (which does not see the user's shell PATH) and a timeout guard; shouldKeepalive() decides whether to ping based on enabled state, freshness of the poll result (OK/PARTIAL_DATA only, never stale/retained data), whether a 5-hour session window is already active, remaining weekly quota, and a cooldown; createKeepalive() wires those into a controller with onProviderResult()/getStatus() that never throws out of the scheduler's poll loop. Hook the controller into shared/core/scheduler.js right after a provider result is applied, and expose its status on the summary object so shared/ui/render.js can render one warning line on the Claude card when the last attempt failed (e.g. "Keepalive failed (exit 127)"). The hook is a no-op when no keepalive controller is injected, so the GNOME extension (which never wires one) is unaffected.
Add keepaliveEnabled (Bool, default false) and keepaliveCommand (String,
defaulting to the verified `claude -p ok --model haiku ...` ping) to both
Plasma variants' main.xml, and a matching CheckBox + advanced TextField in
configGeneral.qml explaining the mechanics and the PATH caveat.
Extend ExecReader.qml with runShell(cmd), reusing the existing "executable"
Plasma DataSource (already used for reading credentials) to run an
already-composed shell command asynchronously and resolve {exitCode,
stderr}. Wire main.qml to pass isEnabled/getCommand/runCommand -- backed by
Plasmoid.configuration and reader.runShell -- into App.createApp(), and
extend kde/shared/code/app.mjs to build a keepalive controller from those
deps and inject it into the scheduler. Both Plasma 5 and Plasma 6 trees are
kept in parity.
Document the feature (mechanics, token cost, PATH note, GNOME follow-up) in
the README and list keepalive.js in CLAUDE.md's architecture overview.
This was referenced Sep 8, 2026
danielbanariba
force-pushed
the
feat/session-keepalive
branch
from
September 10, 2026 06:56
2c6de06 to
c6cf379
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #10
Summary
shared/core/keepalive.js:shouldKeepalive()decides whether to ping (enabled, fresh poll result, no active 5-hour window, weekly quota not exhausted, 30-minute cooldown),buildShellCommand()wraps the user's command with the PATH prefix a plasmashell-style environment needs plus a 120s timeout,createKeepalive()wires those into a controller that never throws out of the scheduler's poll loop.shared/core/scheduler.jscalls the controller once per Claude poll result, right afterapplyProviderResult/backoffManager.recordResult. With no controller injected (the default, and always true for the GNOME extension) the scheduler behaves exactly as before.shared/ui/render.js) when the last attempt failed, e.g. "Keepalive failed (exit 127)", so a brokenPATHor command is visible instead of silent.keepaliveEnabled(bool, default false) andkeepaliveCommand(string, defaults to the ping command below) in bothmain.xmls, a checkbox + advanced text field in bothconfigGeneral.qmls, andrunShell()added toExecReader.qml— reusing the same Plasma "executable" DataSource the widget already uses to read the credentials file. Plasma 5 and 6 trees kept in parity.keepalive.jsadded to theCLAUDE.mdarchitecture list.Why
The 5-hour session window opens at the first message and closes exactly 5 hours later. If the machine is on well before the first real message, the reset lands later than it needs to. A tiny ping at boot (or whenever no window is active) opens the window earlier, which only ever moves the reset earlier — unused quota does not carry over, so this cannot hurt. Default
keepaliveCommand:~240 input / ~40 output tokens per ping, at most a few per day.
plasmashelldoes not see the user's shellPATH(confirmed viasystemctl --user show-environmentand/proc/<pid>/environon the machine this was built on — both show only/usr/local/bin:/usr/bin), sobuildShellCommand()prepends$HOME/.local/binand$HOME/.claude/localitself rather than relying on a login shell.Changes
shared/core/keepalive.jsshouldKeepalive,buildShellCommand,createKeepaliveshared/core/scheduler.jskeepalivedependency; hook called after each Claude poll result; status exposed on the summary objectshared/ui/render.jstoKeepaliveWarningText(); one warning line on the Claude cardshared/index.mjskde/shared/code/app.mjscreateApp()acceptsisEnabled/getCommand/runCommandand builds the keepalive controllerkde/plasma{5,6}/contents/config/main.xmlkeepaliveEnabled,keepaliveCommandentrieskde/plasma{5,6}/contents/ui/configGeneral.qmlkde/plasma{5,6}/contents/ui/ExecReader.qmlrunShell(cmd)— runs an already-composed command through the existing DataSource, resolves{exitCode, stderr}kde/plasma{5,6}/contents/ui/main.qmlPlasmoid.configuration.keepalive*andreader.runShellintoApp.createApp()README.md,CLAUDE.mdtest/unit/keepalive.test.js, additions toscheduler.test.jsandui-render.test.jsTest plan
test/unit/keepalive.test.jscovers: enabled/disabled, every fresh vs. stale status code, an active vs. expired vs. missing/unparsablesessionResetsAtIso(including the grace period boundary), weekly-exhausted, the cooldown,buildShellCommand()'s exact output, andcreateKeepalive()'s controller behavior (records the attempt time before the runner resolves, never throws when the runner rejects or is missing, only acts on theclaudeprovider).scheduler.test.jsadditions: the hook fires with the mapped{code, data}shape after a Claude poll, and is a silent no-op when nokeepaliveoption is passed.ui-render.test.jsadditions: the warning line appears only after a failed attempt, and is absent otherwise.main, it is 96 passed / 1 pre-existing, unrelated failure —bun:test'svi.advanceTimersByTimeshim, present onmainbefore this change).plasma5andplasma6viascripts/build/bundle-kde.sh; confirmed byrgthat the compiled bundle contains the new identifiers, and that esbuild's ES2015 target correctly downlevels every?./??/async/awaitthis code uses (zero matches for?./??in the compiled output).five_hourblock is classified by the existing Claude provider (PARTIAL_DATAwhen the block is entirely absent,OKwithsessionResetsAtIso: nullwhen present but zeroed) and confirmed by test that the keepalive trigger fires correctly in both shapes.Notes for review
{code, data}(post-applyProviderResult), not the provider's raw{ok, data, error}— this avoids re-deriving the OK/PARTIAL_DATA/etc. classification a second time insidekeepalive.js. Happy to change the shape if you'd rather it stayed closer to the raw provider result.extension/is untouched. Opening a follow-up issue for it if this lands.ExecReader.qml's existingreadFile/resolveHome.