Skip to content

fix(miner-ui): resolve governor pause/resume chat intents to the registered actions - #8781

Merged
loopover-orb[bot] merged 1 commit into
JSONbored:mainfrom
tryeverything24:fix-governor-chat-actions-8670
Jul 26, 2026
Merged

fix(miner-ui): resolve governor pause/resume chat intents to the registered actions#8781
loopover-orb[bot] merged 1 commit into
JSONbored:mainfrom
tryeverything24:fix-governor-chat-actions-8670

Conversation

@tryeverything24

Copy link
Copy Markdown
Contributor

Closes #8670

What

apps/loopover-miner-ui's governor pause/resume chat-action family was fully built and unit-tested — runGovernorChatAction (dispatch wrapper), GovernorChatActionResult (result renderer), chat-governor-action-copy.ts (Ledgers-verbatim copy) — but dead in the product: conversation.tsx's handleSubmit only resolved portfolio-queue text, so an operator typing "pause the governor" into the chat rail was silently answered by the generic read-only streaming assistant. runGovernorChatAction had zero non-test callers and GovernorChatActionResult was never imported outside its own test, exactly as the issue's repo-wide grep found.

How

All four Deliverables, in this one PR:

  • New text resolversrc/lib/chat-governor-resolve.ts (resolveGovernorChatAction) mirrors chat-portfolio-queue-resolve.ts's pattern: pure (no fetch, no dispatch), word-bounded pause/halt/suspend vs resume/unpause intent detection that must co-occur with the word "governor", an optional pause reason from a trailing because … / reason: … clause (mapped to the { reason } params shape isGovernorPauseChatParams validates), and an explicit unresolvable result for empty/ambiguous/verb-less text so a governor question still falls through to the assistant and nothing is ever best-guess dispatched.
  • handleSubmit wiringconversation.tsx resolves governor intent right after the portfolio-queue check and, on a match, performs the same idempotent registerGovernorChatActions() registration the Vite dev-server entry does, then dispatches through runGovernorChatAction (flag-gated dispatchChatAction + governorGatedHandler, never a direct handler call). Unresolved text streams exactly as before.
  • GovernorChatActionResult rendered in the message stream — the in-flight round-trip renders the component's pending state (role="status", "Updating governor…") in the same viewport slot the live streaming render uses, and the resolved turn is committed to the message list with the result attached, where MessageBubble now renders it through GovernorChatActionResult (error → role="alert", success → the Ledgers-verbatim "Paused since …"/"Not paused" copy). Non-executed dispatches (flag off / gated) and thrown dispatches surface as inline system notes, mirroring the existing failure convention.
  • End-to-end RTL testchat-conversation.test.tsx gains a 7-test #8670 describe that renders ChatConversation, types governor-intent text into the real composer, and drives the REAL registration + dispatch pipeline (createChatActionRegistry() + injected pauseGovernor/resumeGovernor clients only): asserts the pause action actually fires, the because-clause reaches pauseGovernor("release traffic spiked") as the structured reason, resume fires, GovernorChatActionResult's copy appears in the message list, pending copy shows while outstanding, non-executed/thrown dispatches surface, and streamChat is never called for action turns (0 calls) while ordinary governor questions still stream. Resolver branches are pinned in chat-governor-resolve.test.ts.

Before / after (RED proof)

The new end-to-end tests on unfixed main (fix stashed, tests kept) fail exactly as the issue describes — governor-intent text falls through to the generic assistant and no action ever fires:

× END-TO-END: typing "pause the governor" fires the pause action and renders GovernorChatActionResult copy
  AssertionError: expected "vi.fn()" to be called 1 times, but got 0 times
× typing "resume the governor" fires the resume action and renders the not-paused copy
  AssertionError: expected "vi.fn()" to be called 1 times, but got 0 times
(6 of the 7 new tests fail on main; the falls-through-to-assistant test passes, pinning the unchanged path)

With the fix, the full app suite:

Test Files  38 passed (38)
     Tests  407 passed (407)

