MosswartOverlord/frontend/src/api/endpoints.ts
Erik 79cf88d3f7 feat(agent): Phase 1 — chat-window AI assistant via Claude Code subprocess
Adds an in-dashboard AI assistant that answers questions about live game
state. Designed reactively (no background loops) — every user message in
the chat window or via /api/agent/ask runs one `claude -p` invocation.

Architecture:
- New host-side FastAPI service (agent/) on 127.0.0.1:8767, OUTSIDE the
  dereth-tracker Docker container because `claude` and ~/.claude
  credentials live on the host.
- nginx routes /api/agent/* to the host service.
- The same browser session cookie the tracker issues authenticates
  agent requests (shared SECRET_KEY).
- The agent shells out to `claude -p --session-id <uuid>` with
  cwd=/home/erik/MosswartOverlord. Sessions persist as JSONL on disk
  via Claude Code's built-in machinery.
- An MCP stdio server (agent/mcp_overlord.py) exposes tools to Claude:
  get_live_players, get_recent_rares, query_telemetry_db (read-only,
  parsed by sqlglot to reject DML/DDL), get_player_state, get_inventory,
  get_inventory_search, get_combat_stats, get_equipment_cantrips,
  get_quest_status, get_server_health, suitbuilder_search.
- Read-only PG role (overlord_agent_ro) is the second line of defense
  on the SQL tool — even a parser bypass can't mutate.

Frontend:
- AgentWindow.tsx — draggable chat window with localStorage-pinned
  session UUID, "New Chat" button, on-mount rehydration from
  /agent/sessions/{id}/history (parses Claude Code's JSONL).
- Wired into WindowRenderer + Sidebar (🤖 Assistant button).

Operational:
- systemd unit (overlord-agent.service) + install.sh.
- agent/README.md documents env vars, deploy flow, smoke tests.
- nginx/overlord.conf gets a new /api/agent/ location with 180s timeout.
- CLAUDE.md gets an "Overlord Assistant Mode" section briefing the
  agent on which tools to use and how to behave.

NOT YET DEPLOYED — server still needs:
1. Apply agent/sql/0001_overlord_agent_ro.sql + ALTER ROLE password
2. Add AGENT_DB_DSN to /home/erik/MosswartOverlord/.env
3. bash agent/install.sh (creates venv, installs unit, starts service)
4. sudo cp /home/erik/MosswartOverlord/nginx/overlord.conf to nginx + reload

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 20:43:59 +02:00

48 lines
1.7 KiB
TypeScript

import { apiFetch, apiPost } from './client';
import type { TelemetrySnapshot, CombatStatsMessage, ServerHealth } from '../types';
interface LiveResponse {
players: TelemetrySnapshot[];
}
interface CombatStatsResponse {
stats: CombatStatsMessage[];
}
// v1 response shapes: /total-rares → { all_time, today }, /total-kills → { total }
interface RaresResponse { all_time: number; today: number; }
interface KillsResponse { total: number; }
export const getLive = () => apiFetch<LiveResponse>('/live');
export const getCombatStats = () => apiFetch<CombatStatsResponse>('/combat-stats');
export const getServerHealth = () => apiFetch<ServerHealth>('/server-health');
export const getTotalRares = () => apiFetch<RaresResponse>('/total-rares');
export const getTotalKills = () => apiFetch<KillsResponse>('/total-kills');
export const getCharacterStats = (name: string) => apiFetch<Record<string, unknown>>(`/character-stats/${encodeURIComponent(name)}`);
// ─── Agent endpoints (host-side service via /api/agent/*) ──────────────────
export interface AgentAskResponse {
result: string;
session_id: string;
duration_ms: number;
num_turns: number;
is_error: boolean;
}
export interface AgentHistoryMessage {
role: 'user' | 'assistant';
text: string;
timestamp?: string;
}
export const agentAsk = (message: string, sessionId: string) =>
apiPost<AgentAskResponse>('/agent/ask', { message, session_id: sessionId });
export const agentNewSession = () =>
apiPost<{ session_id: string }>('/agent/sessions/new', {});
export const agentSessionHistory = (sessionId: string) =>
apiFetch<{ messages: AgentHistoryMessage[] }>(
`/agent/sessions/${encodeURIComponent(sessionId)}/history`,
);