fix(routines): hot-enable on LLM key save — resolve chat agent live per run#483
Merged
Conversation
…er run Routines was the last feature needing a manual restart on first-run: the kernel wired it at boot behind `if (graphPool && orchestrator)`, capturing the orchestrator by reference, so a key saved via the Setup Wizard never reached it (the boot log literally said "then restart to enable routines"). Adopt chat's live-resolution pattern (issue #473): - initRoutines/RoutineRunner take `getOrchestrator` (live resolver over the ServiceRegistry) instead of a captured `orchestrator`; the boot gate is graphPool-only. The next cron fire after key save just works — no trigger code, no re-init; a key rotation swaps to the new bundle too. - Keyless fires record an `error` run naming the missing key, checked before the sender lookup — pre-key the channel senders are dark too and would otherwise mask the cause with 'no proactive sender'. The pause re-read now runs first so a raced fire on a paused row stays a skip. - POST /:id/trigger returns a synchronous 503 `routines.chat_unavailable` instead of a 202 whose background run is guaranteed to fail; the web-ui maps the code to a catalog message (EN/DE) naming the fix. 7 new tests: keyless error run + check ordering, hot-enable flip, key rotation across instances, trigger 503→202 flip (router-level analog of chatSessionsRouterGraceful). Fixes #473 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Fixes #473
Problem
Routines was the only feature that couldn't hot-enable when the LLM key is saved: the kernel wired it once at boot behind
if (graphPool && orchestrator), capturing the orchestrator by reference. On a fresh install (key entered post-boot via the Setup Wizard) routines stayed dark until a manual restart — the boot log literally said "then restart to enable routines". This was the last manual step in the first-run flow (follow-up from #471).Approach — chat's live-resolution pattern
initRoutines/RoutineRunnertake agetOrchestratorlive resolver (closure overserviceRegistry.get('chatAgent')?.raw, mirroring the chat routes'getChatAgentBundle) instead of a captured instance. The boot gate is now graphPool-only; stores, router,manage_routinetool, cron registration, and catch-up all initialize at boot regardless of key state.chatAgent@1, the next cron fire just works. A key rotation (reactivate publishes a new bundle) is picked up the same way; each run pins the instance current at fire time.errorrun naming the missing key — consistent with One-click deploy (Render + Fly.io) — and the four first-run fixes it surfaced #471's "fresh installs say what's missing". The chat-agent check runs before the sender lookup because pre-key the channel plugins (requires: chatAgent@^1) are dark too, and the old order would report a misleadingno proactive sender. The pause/delete re-read now runs first, so a raced fire on a just-paused row stays a silent skip.POST /:id/triggerreturns a synchronous 503routines.chat_unavailableinstead of a fire-and-forget 202 whose run is guaranteed to fail; the web-ui maps the code to a catalog message (EN/DE) instead of rendering the raw error.Tests
7 new (all green, full middleware suite 4018 pass / 0 fail; web-ui typecheck, vitest,
i18n:checkpass):errorrun with the structured message, asserting the chat check precedes the sender lookupchatSessionsRouterGraceful.test.ts)Notes
git diff main...feat/one-click-deployis empty for every touched file).UnknownChannelErrorrun and self-heals on the next fire.🤖 Generated with Claude Code