MosswartOverlord/static/assets/IssuesWindow-ea-1SCjj.js
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

1 line
6.3 KiB
JavaScript

import{r as n,a as W,j as e,D as L}from"./index-Dcc3Au5i.js";import"./react-yfL0ty4i.js";const O={plugin:{label:"Plugin",color:"#8844cc"},overlord:{label:"Overlord",color:"#4488cc"},nav:{label:"Nav",color:"#44aa44"},macro:{label:"Macro",color:"#cc8844"},other:{label:"Other",color:"#888888"}},i={padding:"3px 6px",fontSize:"0.8rem",border:"1px solid #555",background:"#2a2a2a",color:"#ddd",borderRadius:0},N={...i,fontSize:"0.75rem"},g={padding:"4px 12px",background:"#4a80c0",color:"#fff",border:"1px solid #336699",cursor:"pointer",fontSize:"0.75rem"},a={padding:"3px 8px",background:"#444",color:"#ccc",border:"1px solid #555",cursor:"pointer",fontSize:"0.7rem"},F=({id:P,zIndex:A})=>{const[h,R]=n.useState([]),[c,m]=n.useState(""),[y,u]=n.useState(""),[f,$]=n.useState("plugin"),[v,d]=n.useState(null),[p,j]=n.useState(""),[b,S]=n.useState(""),[C,z]=n.useState(""),[T,k]=n.useState({}),x=n.useCallback(async()=>{try{const t=await W("/issues");R((t.issues??[]).sort((r,s)=>(r.resolved?1:0)-(s.resolved?1:0)))}catch{}},[]);n.useEffect(()=>{x()},[x]);const l=async(t,r)=>{await fetch(`/api${t}`,{...r,credentials:"include",headers:{"Content-Type":"application/json",...r.headers}}),x()},E=async()=>{c.trim()&&(await l("/issues",{method:"POST",body:JSON.stringify({title:c.trim(),description:y.trim(),category:f})}),m(""),u(""))},B=t=>{if(v===t.id){d(null);return}d(t.id),j(t.title),S(t.description||""),z(t.category||"other")},I=async t=>{p.trim()&&(await l(`/issues/${t}`,{method:"PATCH",body:JSON.stringify({title:p.trim(),description:b.trim(),category:C})}),d(null))},w=async t=>{const r=(T[t]||"").trim();r&&(await l(`/issues/${t}/comments`,{method:"POST",body:JSON.stringify({text:r})}),k(s=>({...s,[t]:""})))};return e.jsxs(L,{id:P,title:"Issues Board",zIndex:A,width:540,height:520,children:[e.jsxs("div",{style:{flex:1,overflowY:"auto",padding:6,fontSize:"0.8rem"},children:[h.length===0&&e.jsx("div",{style:{padding:10,color:"#888",textAlign:"center"},children:"No open issues"}),h.map(t=>{const r=O[t.category]||O.other,s=t.created?new Date(t.created).toLocaleDateString("sv-SE"):"",D=t.comments||[];return e.jsxs("div",{style:{padding:"6px 8px",marginBottom:4,background:"#1f1f1f",borderRadius:3,border:"1px solid #333",opacity:t.resolved?.55:1},children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6,flexWrap:"wrap"},children:[e.jsx("span",{style:{fontSize:"0.65rem",padding:"1px 6px",borderRadius:3,background:r.color,color:"#fff",fontWeight:600},children:r.label}),e.jsx("strong",{style:{fontSize:"0.8rem",flex:1},children:t.title}),e.jsxs("span",{style:{fontSize:"0.65rem",color:"#888"},children:["by ",t.author||"User"]}),e.jsx("span",{style:{color:"#666",fontSize:"0.65rem"},children:s})]}),t.description&&e.jsx("div",{style:{color:"#999",marginTop:3,fontSize:"0.75rem"},children:t.description}),e.jsxs("div",{style:{display:"flex",gap:4,marginTop:4},children:[t.resolved?e.jsxs(e.Fragment,{children:[e.jsx("button",{style:{...a,fontSize:"0.65rem"},onClick:()=>l(`/issues/${t.id}`,{method:"PATCH",body:JSON.stringify({resolved:!1})}),children:"↻ Reopen"}),e.jsx("button",{style:{...a,fontSize:"0.65rem",color:"#c66"},onClick:()=>{confirm(`Delete issue "${t.title}"?`)&&l(`/issues/${t.id}`,{method:"DELETE"})},children:"🗑 Delete"})]}):e.jsx("button",{style:{...a,fontSize:"0.65rem",background:"rgba(68,204,68,0.15)",color:"#4c4",border:"1px solid rgba(68,204,68,0.3)"},onClick:()=>l(`/issues/${t.id}`,{method:"PATCH",body:JSON.stringify({resolved:!0})}),children:"✓ Resolve"}),e.jsx("button",{style:{...a,fontSize:"0.65rem"},onClick:()=>B(t),children:"✎ Edit"})]}),v===t.id&&e.jsxs("div",{style:{marginTop:4,padding:4,background:"#222",borderRadius:3},children:[e.jsxs("div",{style:{display:"flex",gap:4,marginBottom:4},children:[e.jsx("input",{value:p,onChange:o=>j(o.target.value),style:{...i,flex:1}}),e.jsxs("select",{value:C,onChange:o=>z(o.target.value),style:N,children:[e.jsx("option",{value:"plugin",children:"Plugin"}),e.jsx("option",{value:"overlord",children:"Overlord"}),e.jsx("option",{value:"nav",children:"Nav"}),e.jsx("option",{value:"macro",children:"Macro"}),e.jsx("option",{value:"other",children:"Other"})]})]}),e.jsxs("div",{style:{display:"flex",gap:4},children:[e.jsx("textarea",{value:b,onChange:o=>S(o.target.value),rows:2,style:{...i,flex:1,fontSize:"0.75rem",resize:"vertical"}}),e.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:2},children:[e.jsx("button",{style:{...g,fontSize:"0.7rem",padding:"3px 8px"},onClick:()=>I(t.id),children:"Save"}),e.jsx("button",{style:{...a},onClick:()=>d(null),children:"Cancel"})]})]})]}),e.jsxs("div",{style:{marginTop:4,paddingTop:4,borderTop:"1px solid #2a2a2a"},children:[D.length===0?e.jsx("div",{style:{color:"#555",fontSize:"0.7rem",padding:"2px 0"},children:"No comments yet"}):D.map(o=>e.jsxs("div",{style:{marginBottom:3,fontSize:"0.72rem"},children:[e.jsx("span",{style:{color:"#8ac",fontWeight:500},children:o.author||"Anonymous"}),e.jsx("span",{style:{color:"#555",marginLeft:6,fontSize:"0.6rem"},children:o.created?new Date(o.created).toLocaleDateString("sv-SE"):""}),e.jsx("div",{style:{color:"#bbb",marginTop:1},children:o.text})]},o.id)),e.jsxs("div",{style:{display:"flex",gap:4,marginTop:3},children:[e.jsx("input",{value:T[t.id]||"",onChange:o=>k(J=>({...J,[t.id]:o.target.value})),placeholder:"Add a comment...",style:{...i,flex:1,fontSize:"0.75rem"},onKeyDown:o=>{o.key==="Enter"&&w(t.id)}}),e.jsx("button",{style:{...g,fontSize:"0.7rem",padding:"3px 8px"},onClick:()=>w(t.id),children:"Post"})]})]})]},t.id)})]}),e.jsxs("div",{style:{padding:6,borderTop:"1px solid #333"},children:[e.jsxs("div",{style:{display:"flex",gap:4,marginBottom:4},children:[e.jsx("input",{value:c,onChange:t=>m(t.target.value),placeholder:"Issue title...",style:{...i,flex:1},onKeyDown:t=>{t.key==="Enter"&&E()}}),e.jsxs("select",{value:f,onChange:t=>$(t.target.value),style:N,children:[e.jsx("option",{value:"plugin",children:"Plugin"}),e.jsx("option",{value:"overlord",children:"Overlord"}),e.jsx("option",{value:"nav",children:"Nav"}),e.jsx("option",{value:"macro",children:"Macro"}),e.jsx("option",{value:"other",children:"Other"})]})]}),e.jsxs("div",{style:{display:"flex",gap:4},children:[e.jsx("textarea",{value:y,onChange:t=>u(t.target.value),placeholder:"Description (optional)...",rows:2,style:{...i,flex:1,fontSize:"0.75rem",resize:"vertical"}}),e.jsx("button",{style:{...g,alignSelf:"flex-end"},onClick:E,children:"Add"})]})]})]})};export{F as IssuesWindow};