The chatbot agent now has 14 tools (was 4) so it can answer about ANY
server situation from live data instead of a static snapshot:
- get_server_stats (now also returns clean count)
- get_top_channels, get_recent_activity, get_top_flagged
- search_messages (LIKE keyword search)
- get_user_messages, get_user_profile, get_user_reputation
- get_channel_culture
- get_message_detail (full AI analysis of one message)
- get_message_reviews (human moderation queue by status)
- get_voice_recordings (with transcriptions)
- get_moderation_timeline (daily flagged/warn/clean trend)
- get_corrections (AI false-positive correction history)
Security/quality:
- Every executor now uses parameterized drizzle queries (eq/like/and).
The old code interpolated model-supplied IDs into sql.raw() — a SQL
injection vector. Removed.
- Split static tool *definitions* into chatbot.toolDefs.ts (no DB import)
so the LLM-facing schema can be unit-tested without loading the
database/config layer. chatbot.tools.ts keeps only the executor.
Verified: tsc + biome clean, 40 backend tests pass (4 new covering the
tool-contract: names unique, required args declared, full situation
coverage).
Co-Authored-By: Claude Opus 5 (Nous Research)
The chatbot already had an agentic tool loop (get_server_stats,
get_top_channels, get_recent_activity, get_top_flagged), but processMessage
still baked a serverInsights snapshot into the system prompt and told the
model to "answer from that data". That defeats the tools: the model answered
from a stale snapshot instead of living numbers, and the guild/channel scope
the frontend sends was never forwarded to the tools.
Changes (services/backend/src/modules/chatbot):
- Remove getServerInsights() + ServerInsights (dead after this change).
- buildSystemPrompt(): drop the hardcoded stats block; instruct the model it
has NO memorized server numbers and MUST call a tool for any server-data
question, answering only from tool results.
- processMessage(): stop fetching insights; pass the request guildId/channelId
scope through to callLLM.
- callLLM(): accept scope; auto-fill empty guildId/channelId on tool calls from
the request scope so the model never has to guess IDs and tools always query
the right server.
Behavior: answers now come from live DB data via tools, scoped to the server
the user is chatting in. tsc + biome + 36 backend tests green.
Co-Authored-By: Claude Opus 5 (Nous Research)
Backend:
- New chatbot.tools.ts: 4 tools (get_server_stats, get_top_channels,
get_recent_activity, get_top_flagged) with real DB executors
- chatbot.service: agentic loop — stream:true, parse SSE, execute
tool_calls, feed results back, up to 4 rounds
- controller: resolve userId from X-User-Id header (no-login device
uuid) with auth middleware precedence; history/clear scoped per user
Frontend:
- use-chatbot-user: mint UUID in localStorage, send as X-User-Id
- chatbotApi.send/getHistory/clearHistory accept userId header
- client.ts: apiRequest supports custom headers per call
- provider: history load + send + clear keyed to device user id
Build & Deploy / build-and-push (backend) (push) Failing after 35s
Build & Deploy / build-and-push (discord-gateway) (push) Failing after 25s
Build & Deploy / build-and-push (proxy) (push) Failing after 25s
- Remove pnpm workspace, moon repo, and all monorepo tooling
- Delete packages/shared/, embed shared code directly into each service
- Copy packages/shared/src/* -> services/backend/src/shared/ and services/discord-gateway/src/shared/
- Replace all @bete/shared imports with @/shared/ path alias
- Remove @bete/shared workspace dependency from both services
- Update root package.json scripts from --filter to --prefix
- Rewrite Dockerfiles to build each service standalone
- Clean up biome.json, .gitignore, remove root drizzle.config.ts