# 08 — Staff auto / AI assist UI **Agent:** 8/10 · **Status:** Implemented **Coordinates with:** `02-contract` (fields), `05-ticket-detail` (schema + PATCH), `03`/`04` (match + AI drafts), `14-support-staff-ui` (desk shell) --- ## PROBLEM Staff need to see auto-match / AI assist outcomes on a ticket, approve or edit `draft_only` AI drafts, disable further auto per ticket, and filter the queue for `needs_human` / `ai_draft` — without elevating `support_staff` beyond the existing desk gate. --- ## WHAT SHIPPED ### Access (least privilege preserved) | Actor | Surfaces | |-------|----------| | `support_staff` | `/admin/support/**` only via `requireSupportDesk` / `RequireSupportDesk` | | Full admin | Same desk + assign picker (unchanged) | - New endpoints mount under the **same** `RequireSupportDesk` group as claim/reply. - Approve/discard/get still call `staffMayAccessTicket` (support_staff: own claim or unassigned open/pending; cross-assignee → 404 / 409). - No KB/settings/billing routes exposed to support_staff. ### Queue (`/admin/support`) - Filter chips: **Any auto state** · **Needs human** · **AI draft** (`?flag=`). - Persisted in URL with existing `scope` / `status`. - **Auto** column: status badge or “Auto off”. - API: `GET /api/admin/support/tickets?flag=needs_human|ai_draft` - `ai_draft` → `auto_reply_status = ai_draft` - `needs_human` → `handed_off|failed|skipped` ### Ticket detail (`/admin/support/[id]`) - **Auto-match / AI assist** panel: status, last attempt, disable / re-enable auto (`PATCH { auto_reply_disabled }`). - When `auto_reply_status=ai_draft`: edit draft body → **Approve & send** or **Discard draft**. - Thread bubbles label auto/KB/AI messages; drafts stay internal-note style. - Client falls back to public reply + disable if approve/discard routes are not yet mounted (404/501). ### API client `$lib/support/admin-api.ts` + `$lib/support/auto-assist.ts` | Method | Path | |--------|------| | list (+ `flag`) | `GET …/tickets` | | update (incl. `auto_reply_disabled`) | `PATCH …/tickets/{id}` | | approve draft | `POST …/tickets/{id}/ai-draft/approve` | | discard draft | `POST …/tickets/{id}/ai-draft/discard` | Domain: `apps/api/internal/support/staff_auto.go` (`ApproveAIDraft`, `DiscardAIDraft`, `ApplyQueueFlag`). --- ## FILES | Area | Path | |------|------| | Helpers | `apps/web/src/lib/support/auto-assist.ts` | | Client | `apps/web/src/lib/support/admin-api.ts` | | Queue UI | `apps/web/src/routes/admin/support/+page.svelte` | | Detail UI | `apps/web/src/routes/admin/support/[id]/+page.svelte` | | Domain | `apps/api/internal/support/staff_auto.go`, `types.go` (`ListFilter.Flag`), `errors.go` | | List filter | `apps/api/internal/support/tickets.go` (`ApplyQueueFlag`) | | HTTP | `support_handlers.go`, `server.go` (approve/discard mounts) | --- ## VERIFICATION ```text cd apps/web && npm run check cd apps/api && go test ./internal/support/ ./internal/httpapi/ -count=1 ``` Manual: 1. As `support_staff`: open `/admin/support?flag=needs_human` and `?flag=ai_draft` — no billing/settings nav. 2. Ticket with FAQ/AI outcome: panel shows status; **Disable auto** sticks across refresh. 3. `ai_draft` ticket: edit draft → Approve & send → customer-visible agent reply; draft queue clears. 4. Discard → `handed_off` / auto disabled; ticket remains claimable. 5. Ticket assigned to another agent → get/approve → 404/409 (not full admin leak). --- ## ASSUMPTIONS - ASSUMPTION: Ticket auto columns come from agent 5 migration `031_support_ticket_detail` (+ agent 3 `032` KB). UI tolerates missing fields as empty/`none`. - ASSUMPTION: `draft_only` AI posts internal notes with `is_auto_reply` + `auto_source=ai` and sets `auto_reply_status=ai_draft` (agent 4). - ASSUMPTION: Public staff reply already sets `auto_reply_disabled` (existing `ReplyAsAgent`). ## ROLLBACK Revert staff UI/client + `staff_auto.go` + approve/discard routes + `ListFilter.Flag`. Leave schema migrations in place.