Local vitest coverage thresholds (85/85/75/85, enforced in CI for this app) pass at 91.81% statements / 89.64% branches / 87.55% functions / 94.36% lines; every new/changed file is at 100% statements (chat-governor-resolve.ts 100% across all four metrics). tsc --noEmit, eslint and prettier --check on the changed files, and git diff --check are all green.

UI Evidence

The miner-ui chat rail on the Overview route, served by vite dev in the app's own demo mode (VITE_DEMO_MODE=1, sample data, no live backend), captured per viewport × theme below. The read-only POST /api/chat stream is answered at the network boundary by a fixture (the generic grounded assistant reply); the governor path needs no fixture at all — in demo mode pauseGovernor performs the real demo-state write, so the After column is the actual resolver → registration → flag-gated dispatch → GovernorChatActionResult path executing in the browser. Look at the reply to "pause the governor": before, the generic assistant answers in prose and nothing happens (the bug); after, the pause action executes and the system turn renders GovernorChatActionResult's "Paused since …" copy. Capture note: current main cannot paint this SPA at all — @loopover/engine's dist/calibration/backtest-split.js (#8097) reaches the browser graph through the chat-action registry chain and its top-level node:crypto access makes Vite's browser-external shim throw before first render, on clean main exactly as on this branch — so the harness replaces that shim with an inert stub at the network boundary; none of those node-only modules are executed by the surface being captured, and the un-stubbed behavior is identical (a blank page) before and after this change.

Viewport · Theme Before After
Desktop · Light Desktop light before — pause the governor answered by the generic assistant, no action fires Desktop light after — pause dispatched, GovernorChatActionResult renders Paused since timestamp
Desktop · Dark Desktop dark before — pause the governor answered by the generic assistant, no action fires Desktop dark after — pause dispatched, GovernorChatActionResult renders Paused since timestamp
Tablet · Light Tablet light before — pause the governor answered by the generic assistant, no action fires Tablet light after — pause dispatched, GovernorChatActionResult renders Paused since timestamp
Tablet · Dark Tablet dark before — pause the governor answered by the generic assistant, no action fires Tablet dark after — pause dispatched, GovernorChatActionResult renders Paused since timestamp
Mobile · Light Mobile light before — chat sheet, pause the governor answered by the generic assistant Mobile light after — chat sheet, pause dispatched, Paused since copy rendered
Mobile · Dark Mobile dark before — chat sheet, pause the governor answered by the generic assistant Mobile dark after — chat sheet, pause dispatched, Paused since copy rendered

…onversation

The chat rail registered and unit-tested the whole governor pause/resume
action family but never dispatched it: conversation.tsx only resolved
portfolio-queue text, so governor-intent messages fell through to the
read-only streaming assistant and GovernorChatActionResult was never
rendered anywhere.

Add resolveGovernorChatAction (mirroring chat-portfolio-queue-resolve),
wire it into handleSubmit ahead of the generic stream so a matched intent
dispatches through runGovernorChatAction (registration + flag-gated
dispatch + gate), and render GovernorChatActionResult in the message
stream: pending copy below the list while the round-trip is in flight,
and the resolved Ledgers-verbatim result as the turn outcome.

Closes JSONbored#8670
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@loopover-orb loopover-orb Bot added the gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. label Jul 26, 2026
@loopover-orb

loopover-orb Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Tip

✅ LoopOver review result - approve/merge recommended

Review updated: 2026-07-26 05:08:36 UTC

6 files · 1 AI reviewer · no blockers · readiness 95/100 · CI green · unknown

✅ Suggested Action - Approve/Merge

  • safe to merge

Review summary
This PR wires the already-built governor pause/resume chat-action stack into `conversation.tsx`'s `handleSubmit`, closing #8670 by making typed governor intents ('pause the governor', 'resume the governor') actually dispatch through the existing `runGovernorChatAction`/registry/gate pipeline instead of silently falling through to the generic streaming assistant. The new `resolveGovernorChatAction` resolver mirrors the existing portfolio-queue resolver's pure, no-best-guess pattern (word-bounded verbs, disjoint pause/resume sets, explicit unresolvable fallback for questions/ambiguous text), and `GovernorChatActionResult` is now rendered both as the pending footer and in the committed message bubble. Tests are thorough and exercise the real registry/dispatch wire end-to-end, matching the existing Ledgers-verbatim copy conventions.

