Context
apps/loopover-miner-ui/src/routes/ledgers.tsx's GovernorControlSection (lines 60-114) is the only existing UI for governor pause/resume today — a "Pause governor" / "Resume governor" button pair, wired up by LedgersPage (lines 202-266). LedgersPage calls pauseGovernorAction/resumeGovernorAction (defaulted to pauseGovernor/resumeGovernor from apps/loopover-miner-ui/src/lib/governor.ts) inside runGovernorAction (lines 237-243), which flips an actionPending boolean (line 215) around the call so the buttons disable during the round-trip, then stores the returned GovernorPauseStateResult back into pauseState for GovernorControlSection to render.
lib/governor.ts exports GOVERNOR_PAUSE_API_PATH = "/api/governor/pause" and GOVERNOR_RESUME_API_PATH = "/api/governor/resume" (lines 7-8), and pauseGovernor(reason?) / resumeGovernor() (lines 71-78), which POST to those two routes and always resolve to a typed GovernorPauseStateResult ({ ok: true; pauseState } | { ok: false; error }) — network failures are caught and turned into the ok: false shape rather than thrown. Those two HTTP routes are served by apps/loopover-miner-ui/vite-governor-api.ts, whose own header comment is explicit that it is "a thin bridge to the EXISTING governor-state.js exports (loadPauseState/savePauseState)" — the same functions the CLI's governor pause/governor resume subcommands already use via packages/loopover-miner/lib/governor-pause-cli.js — and that it "never touches governor-chokepoint.js/governor-chokepoint-persisted.js." Pause/resume is administrative control over the governor, not a chokepoint-gated content-producing write like open_pr/file_issue, so it is intentionally a simpler, separate code path from those — there is nothing to route through the chokepoint here, only the existing HTTP API to reuse.
The miner dashboard redesign is adding a persistent chat rail to apps/loopover-miner-ui (mounted once in __root.tsx, surviving route navigation) that is action-capable in v1 for three narrowly-scoped action families: discover/attempt, portfolio release/requeue, and governor pause/resume — this issue is the governor pause/resume family. The safety case for chat being action-capable at all rests entirely on chat calling the exact same endpoints the existing buttons already call, through one shared dispatch layer, never a second path — the miner already has full local write autonomy over its own machine, so a chat input in front of controls that already exist as buttons creates no new privilege, provided it is genuinely the same controls and not a reimplementation.
This issue depends on, but does not itself build, two sibling pieces of this same redesign: the chat action-dispatch scaffolding issue (the shared action registry/execution layer plus its config flag, off by default) and the chat message-list issue (the inline chat surface this issue's result renders into). If either has not landed when this is picked up, this issue is blocked — do not build a local or parallel dispatch/render mechanism to route around a missing dependency. Beyond those two prerequisites, this issue calls an already-shipped endpoint through an already-designed dispatch contract: there is no open design question left to resolve here, only wiring two actions into it.
Requirements
⚠️ Read this before starting. This issue must not add, wrap, or duplicate any governor write logic. The only two calls this issue is allowed to make are pauseGovernor() and resumeGovernor(), exported from apps/loopover-miner-ui/src/lib/governor.ts, which already POST to /api/governor/pause//api/governor/resume. A PR that calls packages/loopover-miner/lib/governor-state.js (savePauseState/loadPauseState) directly, that adds a new /api/governor/* route (in vite-governor-api.ts or a new plugin), or that reimplements the POST body/fetch logic instead of importing the existing functions, does NOT satisfy this issue and will be closed.
- Register two chat-dispatchable actions —
governor_pause (accepting an optional reason: string) and governor_resume (no arguments) — with the chat action-dispatch layer, using whatever registration contract the scaffolding issue defines.
- Each action's handler must call
pauseGovernor(reason) / resumeGovernor() from apps/loopover-miner-ui/src/lib/governor.ts and return the same GovernorPauseStateResult union those functions already produce — do not narrow, rename, or re-shape that result before handing it to the dispatch layer's result plumbing.
- Both actions must be gated behind the shared action-dispatch config flag introduced by the scaffolding issue — this issue must not read, define, or check a second, governor-specific flag.
- Add the inline chat rendering for the result, reusing the exact copy already established in
GovernorControlSection (ledgers.tsx:85-89): "Paused since <pausedAt> (<reason>)" when paused with a reason, "Paused since <pausedAt>" when paused without one, "Not paused" otherwise, and the existing error text "Could not read the local governor state: <error>" (ledgers.tsx:79-82) for the ok: false case — do not invent new wording for states this codebase already has copy for.
- A pending/in-flight state must be surfaced in chat while the request is outstanding, mirroring
LedgersPage's actionPending/runGovernorAction pattern (ledgers.tsx:215,237-243), so the chat surface never looks unresponsive during the POST round-trip.
- The existing button-driven
GovernorControlSection on the Ledgers route must be left functionally unchanged — this issue adds a second entry point to the same two functions, not a replacement for the first.
Deliverables
Test Coverage Requirements
apps/** is excluded from Codecov entirely (codecov.yml's coverage.ignore includes "apps/**"), so codecov/patch will not numerically gate this PR. That does not relax the bar: npm run test:coverage's local vitest backstop still runs over this code, and every new branch needs an explicit test — ok: true vs ok: false for both governor_pause and governor_resume, the pending-state render, and the paused-with-reason vs paused-without-reason vs not-paused render — mirroring the pattern already in apps/loopover-miner-ui/src/governor.test.tsx (e.g. its existing "renders an error message when the local API is unreachable" and paused/not-paused cases for GovernorControlSection). Add a regression test asserting the two action handlers call pauseGovernor/resumeGovernor (not governor-state.js directly, and not a hand-rolled fetch) so a future refactor can't silently reintroduce a parallel write path.
Expected Outcome
A miner operator can trigger a pause or resume from the chat rail and have it actually pause/resume the governor — through the identical /api/governor/pause//api/governor/resume calls the existing Ledgers-page buttons already make — with the result (paused state, reason, timestamp, or an error) shown inline in chat while it's pending and once it resolves. No new governor write path exists anywhere in the codebase; the action stays inert until the shared action-dispatch config flag is explicitly enabled per-install.
Links & Resources
apps/loopover-miner-ui/src/lib/governor.ts — pauseGovernor/resumeGovernor and the API path constants this issue must call, unchanged.
apps/loopover-miner-ui/vite-governor-api.ts — the server-side bridge those functions call into; not to be modified by this issue.
apps/loopover-miner-ui/src/routes/ledgers.tsx — GovernorControlSection (lines 60-114) and LedgersPage (lines 202-266) for the existing button-driven precedent: copy, pending-state handling, and result shape.
apps/loopover-miner-ui/src/governor.test.tsx — the existing test file to mirror for coverage rigor.
packages/loopover-miner/lib/governor-state.js / governor-pause-cli.js — the underlying store and CLI equivalent (governor pause/governor resume); referenced for context only, not to be called directly from this issue's code.
- The chat action-dispatch scaffolding issue (shared registry + config flag) — hard prerequisite; this issue registers into it rather than building its own dispatch mechanism.
- The chat message-list issue — renders this issue's inline result; confirm its message shape before finalizing this issue's render output.
- Sibling action-family issues in the same redesign: the portfolio release/requeue chat-action issue and the discover/attempt chat-action issue — same dispatch layer, same "call the existing endpoint, never a new one" constraint, different underlying calls.
Context
apps/loopover-miner-ui/src/routes/ledgers.tsx'sGovernorControlSection(lines 60-114) is the only existing UI for governor pause/resume today — a "Pause governor" / "Resume governor" button pair, wired up byLedgersPage(lines 202-266).LedgersPagecallspauseGovernorAction/resumeGovernorAction(defaulted topauseGovernor/resumeGovernorfromapps/loopover-miner-ui/src/lib/governor.ts) insiderunGovernorAction(lines 237-243), which flips anactionPendingboolean (line 215) around the call so the buttons disable during the round-trip, then stores the returnedGovernorPauseStateResultback intopauseStateforGovernorControlSectionto render.lib/governor.tsexportsGOVERNOR_PAUSE_API_PATH = "/api/governor/pause"andGOVERNOR_RESUME_API_PATH = "/api/governor/resume"(lines 7-8), andpauseGovernor(reason?)/resumeGovernor()(lines 71-78), which POST to those two routes and always resolve to a typedGovernorPauseStateResult({ ok: true; pauseState }|{ ok: false; error }) — network failures are caught and turned into theok: falseshape rather than thrown. Those two HTTP routes are served byapps/loopover-miner-ui/vite-governor-api.ts, whose own header comment is explicit that it is "a thin bridge to the EXISTINGgovernor-state.jsexports (loadPauseState/savePauseState)" — the same functions the CLI'sgovernor pause/governor resumesubcommands already use viapackages/loopover-miner/lib/governor-pause-cli.js— and that it "never touchesgovernor-chokepoint.js/governor-chokepoint-persisted.js." Pause/resume is administrative control over the governor, not a chokepoint-gated content-producing write likeopen_pr/file_issue, so it is intentionally a simpler, separate code path from those — there is nothing to route through the chokepoint here, only the existing HTTP API to reuse.The miner dashboard redesign is adding a persistent chat rail to
apps/loopover-miner-ui(mounted once in__root.tsx, surviving route navigation) that is action-capable in v1 for three narrowly-scoped action families:discover/attempt, portfolio release/requeue, and governor pause/resume — this issue is the governor pause/resume family. The safety case for chat being action-capable at all rests entirely on chat calling the exact same endpoints the existing buttons already call, through one shared dispatch layer, never a second path — the miner already has full local write autonomy over its own machine, so a chat input in front of controls that already exist as buttons creates no new privilege, provided it is genuinely the same controls and not a reimplementation.This issue depends on, but does not itself build, two sibling pieces of this same redesign: the chat action-dispatch scaffolding issue (the shared action registry/execution layer plus its config flag, off by default) and the chat message-list issue (the inline chat surface this issue's result renders into). If either has not landed when this is picked up, this issue is blocked — do not build a local or parallel dispatch/render mechanism to route around a missing dependency. Beyond those two prerequisites, this issue calls an already-shipped endpoint through an already-designed dispatch contract: there is no open design question left to resolve here, only wiring two actions into it.
Requirements
governor_pause(accepting an optionalreason: string) andgovernor_resume(no arguments) — with the chat action-dispatch layer, using whatever registration contract the scaffolding issue defines.pauseGovernor(reason)/resumeGovernor()fromapps/loopover-miner-ui/src/lib/governor.tsand return the sameGovernorPauseStateResultunion those functions already produce — do not narrow, rename, or re-shape that result before handing it to the dispatch layer's result plumbing.GovernorControlSection(ledgers.tsx:85-89):"Paused since <pausedAt> (<reason>)"when paused with a reason,"Paused since <pausedAt>"when paused without one,"Not paused"otherwise, and the existing error text"Could not read the local governor state: <error>"(ledgers.tsx:79-82) for theok: falsecase — do not invent new wording for states this codebase already has copy for.LedgersPage'sactionPending/runGovernorActionpattern (ledgers.tsx:215,237-243), so the chat surface never looks unresponsive during the POST round-trip.GovernorControlSectionon the Ledgers route must be left functionally unchanged — this issue adds a second entry point to the same two functions, not a replacement for the first.Deliverables
governor_pause/governor_resumeaction registrations, wired topauseGovernor/resumeGovernorfromapps/loopover-miner-ui/src/lib/governor.ts, gated behind the shared action-dispatch flag.GovernorControlSection's existing copy verbatim.ok: true/ok: falsebranches and the pending state, at the same rigor as the existingapps/loopover-miner-ui/src/governor.test.tsx.Test Coverage Requirements
apps/**is excluded from Codecov entirely (codecov.yml'scoverage.ignoreincludes"apps/**"), socodecov/patchwill not numerically gate this PR. That does not relax the bar:npm run test:coverage's local vitest backstop still runs over this code, and every new branch needs an explicit test —ok: truevsok: falsefor bothgovernor_pauseandgovernor_resume, the pending-state render, and the paused-with-reason vs paused-without-reason vs not-paused render — mirroring the pattern already inapps/loopover-miner-ui/src/governor.test.tsx(e.g. its existing "renders an error message when the local API is unreachable" and paused/not-paused cases forGovernorControlSection). Add a regression test asserting the two action handlers callpauseGovernor/resumeGovernor(notgovernor-state.jsdirectly, and not a hand-rolledfetch) so a future refactor can't silently reintroduce a parallel write path.Expected Outcome
A miner operator can trigger a pause or resume from the chat rail and have it actually pause/resume the governor — through the identical
/api/governor/pause//api/governor/resumecalls the existing Ledgers-page buttons already make — with the result (paused state, reason, timestamp, or an error) shown inline in chat while it's pending and once it resolves. No new governor write path exists anywhere in the codebase; the action stays inert until the shared action-dispatch config flag is explicitly enabled per-install.Links & Resources
apps/loopover-miner-ui/src/lib/governor.ts—pauseGovernor/resumeGovernorand the API path constants this issue must call, unchanged.apps/loopover-miner-ui/vite-governor-api.ts— the server-side bridge those functions call into; not to be modified by this issue.apps/loopover-miner-ui/src/routes/ledgers.tsx—GovernorControlSection(lines 60-114) andLedgersPage(lines 202-266) for the existing button-driven precedent: copy, pending-state handling, and result shape.apps/loopover-miner-ui/src/governor.test.tsx— the existing test file to mirror for coverage rigor.packages/loopover-miner/lib/governor-state.js/governor-pause-cli.js— the underlying store and CLI equivalent (governor pause/governor resume); referenced for context only, not to be called directly from this issue's code.