{ "doc": "01-extension-points", "agent": "1/10", "generated_for": "support-auto", "related_docs": [ "docs/support-auto/01-inventory.md", "docs/admin-roles-support/11-support-api-contract.json", "docs/admin-roles-support/12-support-backend.md", "docs/admin-roles-support/13-support-ratings.md", "docs/admin-roles-support/14-support-staff-ui.md", "docs/plan-permissions/01-feature-keys.json" ], "ai_admin_settings_paths": { "web_ui": { "route": "/admin/settings", "file": "apps/web/src/routes/admin/settings/+page.svelte", "tab": "ai", "label": "AI roles", "roles_shown": ["processing", "vectorization", "docs_api", "support"] }, "web_client": { "platform_settings": "apps/web/src/lib/admin-platform-settings.ts", "ai_roles": "apps/web/src/lib/admin-ai-roles.ts", "symbols": [ "loadPlatformAdminSettings", "savePlatformAdminSettings", "saveAIRoles", "extractAIRoles", "testAIRole", "AI_ROLES", "AI_ROLE_META.support" ], "api": { "get_put": "GET|PUT /api/admin/settings", "payload_key": "ai_roles", "role_probe_expected": "POST /api/admin/settings/ai-roles/{role}/test", "role_probe_status": "client_optional_404_skipped_not_mounted" } }, "api": { "handlers": "apps/api/internal/httpapi/admin_settings_handlers.go", "symbols": ["handleGetAdminSettings", "handlePutAdminSettings"], "auth": ["RequireSession", "RequirePlatformAdmin"], "mount": "apps/api/internal/httpapi/server.go" }, "platformsettings": { "package": "apps/api/internal/platformsettings", "role_constants": { "file": "apps/api/internal/platformsettings/types.go", "AIRoleProcessing": "processing", "AIRoleVectorization": "vectorization", "AIRoleDocsAPI": "docs_api", "AIRoleSupport": "support", "AIRoles": ["processing", "vectorization", "docs_api", "support"] }, "symbols": [ { "name": "ValidAIRole", "path": "apps/api/internal/platformsettings/ai_configs.go", "line": 30 }, { "name": "publicAIConfigs", "path": "apps/api/internal/platformsettings/ai_configs.go", "line": 39, "sym": "sym:descrybe-v2:apps/api/internal/platformsettings/ai_configs.go:39:publicAIConfigs" }, { "name": "patchAIConfigs", "path": "apps/api/internal/platformsettings/ai_configs.go", "line": 116 }, { "name": "ResolveAIConfig", "path": "apps/api/internal/platformsettings/ai_configs.go", "line": 270 } ], "storage_note": "JSON in platform company_settings (SystemCompanyID); no dedicated AI roles table" }, "aiprovider": { "file": "apps/api/internal/aiprovider/roles.go", "RoleSupport": "support", "symbols": [ { "name": "ResolveCompleterForRole", "path": "apps/api/internal/aiprovider/roles.go", "line": 44, "sym": "sym:descrybe-v2:apps/api/internal/aiprovider/roles.go:44:ResolveCompleterForRole", "note": "May resolve RoleSupport when slot configured; ticket auto-reply must still gate via TryAutoReplyLLM" } ] }, "not_for_tickets": { "docs_ask": { "component": "apps/web/src/lib/components/docs/DocsAskGuide.svelte", "guide": "apps/web/src/lib/docs-guide", "rule": "rule-based only; never AIRoleDocsAPI or AIRoleSupport" }, "tenant_ai_test": { "path": "POST /integrations/ai/test", "handler": "handleTestAIIntegration", "note": "company BYOK probe, not platform ai_roles" } } }, "support_desk_inventory": { "schema": [ "apps/api/sql/schema/025_support_center.sql", "apps/api/sql/schema/027_capabilities_support_perf.sql", "apps/api/sql/schema/029_staff_roles.sql", "apps/api/sql/schema/030_support_desk.sql" ], "service_package": "apps/api/internal/support", "key_symbols": [ { "name": "Create", "path": "apps/api/internal/support/tickets.go", "line": 252, "hook_note": "Post-create extension for content auto-reply; must not call Completer directly" }, { "name": "ReplyAsUser", "path": "apps/api/internal/support/tickets.go", "line": 306 }, { "name": "ReplyAsAgent", "path": "apps/api/internal/support/tickets.go", "line": 369, "hook_note": "Human-only; draft-assist UI should call TryAutoReplyLLM separately" }, { "name": "GetAdmin", "path": "apps/api/internal/support/tickets.go", "line": 198, "sym": "sym:descrybe-v2:apps/api/internal/support/tickets.go:198:GetAdmin", "hook_note": "Richer staff detail / CSAT attach point" }, { "name": "TryAutoReplyLLM", "path": "apps/api/internal/support/ai_auto_reply.go", "line": 18, "sym": "sym:descrybe-v2:apps/api/internal/support/ai_auto_reply.go:18:TryAutoReplyLLM", "status": "stub_refuses", "error": "ErrAIAutoReplyDisabled" }, { "name": "SubmitCSAT", "path": "apps/api/internal/support/ratings.go", "line": 18 }, { "name": "AggregateCSAT", "path": "apps/api/internal/support/ratings.go", "line": 78 }, { "name": "Claim", "path": "apps/api/internal/support/desk.go", "line": 108 }, { "name": "ResolveStaffAccess", "path": "apps/api/internal/auth/staff.go", "line": 64 } ], "http_routes": { "customer_prefix": "/api/support", "staff_prefix": "/api/admin/support", "staff_middleware": "RequireSupportDesk", "admin_only_examples": [ "GET /api/admin/support/csat", "PUT /api/admin/support/agents/{id}", "GET|PUT /api/admin/settings" ] }, "web": { "customer": [ "apps/web/src/routes/support/+page.svelte", "apps/web/src/routes/support/new/+page.svelte", "apps/web/src/routes/support/[ticketId]/+page.svelte" ], "staff": [ "apps/web/src/routes/admin/support/+page.svelte", "apps/web/src/routes/admin/support/[id]/+page.svelte" ], "clients": [ "apps/web/src/lib/support/api.ts", "apps/web/src/lib/support/admin-api.ts", "apps/web/src/lib/support/types.ts" ] }, "plan_permission_keys": { "section": "support", "keys": [ "support.center", "support.ticket_create", "support.ticket_thread" ], "shell": ["shell.support_notifications"], "defaults": "ON for all public plans per docs/plan-permissions/06-defaults-matrix", "missing_for_auto_reply": [ "no support.auto_reply key", "no support.ai_assist key", "CSAT not a separate feature key" ] } }, "extension_points": { "richer_ticket_detail": [ { "id": "staff_get_payload", "target": "GetAdmin / handleAdminGetSupportTicket", "path": "apps/api/internal/support/tickets.go", "opportunity": "Already joins company/requester/assignee + messages + attachCSAT; expose resolved_by, richer org context" }, { "id": "admin_client_types", "target": "SupportTicket in admin-api.ts", "path": "apps/web/src/lib/support/admin-api.ts", "gap": "Omits csat / csat_eligible / resolved_by_user_id despite API capability" }, { "id": "staff_detail_ui", "target": "admin/support/[id]/+page.svelte", "gap": "Shows subject, category, priority, company, thread, claim/assign — no CSAT panel, no linked resources, no attachments" }, { "id": "csat_aggregate_ui", "target": "GET /api/admin/support/csat", "gap": "API exists; no admin UI page/widget" }, { "id": "schema_extensions", "note": "No attachments/metadata/product_id columns; author_role=system available for bot messages; csat_token_* unused" } ], "auto_response_from_question_content": [ { "id": "post_create_hook", "preferred_insert": "after successful Create commit (or async job enqueued from handleCreateSupportTicket)", "inputs": ["CreateInput.subject", "CreateInput.body", "CreateInput.category"], "pattern_reuse": "DocsAskGuide / docs-guide decision tree (rule-based, no LLM)", "output": "insert support_messages author_role=system OR ReplyAsAgent-equivalent with system author" }, { "id": "post_user_reply_hook", "target": "ReplyAsUser", "note": "Same matcher for follow-ups; reopen already sets status=open" }, { "id": "missing_today", "items": [ "no FAQ/canned reply store", "no keyword classifier", "no system first-response writer" ] } ], "ai_fallback_when_auto_fails": [ { "id": "sole_llm_gate", "target": "TryAutoReplyLLM", "path": "apps/api/internal/support/ai_auto_reply.go", "line": 18, "current": "returns ErrAIAutoReplyDisabled", "intended_deps": [ "platformsettings.AIRoleSupport / ResolveAIConfig", "aiprovider.ResolveCompleterForRole(..., RoleSupport)" ], "contract": "Create/ReplyAsUser/ReplyAsAgent must not call Completer; route AI through this function" }, { "id": "config_prerequisite", "path": "/admin/settings ai_roles.support", "fields": ["provider", "base_url", "model", "api_key", "enabled"], "note": "Slot configurable today; product still needs explicit enable beyond stub" }, { "id": "suggested_flow", "steps": [ "rule/FAQ matcher on question content", "on miss or low confidence → TryAutoReplyLLM", "on LLM refuse/error → leave open for staff claim (no silent drop)", "optional: draft-only for agent UI vs auto-post system message" ] }, { "id": "do_not", "items": [ "wire DocsAskGuide to AIRoleSupport", "use AIRoleDocsAPI for tickets", "call ResolveCompleterForRole from ReplyAsAgent" ] } ] }, "gaps_summary": { "ai_admin": "paths exist and support role is first-class", "auto_reply": "stub only; no content matcher; no enable flag/job", "staff_detail": "basic thread UI; CSAT/org depth/attachments missing in staff client", "plan_keys": "tenant support.* only; no auto-reply entitlements" } }