Remove repeated Plant, Financial and Workforce shortcut cards. Add site-scoped Plant and Financial sidebar attention status with automatic refresh. Include accumulated delivery processing, purchase order recovery, supplier reconciliation, plant data and user management changes and audit records.
5.4 KiB
How "Ask" works (MEGA) — brief for building the same thing in Healy
Correction first
The in-app chat does NOT use MCP. It calls tool functions directly inside the Next.js server.
MCP (src/mcp/server.ts) is a separate, optional, read-only wrapper over the same tools, run locally
over stdio for Claude Desktop/Code. Build the tool layer first; treat MCP as a thin extra.
Stack
- Next.js 16 (App Router), React 19, TypeScript, Tailwind 4, vitest
- PocketBase via
pocketbaseSDK, superuser client on the server only (pbAdmin()) - Model: MiniMax M3 via OpenAI-compatible endpoint (
https://api.minimax.io/v1); Anthropicclaude-opus-5behind the same interface.CHAT_PROVIDERselects. - Streaming: SSE from
POST /api/chat(plainReadableStream; browser reads withfetch) - Tool schemas:
zod->zod-to-json-schema - UI markdown:
react-markdown+remark-gfmmapped onto the app's own table styles
Architecture
src/lib/chat/— provider-neutral core:types.ts(no SDK imports),loop.ts(model -> tools -> model, with max-iterations valve),provider-anthropic.ts,provider-openai.ts(one SDK each),system-prompt.ts(built per request: date + user memories),think-filter.ts.src/lib/agent/— tools:tools.ts(read tools),tools-actions.ts(write tools),load.ts(fetching),format.ts(shaping +displaystrings),resolve.ts(name -> id),query.ts(genericquery_records),gateway.ts(the write seam),schema-cache.ts.src/lib/metrics/— ALL business calculations as pure tested functions, shared by dashboard and agent./api/chat— env gate, identify user, persist thread, run loop, stream events./dashboard/chat— client SSE reader, threads, attachments, confirm cards, charts, PDF.
Rules that make it trustworthy
- The model never does arithmetic. Tools return finished numbers + preformatted
display. Prompt bans sum/average/percent/"combined" and says what to do instead (list rows, say aggregate not available). The first wording failed a live probe — keep "combined value of top three" as a regression probe. - Name resolution returns ambiguity lists instead of guessing.
query_records: typed generic read, filters bound withpb.filter(), grouping/aggregation in JS.- Tool failures go back to the model as error results and are logged server-side.
Writes
Model calls propose_changes -> pending row in ask_log -> confirm card -> human presses Confirm ->
applyAction writes -> each op keeps a before snapshot -> Undo. Ops validated against live schema.
authorize() is the single permission seam with a NEVER_WRITABLE set (_superusers, users, chat tables).
Ops run sequentially so $ref: can reference records created earlier in the batch.
For Healy: remove the delete op kind entirely (union type, planner, apply). Decide deliberately
whether Undo may delete records the agent itself just created (recommended: yes, and nothing else).
Gotchas
MiniMax
- M2/M2.7 are text-only. M3 is the first accepting BOTH
toolsand images. Test both together. - Use the OpenAI-compatible route; the
/anthropicshim mangles tool-call args. - Reasoning streams inline in
<think>…</think>; strip it with a stateful filter (tags split across chunks). - Tool-call args arrive as string fragments per index; concatenate before
JSON.parse. - Key must be an Open Platform API key (not Token Plan subscription key, not Hailuo/MiniMax Agent).
- Image endpoint:
{base minus /v1}/v1/image_generation(appending /v1 to base gave /v1/v1). Anthropic budget_tokensand assistant prefill are removed (400). Usethinking: {type: "adaptive"}. PocketBasesort=-created/-updatedcan 400 with an empty body. Sort a real field or use autodate fields.getList(1, 500)silently truncates; usegetFullList.fields:that omits a date field makes later date logic silently skip rows.- A detail-less 400 is usually a bad filter/sort; reproduce with a direct
getFullList. Chat plumbing - History crosses JSON; types guarantee nothing. Normalise in the route (assistant turns arrive without
toolCalls). - Compare
Originto theHostheader, notrequest.url. - Agent loop needs more than 8 iterations for "read file -> match -> propose".
Brief to give the Healy agent
Build a chat agent for Healy shaped like MEGA's "Ask". Next.js + PocketBase, SSE from
POST /api/chat. Provider-neutral loop with two adapters (MiniMax M3 via OpenAI-compatible endpoint; Anthropic fallback). Tools are zod-schema'd functions; all business maths lives in pure tested functions shared with the UI. Reads: typed tools per domain + genericquery_records; model never does arithmetic; tools returndisplay. Writes: create and update only, NO delete. Propose -> pending log row -> human confirm -> apply, withbeforesnapshot and undo; validate against live schema; keep a never-writable list. API access: expose the same handlers as REST and as an MCP server, both authenticated. Also: strip<think>, reassemble fragmented tool args, normalise history at the route, log tool failures.
Decide deliberately (API + MCP + writes)
- Auth: Ask used an
x-ask-userheader + shared PIN during testing. A create/update API or MCP surface needs real per-user authentication. - MCP is headless (no human to confirm). Either keep MCP read-only, or let it only create PENDING actions a person confirms in the app (recommended).