Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
# 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`
|
||||
Reference in New Issue
Block a user