Files
dashcaddy/status
Hermes e80e3273b7 [pending-judge] feat(DC-163): AI assistant that works — LLM intent engine + fixed chat panel
The floating assistant was broken end-to-end; Sami said "Do it that works".
Three user-visible bugs fixed + the promised LLM delegation implemented:

FRONTEND (status/js/ai-chat.js — rewritten):
- Response parsing: old code read data.data; the API returns the FLAT ok()
  envelope — EVERY reply rendered "Sorry, something went wrong". Now reads
  the real shape (contract asserted by route test).
- Suggestion chips: onclick="...send('"Deploy Plex"')" — nested quotes,
  invalid JS, dead buttons (shipped like this). Now data-driven buttons with
  addEventListener.
- Deploy button: called window.openAppSelector which NEVER existed anywhere
  in the tree (grep-verified). Now opens the REAL deploy modal via new
  window.openDeployConfigFor(templateId) exported by app-selector.js —
  resolves against the live API template cache, reuses showDeployConfig
  (same path a manual catalog click takes). Widgets route to the grid.
- Conversation history (last 6 turns) now rides along per request, engine
  badge in header, ⚙ settings sub-panel (baseUrl/model/API key + Test +
  Remove), all server text via textContent (no HTML injection), CSP-safe
  (no inline handlers).

BACKEND (routes/ai-intent.js):
- TWO engines, one contract. LLM classifier (OpenAI-compatible chat
  completions) with system prompt embedding the LIVE 78-app catalog + the
  six wizard categories; regex patterns kept as the zero-config default and
  the automatic failover (HTTP error, timeout via AbortController,
  unparseable output -> pattern answers; response carries engine +
  llmError so the UI can say which served — no silent degradation).
- Closed-vocabulary schema validation: intent MUST be in the known set else
  'unknown'; appId MUST exist in APP_TEMPLATES else nulled (hallucinated
  ids cannot leak into deploy plans); categories filtered to the wizard's
  real keys; reply stripped of control chars + bounded to 600; history
  role-filtered (only user/assistant pass — client-injected system turns
  dropped) + capped; input capped to 1000 chars.
- enrich() rebuilds the response contract per intent; pattern-canned
  response is dropped when the LLM rerouted the intent (no stale shape);
  deployPlan body.port now fills from the template's defaultPort (was
  hardcoded null); deployPlan shape asserted against wizard usage.
- Key storage: PUT /ai/settings stores {baseUrl,model,apiKey} in the
  ENCRYPTED credential store (key 'ai:llm') via ctx.credentialManager —
  NOT config.json (GET /config echoes the raw file to the browser; that
  route's shape was verified before this decision). Blank apiKey on PUT
  keeps the stored one. GET /ai/settings reports configured/source/model/
  hasApiKey — the key value NEVER crosses the wire (asserted). Env
  fallback AI_LLM_BASE_URL/AI_LLM_API_KEY/AI_LLM_MODEL + legacy
  LLM_PROXY_URL for headless installs. POST /ai/settings/test = one-shot
  real classify probe returning ok/sample or ok:false/reason (never
  throws). DELETE clears.
- GET /ai/capabilities now reports engine + llm status (pattern was honest
  marketing before; now it can be true).

MOUNT (src/app.js): aiIntentRoutes receives siteConfig, log, fetchImpl
(ctx.fetchT — test-injectable) and credentialManager.

Docs: OpenAPI +4 AI path entries (intent, capabilities, settings, settings/test); CHANGELOG entry; dist bundle rebuilt
(openDeployConfigFor present in features.js; SW cache c62511c40d).

Tests: 20 NEW in __tests__/routes/ai-intent-engine.test.js (engine
selection w/ and w/o store, off-catalog phrasing via mock LLM,
hallucinated-id nulling, illegal intent coercion, HTTP-500 failover,
AbortError timeout failover, markdown-fence repair, reply sanitization,
history cap+role filter, env config path, intent-disagreement rebuild w/
real jellyfin port 8096, flat envelope contract, 400s, 1000-char cap,
settings GET/PUT/DELETE/test incl. key-never-echoed assertions,
capabilities engine reporting) + the 18 original pattern tests unchanged +
composition-root e2e 8 pass. Full suite: 3643 passed; only the pre-existing
pdf-parse billing family red (6 failed in documented band [5,7], patch
touches no billing file).
2026-10-08 14:42:31 -07:00
..
2026-03-05 02:26:12 -08:00
2026-03-05 02:26:12 -08:00