Nits — 5 non-blocking
  • conversation.tsx's catch block for a thrown `runGovernorChatActionImpl` (chat-conversation.test.tsx's 'surfaces a thrown dispatch' test) resets `streaming` in `finally` but never explicitly resets `governorActionPending`; worth confirming `runGovernorChatAction`'s own implementation always calls `onPending(false)` in a finally even on throw, since if it doesn't the pending footer could get stuck on a real network failure.
  • fixtures.ts:20 pulls in `GovernorPauseStateResult` from `../../lib/governor` purely for the new optional field — fine, but consider whether `content` duplicating the same copy as `governorActionResult` (both carry `formatGovernorPauseChatMessage` output) is worth a comment noting it's intentional for wire-history/plain-text parity rather than accidental duplication.
  • the cross-package relative import in chat-governor-resolve.ts (`../../../../packages/loopover-miner/lib/chat-governor-actions.js`) is fragile to file moves; consider re-exporting the action-name constants from the existing `@​/lib/chat-governor-actions` module already imported in conversation.tsx instead of a second deep relative path to the same underlying package.
  • Add an assertion in the 'surfaces a thrown dispatch' test that the pending governor footer/status text is no longer present after the failure, to lock in that the pending flag is actually cleared on the error path.
  • Consider factoring the GOVERNOR_PAUSE_CHAT_ACTION/GOVERNOR_RESUME_CHAT_ACTION re-export in chat-governor-resolve.ts through the already-imported `@​/lib/chat-governor-actions` in conversation.tsx to avoid two different import paths reaching the same constants.

Decision drivers

  • ✅ Code review — No blockers (1 reviewer)
  • ✅ Gate result — Passing (No configured blocker found.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ✅ Linked #8670
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ✅ 20/20 Low review scope from cached public metadata (1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 94 registered-repo PR(s), 44 merged, 4 issue(s).
Contributor context ✅ Confirmed Gittensor contributor tryeverything24; Gittensor profile; 94 PR(s), 4 issue(s).
Improvement ✅ Minor risk: clean · value: minor
Linked issue satisfaction

Addressed
The diff adds a new resolveGovernorChatAction text resolver mirroring the portfolio-queue pattern, wires it into conversation.tsx's handleSubmit to dispatch through runGovernorChatAction before falling through to streaming, renders GovernorChatActionResult in both message-bubble.tsx and the pending footer slot, and includes a substantial RTL end-to-end test that types 'pause the governor' and asse

Review context
  • Author: tryeverything24
  • Role context: outside_contributor
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: not available
  • Official Gittensor activity: 94 PR(s), 4 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Start here: Triage stale or unlinked PRs.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
🧪 Chat with LoopOver

Ask LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.

  • @loopover ask <question> answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat <question> answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @loopover mention with a real question is routed to the closest matching read-only command automatically — no exact syntax required.

Full command reference: https://loopover.ai/docs/loopover-commands

🧪 Experimental — new and may change.

Visual preview
Route Viewport Before (production) After (this PR's preview) Diff
/ desktop before /
before /
after /
after /
/ mobile before / (mobile)
before / (mobile)
after / (mobile)
after / (mobile)

Click any thumbnail to open the full-size screenshot. Before = production · After = this PR's preview deploy.

Scroll preview
Route Before (production) After (this PR's preview)
/ before / (scroll)
before / (scroll)
after / (scroll)
after / (scroll)

A short scroll-through clip (desktop) — click either thumbnail to open the full animation. Evidence for scroll-linked behavior a single screenshot can't show.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.

  • Re-run LoopOver review

@loopover-orb loopover-orb Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoopOver approves — the gate is satisfied and CI is green.

@loopover-orb
loopover-orb Bot merged commit 567505b into JSONbored:main Jul 26, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(miner-ui): governor pause/resume chat actions are registered and rendered but never dispatched from the chat conversation

1 participant