feat(daemon): offer operator-initiated restart to adopt an installed build (lr-e85fec) - #420
Conversation
…sub-agent permission on task_notification (lr-b75006) fix(sdk-message-processor): preserve activeTaskToolIds for a pending sub-agent permission on task_notification (lr-b75006)
Adds lib/build-update-check.js: checkForNewerInstalledBuild() compares the SHA a running daemon loaded at startup against a fresh read of lib/build-sha.json, and waitForIdleThenAct() is a small, unit-tested bounded idle-wait primitive (extracted so daemon.js's restart timing logic is testable without spinning up a real daemon process). Pure/detection-only in this commit -- no wiring into daemon.js yet. TASK: lr-e85fec
…(lr-e85fec)
Wires build-update-check.js's detection into a periodic (5min, plus
10s after startup) poll in lib/daemon.js. On a detected mismatch,
broadcasts a diagnostic with actionable:{label, action:
'restart_for_build_update'} -- the same diagnostic channel
lib/memory-shed.js already uses -- edge-triggered so it fires once
per detected staleness, not every poll.
lib/project-sessions.js gains a restart_for_build_update WS message,
gated by the same admin-only check as restart_server/shutdown_server.
Its handler (onRestartForBuildUpdate, lib/daemon.js) waits for
in-flight sessions to finish (waitForIdleThenAct, bounded 60s, same
getActiveLiveCount() signal drain.js uses) before calling the
existing spawnAndRestart() -- reused verbatim, not reimplemented.
No restart fires without that explicit operator click: no
post_merge_step, no timer, no automatic trigger. Detection and
actuation are on two different pollers/handlers precisely so a
detection failure can never accidentally become a restart.
TASK: lr-e85fec
diagnostics.js's actionable hint (panel + toast) previously only ever
rendered an icon+label hint, even when actionable.action names
something the client can actually do. Adds _buildActionableEl(),
shared by both render sites, backed by an explicit
ACTIONABLE_HANDLERS allowlist -- a diagnostic's actionable.action
string is matched against this allowlist before anything is sent
back over the socket, so the backend can offer an action but never
dictate an arbitrary WS command through the diagnostic payload.
Wires the one handler this task needs: restart_for_build_update ->
sendWs({type: 'restart_for_build_update'}).
Toast auto-dismiss (6s) is suppressed when an action button is
present -- an operator deciding whether to restart must not have the
offer disappear before they've read it.
TASK: lr-e85fec
Records the design decision (session-preserving hot-reload investigated and rejected as infeasible -- names the specific reasons: live child processes/PTYs/sockets in-heap, no Node require() unload primitive, lr-0287 Tier 2 checkpoint/restore not built) and the shipped shape (detector + attached actuator, gated on an explicit operator click, reusing existing spawnAndRestart/drain signal machinery). TASK: lr-e85fec
|
PEACHES — clean (0 findings) Constraint audit: NO automatic restart path detected. Detection (5-min poll + early 10s check) broadcasts a diagnostic with an actionable button. The button click — gated by admin-only check, identical to restart_server — is the sole entry point to restart. On click, waitForIdleThenAct polls getActiveLiveCount with a 60s timeout (explicit, tested, documented), then restarts. If the operator never clicks, nothing restarts. Admin gating: restart_for_build_update added to the same admin-only gate as restart_server/shutdown_server (lib/project-sessions.js:335-336, same role check at 1876-1878). Drain semantics: 60s wait on in-flight sessions, then forced restart at timeout. Documented and unit-tested; intent is clear — avoid dropping a session the operator did not know was live, but do not deadlock. Detection soundness: Direct SHA comparison (lib/build-update-check.js lines 146-154), not string-matching on error messages. Polls every 5 minutes plus once 10s after startup. Existing warning unchanged: lr-22e8 journald WARN + health.stale still fires independently; this adds an offered affordance on top of it, per architecture.md:72. Brand rules: All user-visible strings use correct terminology: "npm install -g @clagentic/console" and "Restart to apply update" button label follow the clagentic-console / Clagentic: Console convention. Behavior verification: Tests (build-update-check-lr-e85fec.test.js) confirm detection logic, drain timing, and idempotency. No test for button UI itself (integration-level), but core logic is sound. Reviewed against absolute operator directive: NO CREW AGENT RESTARTS THE OPERATOR SERVICES. This diff honors that constraint. Operator decision is preserved; no path bypasses it. |
|
BOBBIE security audit of PR #420 (clagentic/clagentic-console), head 90dd673, task lr-e85fec. Scope: a6e59f2..90dd673 (4 commits, 7 files, matches stated PR scope exactly).
Scanners: gitleaks clean on a6e59f2..90dd673. semgrep --config auto found 11 findings in lib/daemon.js and lib/project-sessions.js, all at lines outside this PR diff hunks (pre-existing code). osv-scanner: no package.json/package-lock.json changes in this diff; existing lockfile advisories are pre-existing and unrelated to this PR scope. No findings. |
|
Merged via clagentic-loadout v0.2.0
|
What changed
Gives the daemon a code path to detect that a newer build has been installed on disk than the one it loaded at startup, and offer the operator a restart through the product UI. Closes the gap lr-e85fec describes: a merged, tested, installed fix (lr-b75006) sat inert because nothing in the product told the operator a restart would apply it.
Design decision (lr-e85fec ranked options 1-3)
FORBIDDEN constraints honored: no post_merge_step, no timer, no automatic/side-effect restart anywhere in this diff. The only caller of spawnAndRestart() from this new path is the WS handler that fires in direct response to the operator clicking the button.
Why
lr-e85fec (P1, filed by holden): a merged, tested, installed fix for a broken permission Allow/Deny safety control (lr-b75006) sat inert. lr-22e8 (closed June) already added a detector with no actuator, and that alone was not enough three months later.
Files changed
Verification
What this does not claim
This does not itself demonstrate the lr-b75006 stale-daemon condition fixed live on PID 604966 -- that requires deploying this PR and observing the real daemon offer and accept the restart, out of scope pre-merge. Left open for NAOMI/operator verification post-merge, per this repo's own reports-success-while-nothing-happened discipline (retro tome #845).
Task: lr-e85fec