You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The miner dashboard redesign adds a persistent chat rail (mounted once in __root.tsx, per the chat-rail shell issue) that answers natural-language questions about the miner's own local state. This issue is the backend half only: a new local streaming API endpoint that grounds answers by tool-calling against the miner's existing read-only MCP tools. No UI component, no action-dispatch, no new tool.
The exact 11 tools this endpoint may call — all registered read-only in packages/loopover-miner/bin/loopover-miner-mcp.js (verified by tool name in that file, one server.registerTool(...) call each):
loopover_miner_ping
loopover_miner_get_portfolio_dashboard
loopover_miner_get_manage_status
loopover_miner_list_claims
loopover_miner_get_audit_feed
loopover_miner_get_run_state
loopover_miner_list_plans
loopover_miner_get_plan
loopover_miner_get_governor_decisions
loopover_miner_status
loopover_miner_get_calibration_report
Every one of these tools' own docstring in that file says "Read-only... never mutates" — this was a closed design decision (v1's grounding tool surface is exactly these 11, no new tool surface), not something this issue re-opens.
The existing local API middleware convention this endpoint must follow: apps/loopover-miner-ui/vite-run-state-api.ts (and its siblings vite-portfolio-queue-api.ts, vite-portfolio-queue-actions-api.ts, vite-ledgers-api.ts, vite-governor-api.ts, vite-ranked-candidates-api.ts) are each a small Vite Plugin exposing one /api/* route, registered in apps/loopover-miner-ui/vite.config.ts's plugins array. All of them sit behind apps/loopover-miner-ui/vite-auth.ts, a per-process random-token HttpOnly/SameSite=Strict cookie gate registered FIRST in that array specifically so — per that file's own header comment — "any FUTURE /api/* endpoint... is covered automatically, with no per-endpoint auth wiring required."
Credential/provider resolution to reuse, not reinvent:packages/loopover-engine/src/miner/driver-factory.ts already resolves which local coding-agent provider is configured, from the MINER_CODING_AGENT_PROVIDER env var (comma-separated fallback list) via resolveFirstConfiguredCodingAgentDriverName/isConfiguredCodingAgentDriver/resolveConfiguredCodingAgentDriverNames, with per-provider model/timeout env keys in CODING_AGENT_DRIVER_CONFIG_ENV (MINER_CODING_AGENT_CLAUDE_MODEL, MINER_CODING_AGENT_CODEX_MODEL, MINER_CODING_AGENT_TIMEOUT_MS). All local, no API-key requirement, fail-closed on unknown/unconfigured names. The sibling module packages/loopover-engine/src/miner/agent-sdk-driver.ts already shows the concrete pattern for driving @anthropic-ai/claude-agent-sdk's query() async-iterable in-process (its defaultQuery, dynamically importing @anthropic-ai/claude-agent-sdk — already a packages/loopover-engine dependency per its package.json), including the injected-AgentSdkQueryFn testability seam this issue should mirror so tests never make a real model call.
The existing privacy-boundary precedent to reuse, not reinvent:packages/loopover-engine/src/track-record-summary.ts defines PUBLIC_FIELD_BLOCKLIST, a regex list (trust score, trustscore, reward, payout, ranking, wallet, hotkey, coldkey, etc.) used to keep sensitive fields out of a public-facing summary. loopover_miner_get_governor_decisions's own docstring similarly states it "INTENTIONALLY EXCLUDES the internal/sensitive payload column (reputation / self-plagiarism / budget state) by construction." None of the 11 tools above emit wallet/hotkey/coldkey/reward/trust-score data today (verified by grep across loopover-miner-mcp.js) — but a conversational endpoint adds a NEW leak surface the tools themselves don't have: a user can simply ask "what's my trust score" in plain language, and an ungrounded model could hallucinate an answer instead of correctly having nothing to say.
⚠️ Read this before starting
The deliverable is a streaming HTTP endpoint. A PR that returns one buffered JSON response (even if correct) does not satisfy this issue — see the exact wire format required below.
The deliverable calls only the 11 loopover_miner_* tools named above. A PR that adds a 12th tool, edits packages/loopover-miner/bin/loopover-miner-mcp.js's registered tool set, or invokes any write-capable loopover_* tool (e.g. loopover_open_pr, loopover_file_issue, anything from src/mcp/local-write-tools.ts / packages/loopover-engine/src/miner/local-write-tools.ts) does NOT resolve this issue and will be closed.
This issue is backend only. No chat UI, composer, message-list, or streaming-renderer component, and no change to apps/loopover-miner-ui/src/routes/__root.tsx, belongs in this PR — those are separate sibling issues that will call the endpoint this issue builds.
No action-dispatch of any kind (discover/attempt, portfolio release/requeue, governor pause/resume) belongs in this PR, even though those are decided for a later, separately-scoped action-dispatch chat issue. This issue's endpoint must never call /api/portfolio-queue/*, /api/governor/*, or any new discover/attempt-mirroring route.
Do not add a new SQLite-backed conversation-history store (mirroring packages/loopover-miner/lib/local-store.js's pattern). The endpoint is stateless: the caller supplies full message history in each request body.
Requirements
New endpoint: POST /api/chat, implemented as a new Vite middleware plugin apps/loopover-miner-ui/vite-chat-api.ts (mirroring the handleXRequest-factored-out-for-testing shape already used by vite-run-state-api.ts), registered in apps/loopover-miner-ui/vite.config.ts's plugins array afterauthPlugin() — the same position every existing /api/* plugin already uses, so the auth-cookie gate covers it with no new auth mechanism.
Request body: { "messages": [{ "role": "user" | "assistant", "content": string }, ...] }; the endpoint must reject (4xx, non-streamed JSON error body) a request whose last message is not role: "user" or whose messages array is empty/malformed.
Response transport: Content-Type: text/event-stream, chunked, consumed via fetch() + ReadableStream on the client (not the native EventSource API, since EventSource cannot send a POST body) — each event is one data: <json>\n\n line. Valid event types: "text" ({ "type": "text", "text": string }, a partial or full answer chunk), "tool_call" ({ "type": "tool_call", "tool": string, "input": object }), "tool_result" ({ "type": "tool_result", "tool": string, "output": unknown }), "error" ({ "type": "error", "code": string, "message": string }), and a final "done" ({ "type": "done" }) that always terminates the stream, including on error.
New engine-side module packages/loopover-engine/src/miner/chat-grounding.ts, exported from packages/loopover-engine/src/index.ts alongside the existing driver-factory.js/agent-sdk-driver.js exports (matching that barrel file's existing convention). It resolves the configured provider using driver-factory.ts's exported functions (resolveFirstConfiguredCodingAgentDriverName, isConfiguredCodingAgentDriver, CODING_AGENT_DRIVER_CONFIG_ENV) — do not read MINER_CODING_AGENT_* env vars directly or duplicate that parsing logic.
Chat requires the agent-sdk provider specifically. driver-factory.ts's claude-cli/codex-cli drivers are a task-shaped, single-turn, buffered CodingAgentDriverResult interface built for one-shot coding attempts (working directory, acceptance-criteria file, maxTurns) — not a fit for a conversational streaming tool-calling loop, and building CLI-subprocess streaming parsing is out of scope here. When the resolved provider name is claude-cli or codex-cli (or nothing is configured), the endpoint must emit a single {"type":"error","code":"chat_requires_agent_sdk_provider"|"no_coding_agent_configured", ...} event followed by {"type":"done"}, never a partial/mock/echoed answer.
When agent-sdk is configured, chat-grounding.ts drives @anthropic-ai/claude-agent-sdk's query() directly (the same SDK import agent-sdk-driver.ts's defaultQuery already uses), with the session's tool access restricted to connecting against packages/loopover-miner/bin/loopover-miner-mcp.js's MCP server (e.g. as a stdio-connected MCP server in the session's tool config) so the 11 tools' existing implementations are called directly — the 11 tools' logic must not be reimplemented or duplicated inline in this new module.
chat-grounding.ts must expose an injectable query-function seam (mirroring agent-sdk-driver.ts's AgentSdkQueryFn injection convention) so every test drives a fake async-iterable instead of making a real model call.
The model's system prompt (a constant string in chat-grounding.ts) must explicitly instruct the model to decline questions about wallet, hotkey, coldkey, reward, payout, or trust-score data, stating plainly that none of the available tools expose it — the exact term set from track-record-summary.ts's PUBLIC_FIELD_BLOCKLIST.
As a defense-in-depth backstop (the system prompt alone is not enforcement), every outgoing "text" event's text field must be checked against a blocklist of the same terms before being written to the stream; a match must redact/replace the offending chunk rather than forward it verbatim, even though no known code path today produces one.
apps/loopover-miner-ui/vite-chat-api.ts must import chat-grounding.ts's exports via packages/loopover-engine's built dist/index.js output (i.e. the same relative-import-into-the-sibling-package style vite-run-state-api.ts already uses for packages/loopover-miner/lib/run-state.js), not by importing TypeScript source under packages/loopover-engine/src/** directly.
Deliverables
packages/loopover-engine/src/miner/chat-grounding.ts — provider resolution (via driver-factory.ts), the agent-sdkquery() session wired to the 11-tool MCP server, the system-prompt blocklist instruction, and the output-side redaction backstop.
Export of chat-grounding.ts's public surface from packages/loopover-engine/src/index.ts.
apps/loopover-miner-ui/vite-chat-api.ts — the POST /api/chat Vite middleware plugin, streaming text/event-stream events per the format above.
Registration of the new plugin in apps/loopover-miner-ui/vite.config.ts's plugins array, positioned after authPlugin().
A root vitest mirror test, test/unit/chat-grounding-engine.test.ts, following the existing test/unit/engine-*.test.ts naming convention (e.g. engine-subprocess-env.test.ts, engine-telemetry-anonymize.test.ts) — required because packages/loopover-engine's own node:test suite is not visible to Codecov; only the vitest-collected paths are.
apps/loopover-miner-ui/src/chat-api.test.ts, mirroring the existing apps/loopover-miner-ui/src/run-state-api.test.ts / apps/loopover-miner-ui/src/auth.test.ts pattern.
Test Coverage Requirements
packages/loopover-engine/src/miner/chat-grounding.ts and its test/unit/chat-grounding-engine.test.ts vitest mirror are inside Codecov's measured paths (codecov.yml's header comment: coverage is collected over src/**, packages/loopover-engine/src/**, and packages/loopover-miner/lib/**) — target 99%+ patch coverage, branch-counted. Cover both sides of every branch, at minimum: provider-configured (agent-sdk) vs. unconfigured/wrong-provider (claude-cli/codex-cli/none) fail-closed paths; tool-call-requested vs. plain-text-no-tool-call paths; and both the blocklist-triggered-redaction and clean-response paths of the output-side term filter (a SUM()/regex-match-or-not is exactly the kind of both-arms case this repo's coverage bar requires).
Add an invariant test asserting the tool-name allowlist passed to the agent-sdk session is exactly the 11 names listed in Context above — no more, no fewer — so a future accidental addition of a 12th tool (or a write-capable one) fails the test, not just code review.
apps/loopover-miner-ui/vite-chat-api.ts and its vite.config.ts registration are outside Codecov's coverage.include: codecov.yml explicitly ignores apps/**. This file is not gated by the 99% patch bar, but real vitest tests are still required so the app's own npm run test:ci and its local 90% vitest backstop (per codecov.yml's own comment) pass — cover: unauthenticated request rejected before reaching the handler (mirroring auth.test.ts's existing pattern), malformed/empty messages body rejected, and a fake upstream stream correctly re-emitted as SSE data: lines terminated by "done".
Local gate: npm run test:ci plus unsharded npm run test:coverage must be green before this is pushed, per the repo's standing one-shot-PR discipline.
Expected Outcome
A locally-running miner (npm run dev in apps/loopover-miner-ui) exposes an authenticated, streaming POST /api/chat endpoint that answers natural-language questions grounded exclusively in the 11 existing loopover_miner_* read-only tools, using the miner's own already-configured local agent-sdk credentials (no new credential handling), and never emits wallet/hotkey/coldkey/reward/payout/trust-score content even when a user asks for it directly. No write action of any kind is reachable through this endpoint. The chat-rail composer/message-list issues can point at this endpoint with no further backend work.
Links & Resources
packages/loopover-miner/bin/loopover-miner-mcp.js — the 11 read-only tools this endpoint may call.
packages/loopover-engine/src/miner/driver-factory.ts — provider/credential resolution to reuse.
packages/loopover-engine/src/miner/agent-sdk-driver.ts — existing @anthropic-ai/claude-agent-sdkquery() precedent and its injected-AgentSdkQueryFn test seam.
packages/loopover-engine/src/miner/local-write-tools.ts — the documented no-cloud-write boundary (LOCAL_WRITE_BOUNDARY), context for why this stays read-only.
packages/loopover-engine/src/track-record-summary.ts — PUBLIC_FIELD_BLOCKLIST, the term set to reuse for the privacy backstop.
apps/loopover-miner-ui/vite-auth.ts — the existing auth-cookie gate that already covers this new endpoint automatically.
apps/loopover-miner-ui/vite-run-state-api.ts (+ siblings vite-portfolio-queue-api.ts, vite-ledgers-api.ts, vite-governor-api.ts) and apps/loopover-miner-ui/vite.config.ts — the local API middleware convention to follow.
apps/loopover-miner-ui/src/run-state-api.test.ts, apps/loopover-miner-ui/src/auth.test.ts — apps-side test convention.
test/unit/engine-subprocess-env.test.ts, test/unit/engine-telemetry-anonymize.test.ts — the root vitest-mirror convention Codecov actually measures for packages/loopover-engine.
codecov.yml — confirms apps/** is excluded from patch coverage; packages/loopover-engine/src/** and packages/loopover-miner/lib/** are included.
Related, not blocking: the chat-rail shell and route-mount issue (mounts the persistent rail in __root.tsx); the composer/message-list/streaming-renderer/typing-indicator UI issues (the consumers of this endpoint); the action-dispatch chat issue (the separately-scoped, later write-capable surface this issue explicitly does not build).
Context
The miner dashboard redesign adds a persistent chat rail (mounted once in
__root.tsx, per the chat-rail shell issue) that answers natural-language questions about the miner's own local state. This issue is the backend half only: a new local streaming API endpoint that grounds answers by tool-calling against the miner's existing read-only MCP tools. No UI component, no action-dispatch, no new tool.The exact 11 tools this endpoint may call — all registered read-only in
packages/loopover-miner/bin/loopover-miner-mcp.js(verified by tool name in that file, oneserver.registerTool(...)call each):loopover_miner_pingloopover_miner_get_portfolio_dashboardloopover_miner_get_manage_statusloopover_miner_list_claimsloopover_miner_get_audit_feedloopover_miner_get_run_stateloopover_miner_list_plansloopover_miner_get_planloopover_miner_get_governor_decisionsloopover_miner_statusloopover_miner_get_calibration_reportEvery one of these tools' own docstring in that file says "Read-only... never mutates" — this was a closed design decision (v1's grounding tool surface is exactly these 11, no new tool surface), not something this issue re-opens.
The existing local API middleware convention this endpoint must follow:
apps/loopover-miner-ui/vite-run-state-api.ts(and its siblingsvite-portfolio-queue-api.ts,vite-portfolio-queue-actions-api.ts,vite-ledgers-api.ts,vite-governor-api.ts,vite-ranked-candidates-api.ts) are each a small VitePluginexposing one/api/*route, registered inapps/loopover-miner-ui/vite.config.ts'spluginsarray. All of them sit behindapps/loopover-miner-ui/vite-auth.ts, a per-process random-tokenHttpOnly/SameSite=Strictcookie gate registered FIRST in that array specifically so — per that file's own header comment — "any FUTURE/api/*endpoint... is covered automatically, with no per-endpoint auth wiring required."Credential/provider resolution to reuse, not reinvent:
packages/loopover-engine/src/miner/driver-factory.tsalready resolves which local coding-agent provider is configured, from theMINER_CODING_AGENT_PROVIDERenv var (comma-separated fallback list) viaresolveFirstConfiguredCodingAgentDriverName/isConfiguredCodingAgentDriver/resolveConfiguredCodingAgentDriverNames, with per-provider model/timeout env keys inCODING_AGENT_DRIVER_CONFIG_ENV(MINER_CODING_AGENT_CLAUDE_MODEL,MINER_CODING_AGENT_CODEX_MODEL,MINER_CODING_AGENT_TIMEOUT_MS). All local, no API-key requirement, fail-closed on unknown/unconfigured names. The sibling modulepackages/loopover-engine/src/miner/agent-sdk-driver.tsalready shows the concrete pattern for driving@anthropic-ai/claude-agent-sdk'squery()async-iterable in-process (itsdefaultQuery, dynamically importing@anthropic-ai/claude-agent-sdk— already apackages/loopover-enginedependency per itspackage.json), including the injected-AgentSdkQueryFntestability seam this issue should mirror so tests never make a real model call.The existing privacy-boundary precedent to reuse, not reinvent:
packages/loopover-engine/src/track-record-summary.tsdefinesPUBLIC_FIELD_BLOCKLIST, a regex list (trust score,trustscore,reward,payout,ranking,wallet,hotkey,coldkey, etc.) used to keep sensitive fields out of a public-facing summary.loopover_miner_get_governor_decisions's own docstring similarly states it "INTENTIONALLY EXCLUDES the internal/sensitive payload column (reputation / self-plagiarism / budget state) by construction." None of the 11 tools above emit wallet/hotkey/coldkey/reward/trust-score data today (verified by grep acrossloopover-miner-mcp.js) — but a conversational endpoint adds a NEW leak surface the tools themselves don't have: a user can simply ask "what's my trust score" in plain language, and an ungrounded model could hallucinate an answer instead of correctly having nothing to say.loopover_miner_*tools named above. A PR that adds a 12th tool, editspackages/loopover-miner/bin/loopover-miner-mcp.js's registered tool set, or invokes any write-capableloopover_*tool (e.g.loopover_open_pr,loopover_file_issue, anything fromsrc/mcp/local-write-tools.ts/packages/loopover-engine/src/miner/local-write-tools.ts) does NOT resolve this issue and will be closed.apps/loopover-miner-ui/src/routes/__root.tsx, belongs in this PR — those are separate sibling issues that will call the endpoint this issue builds.discover/attempt, portfolio release/requeue, governor pause/resume) belongs in this PR, even though those are decided for a later, separately-scoped action-dispatch chat issue. This issue's endpoint must never call/api/portfolio-queue/*,/api/governor/*, or any new discover/attempt-mirroring route.packages/loopover-miner/lib/local-store.js's pattern). The endpoint is stateless: the caller supplies full message history in each request body.Requirements
POST /api/chat, implemented as a new Vite middleware pluginapps/loopover-miner-ui/vite-chat-api.ts(mirroring thehandleXRequest-factored-out-for-testing shape already used byvite-run-state-api.ts), registered inapps/loopover-miner-ui/vite.config.ts'spluginsarray afterauthPlugin()— the same position every existing/api/*plugin already uses, so the auth-cookie gate covers it with no new auth mechanism.{ "messages": [{ "role": "user" | "assistant", "content": string }, ...] }; the endpoint must reject (4xx, non-streamed JSON error body) a request whose last message is notrole: "user"or whosemessagesarray is empty/malformed.Content-Type: text/event-stream, chunked, consumed viafetch()+ReadableStreamon the client (not the nativeEventSourceAPI, sinceEventSourcecannot send a POST body) — each event is onedata: <json>\n\nline. Valid eventtypes:"text"({ "type": "text", "text": string }, a partial or full answer chunk),"tool_call"({ "type": "tool_call", "tool": string, "input": object }),"tool_result"({ "type": "tool_result", "tool": string, "output": unknown }),"error"({ "type": "error", "code": string, "message": string }), and a final"done"({ "type": "done" }) that always terminates the stream, including on error.packages/loopover-engine/src/miner/chat-grounding.ts, exported frompackages/loopover-engine/src/index.tsalongside the existingdriver-factory.js/agent-sdk-driver.jsexports (matching that barrel file's existing convention). It resolves the configured provider usingdriver-factory.ts's exported functions (resolveFirstConfiguredCodingAgentDriverName,isConfiguredCodingAgentDriver,CODING_AGENT_DRIVER_CONFIG_ENV) — do not readMINER_CODING_AGENT_*env vars directly or duplicate that parsing logic.agent-sdkprovider specifically.driver-factory.ts'sclaude-cli/codex-clidrivers are a task-shaped, single-turn, bufferedCodingAgentDriverResultinterface built for one-shot coding attempts (working directory, acceptance-criteria file,maxTurns) — not a fit for a conversational streaming tool-calling loop, and building CLI-subprocess streaming parsing is out of scope here. When the resolved provider name isclaude-cliorcodex-cli(or nothing is configured), the endpoint must emit a single{"type":"error","code":"chat_requires_agent_sdk_provider"|"no_coding_agent_configured", ...}event followed by{"type":"done"}, never a partial/mock/echoed answer.agent-sdkis configured,chat-grounding.tsdrives@anthropic-ai/claude-agent-sdk'squery()directly (the same SDK importagent-sdk-driver.ts'sdefaultQueryalready uses), with the session's tool access restricted to connecting againstpackages/loopover-miner/bin/loopover-miner-mcp.js's MCP server (e.g. as a stdio-connected MCP server in the session's tool config) so the 11 tools' existing implementations are called directly — the 11 tools' logic must not be reimplemented or duplicated inline in this new module.chat-grounding.tsmust expose an injectable query-function seam (mirroringagent-sdk-driver.ts'sAgentSdkQueryFninjection convention) so every test drives a fake async-iterable instead of making a real model call.chat-grounding.ts) must explicitly instruct the model to decline questions about wallet, hotkey, coldkey, reward, payout, or trust-score data, stating plainly that none of the available tools expose it — the exact term set fromtrack-record-summary.ts'sPUBLIC_FIELD_BLOCKLIST."text"event'stextfield must be checked against a blocklist of the same terms before being written to the stream; a match must redact/replace the offending chunk rather than forward it verbatim, even though no known code path today produces one.apps/loopover-miner-ui/vite-chat-api.tsmust importchat-grounding.ts's exports viapackages/loopover-engine's builtdist/index.jsoutput (i.e. the same relative-import-into-the-sibling-package stylevite-run-state-api.tsalready uses forpackages/loopover-miner/lib/run-state.js), not by importing TypeScript source underpackages/loopover-engine/src/**directly.Deliverables
packages/loopover-engine/src/miner/chat-grounding.ts— provider resolution (viadriver-factory.ts), theagent-sdkquery()session wired to the 11-tool MCP server, the system-prompt blocklist instruction, and the output-side redaction backstop.chat-grounding.ts's public surface frompackages/loopover-engine/src/index.ts.apps/loopover-miner-ui/vite-chat-api.ts— thePOST /api/chatVite middleware plugin, streamingtext/event-streamevents per the format above.apps/loopover-miner-ui/vite.config.ts'spluginsarray, positioned afterauthPlugin().packages/loopover-engine/test/chat-grounding.test.ts(node:test, mirroringdriver-factory.test.ts/agent-sdk-driver.test.ts's injected-fake-driver convention).test/unit/chat-grounding-engine.test.ts, following the existingtest/unit/engine-*.test.tsnaming convention (e.g.engine-subprocess-env.test.ts,engine-telemetry-anonymize.test.ts) — required becausepackages/loopover-engine's ownnode:testsuite is not visible to Codecov; only the vitest-collected paths are.apps/loopover-miner-ui/src/chat-api.test.ts, mirroring the existingapps/loopover-miner-ui/src/run-state-api.test.ts/apps/loopover-miner-ui/src/auth.test.tspattern.Test Coverage Requirements
packages/loopover-engine/src/miner/chat-grounding.tsand itstest/unit/chat-grounding-engine.test.tsvitest mirror are inside Codecov's measured paths (codecov.yml's header comment: coverage is collected oversrc/**,packages/loopover-engine/src/**, andpackages/loopover-miner/lib/**) — target 99%+ patch coverage, branch-counted. Cover both sides of every branch, at minimum: provider-configured (agent-sdk) vs. unconfigured/wrong-provider (claude-cli/codex-cli/none) fail-closed paths; tool-call-requested vs. plain-text-no-tool-call paths; and both the blocklist-triggered-redaction and clean-response paths of the output-side term filter (aSUM()/regex-match-or-not is exactly the kind of both-arms case this repo's coverage bar requires).agent-sdksession is exactly the 11 names listed in Context above — no more, no fewer — so a future accidental addition of a 12th tool (or a write-capable one) fails the test, not just code review.apps/loopover-miner-ui/vite-chat-api.tsand itsvite.config.tsregistration are outside Codecov'scoverage.include:codecov.ymlexplicitly ignoresapps/**. This file is not gated by the 99% patch bar, but realvitesttests are still required so the app's ownnpm run test:ciand its local 90% vitest backstop (percodecov.yml's own comment) pass — cover: unauthenticated request rejected before reaching the handler (mirroringauth.test.ts's existing pattern), malformed/emptymessagesbody rejected, and a fake upstream stream correctly re-emitted as SSEdata:lines terminated by"done".npm run test:ciplus unshardednpm run test:coveragemust be green before this is pushed, per the repo's standing one-shot-PR discipline.Expected Outcome
A locally-running miner (
npm run devinapps/loopover-miner-ui) exposes an authenticated, streamingPOST /api/chatendpoint that answers natural-language questions grounded exclusively in the 11 existingloopover_miner_*read-only tools, using the miner's own already-configured localagent-sdkcredentials (no new credential handling), and never emits wallet/hotkey/coldkey/reward/payout/trust-score content even when a user asks for it directly. No write action of any kind is reachable through this endpoint. The chat-rail composer/message-list issues can point at this endpoint with no further backend work.Links & Resources
packages/loopover-miner/bin/loopover-miner-mcp.js— the 11 read-only tools this endpoint may call.packages/loopover-engine/src/miner/driver-factory.ts— provider/credential resolution to reuse.packages/loopover-engine/src/miner/agent-sdk-driver.ts— existing@anthropic-ai/claude-agent-sdkquery()precedent and its injected-AgentSdkQueryFntest seam.packages/loopover-engine/src/miner/local-write-tools.ts— the documented no-cloud-write boundary (LOCAL_WRITE_BOUNDARY), context for why this stays read-only.packages/loopover-engine/src/track-record-summary.ts—PUBLIC_FIELD_BLOCKLIST, the term set to reuse for the privacy backstop.apps/loopover-miner-ui/vite-auth.ts— the existing auth-cookie gate that already covers this new endpoint automatically.apps/loopover-miner-ui/vite-run-state-api.ts(+ siblingsvite-portfolio-queue-api.ts,vite-ledgers-api.ts,vite-governor-api.ts) andapps/loopover-miner-ui/vite.config.ts— the local API middleware convention to follow.apps/loopover-miner-ui/src/run-state-api.test.ts,apps/loopover-miner-ui/src/auth.test.ts— apps-side test convention.packages/loopover-engine/test/driver-factory.test.ts,packages/loopover-engine/test/agent-sdk-driver.test.ts— engine-sidenode:testconvention.test/unit/engine-subprocess-env.test.ts,test/unit/engine-telemetry-anonymize.test.ts— the root vitest-mirror convention Codecov actually measures forpackages/loopover-engine.codecov.yml— confirmsapps/**is excluded from patch coverage;packages/loopover-engine/src/**andpackages/loopover-miner/lib/**are included.__root.tsx); the composer/message-list/streaming-renderer/typing-indicator UI issues (the consumers of this endpoint); the action-dispatch chat issue (the separately-scoped, later write-capable surface this issue explicitly does not build).