# 01 — Support desk + admin AI inventory (auto-reply / AI assist) **Agent:** 1/10 · **Mode:** read-only inventory **Tools:** codehelper `project_context` → `kickoff` → `query`/`investigate`; Read/Grep/Shell fallback (`read_workspace_file` / `write_workspace_file` unavailable in focused MCP profile) **Related:** `docs/admin-roles-support/*`, `docs/plan-permissions/01-feature-keys.json` --- ## Executive summary Support desk is **shipped**: customer tickets + CSAT, staff queue/claim with `support_staff`, platform admin aggregate CSAT, agents directory. Platform AI already has a dedicated **`support` role slot** (provider / base_url / model / encrypted key) editable at **`/admin/settings` → AI roles**. LLM ticket auto-reply is an intentional **safe stub** (`TryAutoReplyLLM` → always `ErrAIAutoReplyDisabled`); Create / ReplyAsUser / ReplyAsAgent must not call Completers. No FAQ/canned/rule-based ticket auto-response exists yet (docs Ask is a separate rule tree and must not use `AIRoleSupport`). --- ## 1. Support desk — current surface ### 1.1 Schema | Migration | Purpose | |-----------|---------| | `025_support_center.sql` | `support_tickets`, `support_messages`, `support_notifications` | | `027_capabilities_support_perf.sql` | assignee / activity indexes | | `029_staff_roles.sql` | `users.staff_role` ∈ `admin\|developer\|support_staff` | | `030_support_desk.sql` | `resolved_by_user_id`, CSAT table + token cols, unassigned index, `ticket_claimed` / `csat_requested` kinds | **Ticket enums:** status `open|pending|resolved|closed`; category `billing|bug|account|other`; priority `low|normal|high`. **Message `author_role`:** `user|agent|system` (authorship, not staff RBAC). **CSAT:** `support_csat_ratings` — one rating per ticket (score 1–5 + comment). Public token columns exist but are **not wired**. ### 1.2 Domain packages | Path | Role | |------|------| | `apps/api/internal/support/types.go` | `Ticket`, `Message`, `CSAT*`, `CreateInput`, `ReplyInput`, list scopes | | `apps/api/internal/support/tickets.go` | `Create`, `Get`/`GetAdmin`, `ReplyAsUser`, `ReplyAsAgent`, `UpdateAdmin`, `listMessages` | | `apps/api/internal/support/desk.go` | scopes, `Claim`/`Release`, `GetAdminForActor` | | `apps/api/internal/support/agents.go` | agents directory via `staff_role` | | `apps/api/internal/support/ratings.go` | `SubmitCSAT`, `AggregateCSAT`, `attachCSAT` | | `apps/api/internal/support/ai_auto_reply.go` | **`TryAutoReplyLLM` stub** (sole LLM gate) | | `apps/api/internal/support/errors.go` | `ErrAIAutoReplyDisabled` | ### 1.3 HTTP API **Customer** (`RequireSession` + `RequireCompany`) — `server.go` ~494–501: | Method | Path | |--------|------| | GET/POST | `/api/support/tickets` | | GET | `/api/support/tickets/{id}` | | POST | `/api/support/tickets/{id}/messages` | | POST | `/api/support/tickets/{id}/csat` | | GET/POST | `/api/support/notifications` (+ read / read-all) | **Staff desk** (`RequireSupportDesk`) — ~304–313: | Method | Path | |--------|------| | GET | `/api/admin/support/tickets` (`scope=inbox\|mine\|unassigned\|all`) | | GET/PATCH | `/api/admin/support/tickets/{id}` | | POST | `…/messages`, `…/claim`, `…/release` | | GET | `/api/admin/support/agents` | **Platform admin only** — ~316–332: | Method | Path | |--------|------| | GET | `/api/admin/support/csat?from=&to=` | | PUT | `/api/admin/support/agents/{id}` | | GET/PUT | `/api/admin/settings` (includes `ai_roles`) | | PATCH | `/api/admin/users/{id}/staff-role` | Contract design: `docs/admin-roles-support/11-support-api-contract.json` · backend notes: `12-support-backend.md` · CSAT: `13-support-ratings.md`. ### 1.4 Staff access | Symbol | Path | Behavior | |--------|------|----------| | `ResolveStaffAccess` | `apps/api/internal/auth/staff.go` | `support_staff` → desk only; `admin`/`developer`/legacy platform admin → full | | `RequireSupportDesk` | `httpapi/middleware.go` | desk routes | | `RequirePlatformAdmin` | same | excludes `support_staff` from billing/settings/users | | `requireSupportDesk` / `requirePlatformAdmin` | `apps/web/src/lib/admin-gate.ts` | UI gates | Visibility: queue + claim (`inbox`/`mine`/`unassigned`; `all` = full admin). Cross-assignee GET → 404; reply on another’s claim → 409. ### 1.5 Web UI | Persona | Routes | Client | |---------|--------|--------| | Customer | `/support`, `/support/new`, `/support/[ticketId]` | `$lib/support/api.ts`, CSAT via `SupportTicketRating.svelte` | | Staff | `/admin/support`, `/admin/support/[id]` | `$lib/support/admin-api.ts` | | Role grant | `/admin/users` | `PATCH …/staff-role` | | AI keys | `/admin/settings` tab **AI roles** | `$lib/admin-ai-roles.ts` | Docs: `14-support-staff-ui.md`, `15-user-support-ui.md`. ### 1.6 Plan-permission keys (tenant Support Center) From `docs/plan-permissions/01-feature-keys.json` / `plan-feature-catalog.ts` — section `support`, defaults **ON** all public plans: | Key | Route | |-----|-------| | `support.center` | `/support` | | `support.ticket_create` | `/support/new` | | `support.ticket_thread` | `/support/[ticketId]` | | `shell.support_notifications` | notification bell | No plan keys for staff desk, AI auto-reply, or CSAT (CSAT rides on thread eligibility). --- ## 2. Admin AI configuration (platform) ### 2.1 Role catalog Stored in platform settings JSON (`company_settings` / SystemCompanyID) — **not** a dedicated SQL table. | Role key | Constant | Purpose today | |----------|----------|---------------| | `processing` | `AIRoleProcessing` / `RoleProcessing` | Product pipeline chat; falls back to legacy `openai` + env | | `vectorization` | `AIRoleVectorization` | Embeddings; env fallback | | `docs_api` | `AIRoleDocsAPI` | Config slot only — **/docs Ask must not use it** | | `support` | `AIRoleSupport` / `RoleSupport` | **Ticket-assist slot** — configure only; auto-reply gated by stub | Fields per role: `provider`, `base_url`, `model`, encrypted `api_key`, `enabled`, optional `extras`. Secrets masked on GET (`api_key_last4` / masked). ### 2.2 Existing AI admin settings paths | Layer | Path / symbol | |-------|----------------| | **UI** | `apps/web/src/routes/admin/settings/+page.svelte` — tabs Overview / **AI roles** / Mail / Other / Admin Users | | **Client load/save** | `apps/web/src/lib/admin-platform-settings.ts` — `GET|PUT /api/admin/settings` | | **Client AI roles** | `apps/web/src/lib/admin-ai-roles.ts` — `AI_ROLES`, `saveAIRoles`, `extractAIRoles`, `testAIRole` | | **API handlers** | `apps/api/internal/httpapi/admin_settings_handlers.go` — `handleGetAdminSettings` / `handlePutAdminSettings` | | **Storage / resolve** | `apps/api/internal/platformsettings/` — `ai_configs.go` (`publicAIConfigs`, `patchAIConfigs`, `ResolveAIConfig`), `types.go` (`AIRoles`, `AIRoleSupport`) | | **Runtime completer** | `apps/api/internal/aiprovider/roles.go` — `ResolveCompleterForRole` (supports `RoleSupport` when configured) | | **Tenant BYOK test (not platform roles)** | `POST /api/…/integrations/ai/test` — `handleTestAIIntegration` | | **Optional role probe** | Client expects `POST /api/admin/settings/ai-roles/{role}/test` — **not mounted**; `testAIRole` treats 404/501 as skipped | **ASSUMPTION:** Enabling LLM ticket assist should use `ResolveCompleterForRole(..., RoleSupport)` (or `ResolveAIConfig(AIRoleSupport)`) **only** inside/behind `TryAutoReplyLLM`, never from Create/Reply handlers directly. ### 2.3 Docs Ask (non-ticket, rule-based) `DocsAskGuide.svelte` + `$lib/docs-guide` — decision tree only. Explicitly must never call `AIRoleDocsAPI` or `AIRoleSupport`. Useful **pattern** for content-based auto-response before LLM fallback — not wired to tickets. --- ## 3. Auto-reply / AI assist — current state | Capability | Status | |------------|--------| | Admin config slot `ai_roles.support` | **Exists** (UI + API + tests) | | `aiprovider.ResolveCompleterForRole(RoleSupport)` | **Resolves** when slot configured | | `support.TryAutoReplyLLM` | **Stub refuses** (`ErrAIAutoReplyDisabled`) | | Call sites from Create / Reply* | **Forbidden by comments**; Create documents “do not call” | | FAQ / keyword / canned auto-reply | **Absent** | | Draft-assist UI for agents | **Absent** | | Job/queue for async auto-reply | **Absent** | | Plan/feature flag for auto-reply | **Absent** | | `author_role=system` messages | Schema allows; **no auto-writer** | Test lock: `ai_auto_reply_test.go` — `TestTryAutoReplyLLM_refuses`. --- ## 4. Gaps (richer detail / auto-response / AI fallback) ### 4.1 Richer ticket detail - Staff `admin-api.ts` `SupportTicket` omits `csat`, `csat_eligible`, `resolved_by_user_id` — API can attach CSAT on GetAdmin but **admin UI/types ignore it**. - No admin CSAT aggregate UI for `GET /api/admin/support/csat`. - No attachments, linked product/feed IDs, or structured metadata on tickets/messages. - No SLA timers, tags, macros, or customer org context beyond company name + requester email. - Public CSAT token invite flow (`csat_token_hash`) unused. - Message list is plain body text only (no markdown/HTML sanitization layer called out for AI drafts). ### 4.2 Auto-response from question content - No classifier on `CreateInput.subject` / `body` or category. - No canned/FAQ store or docs-guide-like tree for tickets. - No `author_role=system` first-response after create. - Create commits then returns — **no post-commit hook** for auto-reply (natural extension: after successful `Create` / `ReplyAsUser`, call a non-LLM matcher then optionally `TryAutoReplyLLM`). ### 4.3 AI fallback when auto fails - Intended entry: implement `TryAutoReplyLLM` using `AIRoleSupport` completer; today always fails closed. - No confidence threshold, human-handoff status (`pending`), or “AI attempted” audit fields. - No product enable flag separate from `ai_roles.support.enabled` (comments require an **explicit** gate). - Docs Ask must remain separate — do not reuse docs_api for ticket LLM. --- ## 5. Prior inventory note `docs/admin-roles-support/02-current-inventory.md` is **stale** relative to shipped staff roles + CSAT (pre–agents 6/12/13/14). Prefer `12`–`15` + this doc for current support/AI state. --- ## 6. Sources (codehelper + docs) - Investigate: `TryAutoReplyLLM`, `publicAIConfigs`, `GetAdmin` - Query: support tickets/CSAT, admin AI settings, plan support keys - Docs: `admin-roles-support/11–15`, `plan-permissions/01-feature-keys.json`, `06-defaults-matrix`