diff --git a/docs/cockpit/2026-04-01-msp-assistant-harness-design.md b/docs/cockpit/2026-04-01-msp-assistant-harness-design.md new file mode 100644 index 00000000..66ae9c69 --- /dev/null +++ b/docs/cockpit/2026-04-01-msp-assistant-harness-design.md @@ -0,0 +1,363 @@ +# MSP Assistant Harness — Design Spec +**Date:** 2026-04-01 +**Status:** Draft — pending user review +**Source:** MSP_Assistant_Harness_Implementation_Plan.docx (v2.0, March 2026) + brainstorming session + +--- + +## Context + +The `/assistant` page currently works as a generic AI chat surface with a task lane side panel and a chat sidebar for session history. It functions well but reads as "AI chat with extras" rather than an MSP engineer's operational tool. + +The goal is to reframe the page as a **live triage cockpit** — the place where an engineer opens a ticket, works through it from intake to resolution, and closes with a structured handoff artifact. The underlying session, branching, and chat architecture is preserved. What changes is layout hierarchy, information density, field labelling, and the conclude output. + +Scope is broader than the original docx: includes all required backend changes to support the frontend properly. + +--- + +## Design Decisions + +### 1. Overall Layout — Stacked Zones + +``` +┌─────────────────────────────────────────────┐ +│ Incident Header (labelled fields, 1 row) │ +├────────────────────────┬────────────────────┤ +│ │ FlowPilot Asks │ +│ Steps Checklist │ (quick replies) │ +│ (left, ~55%) ├────────────────────┤ +│ │ What We Know │ +│ │ (evidence list) │ +├────────────────────────┴────────────────────┤ ← drag handle +│ Conversation Log (muted, darker bg) │ +├─────────────────────────────────────────────┤ +│ Compose area │ +└─────────────────────────────────────────────┘ +``` + +- Work zone (top) and conversation log (bottom) are **drag-resizable** via a handle +- Default split: ~55% work zone, ~45% chat +- Existing left sidebar (session history) unchanged +- Compose area is always pinned to bottom, spans full width +- `workZoneHeight` persisted to `localStorage` so split survives refresh + +### 2. Incident Header + +Single row with explicit micro-labels above each field: + +``` +CLIENT DEVICE CATEGORY HYPOTHESIS +Contoso Ltd ✏ jsmith-desktop ✏ DNS / Network ✏ Corrupted DNS cache on NIC ✏ + [CW #48291] [Resolve ▾] [⋯] +``` + +- Each field has its own `✏` icon (visible on hover) that opens an inline edit popover +- Fields populate from: (a) intake form on session create, (b) AI-inferred updates mid-session via `triage_update`, (c) manual engineer edits via `PATCH /ai-sessions/{id}/triage` +- PSA ticket number shown if linked; action buttons (Resolve, overflow menu) on the right +- Empty fields show muted placeholder text — never blank + +### 3. Work Zone — Steps + FlowPilot Asks + What We Know + +**Left panel (~55%): ordered step checklist** +- Steps displayed as a vertical list: `✓` completed, `→` active (blue border, white text), `○` pending +- Active step is visually distinct +- "Generate Script" CTA appears at the bottom when a script-generation step is active + +**Right panel (~45%): two stacked mini-panels** +- **FlowPilot Asks** (top, amber label): current question from AI. When `options` are provided, renders as quick-reply buttons — clicking a button submits that answer as a chat message. When no `options`, renders a compact free-text input. Panel is empty/hidden when no pending question. +- **What We Know** (bottom, muted label): running evidence list. Each entry: `✓ confirmed` / `✗ ruled out` / `? pending`. AI appends via `triage_update.evidence_items`; engineer can manually add or edit entries. + +### 4. Conversation Log Zone + +- Lives below the work zone, separated by a **drag handle** +- Background: `#13151c` (one step darker than page) — visually recedes +- Label: "CONVERSATION LOG" in muted colour (`text-muted`) +- Messages are compact: `you:` / `fp:` prefixes instead of full name/avatar bubbles +- Scrolls independently +- Not collapsible — drag handle gives control + +### 5. Conclude / Handoff Modal (redesigned) + +On opening "Close Case": + +1. **Header**: "Close Case — [Client Name]" + outcome selector (Resolved / Escalated / Parked) +2. **Structured fields** — pre-filled by streaming `/handoff-draft`, all editable: + - **Root Cause** (short text input) + - **Resolution** (what fixed it) + - **Steps Taken** (list, auto-populated from step checklist) + - **Recommendations** (next steps / preventive actions) +3. **Output destinations** (checkboxes): Post to CW ticket note / Save to Knowledge Base / Send client summary +4. **Confirm** button — triggers resolve/escalate/pause and passes structured fields into `ResolutionOutputGenerator` + +The existing `SessionResolutionOutput` model and `ResolutionOutputGenerator` service are reused. The `/handoff-draft` stream starts immediately on modal open — the engineer can begin editing while fields fill in. + +--- + +## Backend Changes Required + +### 1. New AISession columns (Alembic migration) + +Add to `ai_sessions` table: + +| Column | Type | Purpose | +|--------|------|---------| +| `client_name` | `VARCHAR(255)` | MSP client name for incident header | +| `asset_name` | `VARCHAR(255)` | Device / asset / user being worked on | +| `issue_category` | `VARCHAR(100)` | Human-readable category (e.g. "DNS / Networking") | +| `triage_hypothesis` | `TEXT` | Current working hypothesis — AI-updated + engineer-editable | +| `evidence_items` | `JSONB` | "What We Know" list — persisted for session resume | + +`evidence_items` format: `[{ "text": str, "status": "confirmed" | "ruled_out" | "pending" }]` + +Note: `problem_domain` (existing) is an internal classifier slug. `issue_category` is the human-readable display label for the header. Both coexist. + +### 2. New PATCH endpoint — triage metadata + +``` +PATCH /ai-sessions/{session_id}/triage +Auth: require_engineer_or_admin +Body: { client_name?, asset_name?, issue_category?, triage_hypothesis?, evidence_items? } +Response: { id, client_name, asset_name, issue_category, triage_hypothesis, evidence_items } +``` + +Used when the engineer edits any header field or evidence list manually. + +### 3. Updated schemas — TriageUpdate and QuestionItem.options + +**New `TriageUpdate` model** (returned in chat response when AI infers session context): + +```python +class TriageUpdate(BaseModel): + client_name: str | None = None + asset_name: str | None = None + issue_category: str | None = None + triage_hypothesis: str | None = None + evidence_items: list[dict] | None = None # appends to existing list +``` + +**Updated `ChatMessageResponse`:** +```python +class ChatMessageResponse(BaseModel): + # existing fields unchanged... + triage_update: TriageUpdate | None = None +``` + +**Updated `QuestionItem`** — add `options` for quick-reply buttons: +```python +class QuestionItem(BaseModel): + text: str + context: str = "" + options: list[str] | None = None # quick-reply labels; null = free-text fallback +``` + +### 4. unified_chat_service.py — triage extraction + +After generating each AI response, run a lightweight extraction to populate `triage_update`. Implementation options (pick one during implementation): + +- **Option A (recommended):** Embed structured extraction in the system prompt using an `[TRIAGE_UPDATE]` marker, similar to existing `[QUESTIONS]` / `[ACTIONS]` markers. AI emits the block if it has new triage signals; service parses it. +- **Option B:** Post-response extraction pass using a fast model (`claude-haiku-4-5`) with the last 3 messages as context. + +When `triage_update` contains non-null fields, the service auto-PATCHes the session record (so fields are persisted) AND returns `triage_update` in the response for the frontend to update the header immediately. + +### 5. New streaming endpoint — handoff draft + +``` +POST /ai-sessions/{session_id}/handoff-draft +Auth: require_engineer_or_admin +Response: StreamingResponse (text/event-stream) +``` + +Streams a structured handoff JSON object: +```json +{ "root_cause": "...", "resolution": "...", "steps_taken": ["..."], "recommendations": "..." } +``` + +Built from session context: `problem_summary`, `triage_hypothesis`, `evidence_items`, `conversation_messages` (last 20), step checklist from saved task lane state. + +### 6. Updated conclude schemas + +Add optional structured fields to `ResolveSessionRequest` and `EscalateSessionRequest`: + +```python +root_cause: str | None = None +steps_taken: list[str] | None = None +recommendations: str | None = None +``` + +Pass these into `ResolutionOutputGenerator._build_session_context()` to enrich `psa_ticket_notes` and `client_summary` outputs. + +### 7. Session read endpoint — include new triage fields + +Ensure the session detail response (`GET /ai-sessions/{id}`) returns the new fields so the frontend can restore header state on session resume. + +--- + +## Frontend Changes Required + +### 1. AssistantChatPage layout refactor + +Replace current layout (sidebar + chat column + TaskLane side panel) with the stacked cockpit layout described above. + +**New state:** +- `triageMeta: TriageMeta` — `{ client_name, asset_name, issue_category, triage_hypothesis, evidence_items }` +- `workZoneHeight: number` — persisted to `localStorage('rf-assistant-work-zone-height')` + +**On session load / resume:** populate `triageMeta` from the session response (new fields). + +**On AI response:** if `response.triage_update` is non-null, merge into `triageMeta` (partial update, preserve existing non-null values unless AI overwrites). + +### 2. New component: `IncidentHeader` + +``` +frontend/src/components/assistant/IncidentHeader.tsx +``` + +Props: `triageMeta`, `psaTicketId`, `sessionId`, `onFieldSave(field, value)`, `onResolve`, `onOverflow` + +- Renders labelled single-row header +- Each field: micro-label + value + `✏` icon (visible on hover) +- `✏` opens an `EditPopover` (small popover with text input + Save/Cancel) +- On Save: calls `aiSessionsApi.updateTriage(sessionId, { [field]: value })` +- Empty field shows muted placeholder (e.g. "Unknown client") + +### 3. Refactored component: `StepsPanel` (from TaskLane) + +``` +frontend/src/components/assistant/StepsPanel.tsx +``` + +Same `activeActions` data source. Renders as ordered checklist: +- Completed: `✓` + strikethrough label, muted +- Active: `→` + blue left border, white text, full opacity +- Pending: `○` + muted text + +Script generation CTA: shown at bottom when the active step has `command` containing "script" or when AI has flagged it. + +### 4. New component: `FlowPilotAsks` + +``` +frontend/src/components/assistant/FlowPilotAsks.tsx +``` + +Props: `questions: QuestionItem[]`, `onAnswer(answer: string)` + +- Shows first unanswered question (or empty/hidden state if none) +- When `question.options` is non-null: renders as button row, clicking calls `onAnswer(option)` +- When `question.options` is null: renders compact text input with Send button +- `onAnswer` calls `handleSend` in the parent page with the answer text + +### 5. New component: `WhatWeKnow` + +``` +frontend/src/components/assistant/WhatWeKnow.tsx +``` + +Props: `items: EvidenceItem[]`, `onAdd(text, status)`, `onEdit(index, text, status)` + +- Renders evidence list with status icons: `✓` (confirmed, green), `✗` (ruled out, red), `?` (pending, muted) +- "+ Add finding" link at bottom opens an inline input row +- Items are editable inline (click to edit) +- State lives in `AssistantChatPage` as part of `triageMeta.evidence_items`, synced to backend via `PATCH /triage` + +### 6. Drag handle — resizable split + +Implement as a thin handle bar between work zone and conversation log. On drag: +- Update `workZoneHeight` in state +- Persist to `localStorage` + +On mount: restore from `localStorage`, default to `55%` of available height. + +### 7. Compact conversation log + +Replace current `` bubble rendering in the log zone with a compact list: + +``` +you: Can't resolve external DNS, internal fine +fp: Ping passed — layer 3 OK. Run nslookup google.com. +you: Timed out on 1.1.1.1 too. +``` + +`ChatMessage` component still used for rich rendering (suggested flows, forks) but in a more compact variant. Full bubble rendering available on hover/expand if needed. + +### 8. Redesigned `ConcludeSessionModal` + +Replaces current simple textarea with the structured handoff form. On open: +1. Call `aiSessionsApi.getHandoffDraft(sessionId)` — streaming — populate fields as stream arrives +2. Render outcome selector + 4 structured fields (all `